java實現簽名驗簽詳解(含所有代碼)


第一部分:什么是簽名驗簽?

私鑰:可以解密公鑰加密的數據

公鑰:可以解密私鑰加密的數據

也就是說公鑰和私鑰之間可以互相加解密

 

公鑰加密私鑰解密稱之為——加解密

私鑰加密公鑰解密稱之為——簽名驗簽

 

簽名:使用私鑰對數據進行加密,該操作稱之為——簽名

驗簽:使用與私鑰對應的公鑰進行解密,該操作稱之為——驗簽

到此知道什么是公鑰什么是私鑰,以及區別和可以用來干嘛的了。那么下面開始進入正題(如果公鑰私鑰和簽名驗簽的概念還有不明白的朋友請自行百度,這篇博客的重點是關於如何通過java代碼在實現簽名驗簽)

第二部分:java實現簽名驗簽

廢話:數據在網絡中通信,安全一直是一個比較核心的問題和困擾,博主在2018年的時候,參與過一個支付項目的開發,凡是鑒於當時的水平原文,對於數據安全那一塊的開發並沒有參與,僅僅是使用了別人寫好的api然后調用。印象比較深刻的就是pfx文件和cer文件。當時就知道pfx是用來簽名的,cer文件是用來驗簽的。別的就不知道了。后來到了現在的這家公司,我也到了支付小組,發現這邊的數據傳輸是通過時間戳和一些約定的其他的參數來做一個MD5摘要(這里強調下,MD5不是加密,而是一種信息摘要的算法,是一種散列函數),瞬間就感覺好low的。然后我就會想起之前公司的方式。花了大量的時間去網上查閱資料和看博客。但是結果發現很多都是你抄我我抄你,而且網上的很多根本就沒法用於生產,你們不信自己百度就知道了。你去看看你的證書或者是私鑰怎么給對方,直接將串給對方嗎?這樣是不是顯然沒有達到生成的級別

總體思路:先介紹下總體思路,有助於讀者更好的理解本博客的內容。服務端,創建根證書(相當於ca機構,https協議之所以能夠保證數據的安全傳輸,其核心就是簽名驗簽,如果您對這部分也不了解,或者說想學習下這部分,請給博主留言。只要有一人想知道。我就不惜下班后加班加點寫博客。為你們解釋清楚),然后通過根證書來創建實際來簽名驗簽的證書,當然,除了根證書,這樣的用來簽名驗簽數據的證書需要有兩套。為什么需要兩套呢?

①服務端自己的一套公私鑰(服務端的公鑰是需要先提供給客戶端的)。這一套的作用是:當服務端向客戶端傳輸數據的時候,服務端使用服務端的私鑰進行簽名。然后客戶端使用服務端的公鑰驗簽,這樣客戶端可以驗證服務端的身份和數據是否被篡改

②客戶端自己也有一套公私鑰(當然客戶端的這一套是需要服務端提供的,服務端將客戶端這套的私鑰提供給客戶端,同時服務端需要保留客戶端的公鑰)。這一套的作用是:當客戶端向服務端傳輸數據的時候,客戶端需要通過客戶端的私鑰來簽名,而服務端剛好可以使用客戶端的公鑰來驗簽,以判斷數據在傳輸的過程中是否被篡改過

明白沒?沒明白看代碼。我會注釋的很詳細的

2.1、創建根證書

  1 import com.example.signature.util.IssueCertUtils;
  2 import org.slf4j.Logger;
  3 import org.slf4j.LoggerFactory;
  4 import sun.security.tools.keytool.CertAndKeyGen;
  5 import sun.security.x509.X500Name;
  6 
  7 import java.io.*;
  8 import java.security.*;
  9 import java.security.cert.CertificateException;
 10 import java.security.cert.X509Certificate;
 11 
 12 /**
 13  * fileName:IssueRootCert
 14  *
 15  * @author :zyz
 16  * Date    :2020/1/15 11:29
 17  * -------------------------
 18  * 功能和描述:頒發根證書
 19  **/
 20 public class IssueRootCert {
 21     public static final Logger logger = LoggerFactory.getLogger(IssueRootCert.class);
 22     private static SecureRandom secureRandom;
 23 
 24     static {
 25         //定義隨機數來源
 26         try {
 27             secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");
 28         } catch (NoSuchAlgorithmException e) {
 29             logger.error("算法不存在");
 30         } catch (NoSuchProviderException e) {
 31             logger.error("該隨機數提供者不存在");
 32         }
 33     }
 34 
 35     /**
 36      * 定義pfx根證書文件
 37      */
 38     public static final String ROOT_ISSUE_PFX_FILE = "D:\\signverify\\rootcert\\ROOTCA.pfx";
 39 
 40     /**
 41      * 定義私鑰證書的密碼
 42      */
 43     public static final String ROOT_ISSUE_PFX_PASSWORD = "123456";
 44     /**
 45      * 定義crt根證書文件
 46      */
 47     public static final String ROOT_ISSUE_CRT_FILE = "D:\\signverify\\rootcert\\ROOTCA.cer";
 48 
 49     /**
 50      * 定義根證書的別名
 51      */
 52     public static final String ROOT_ISSUE_ALIAS = "rootca";
 53 
 54     public static void main(String[] args) {
 55         try {
 56             X500Name issue = new X500Name("CN=RootCA,OU=ISI,O=BenZeph,L=CD,ST=SC,C=CN");
 57             issueRootCert(issue);
 58         } catch (IOException e) {
 59             e.printStackTrace();
 60         }
 61 
 62     }
 63 
 64     /**
 65      * 簽名算法
 66      */
 67     public static final String ALGORITHM = "MD5WithRSA";
 68 
 69     public static void issueRootCert(X500Name x500Name) {
 70         try {
 71             CertAndKeyGen certAndKeyGen = new CertAndKeyGen("RSA", ALGORITHM, null);
 72             //設置生成密鑰時使用的隨機數的來源
 73             certAndKeyGen.setRandom(secureRandom);
 74 
 75             //設置密鑰長度,太短容易被攻擊破解
 76             certAndKeyGen.generate(1024);
 77 
 78             //時間間隔設置為10年(設置證書有效期的時候需要使用到)
 79             long interval = 60L * 60L * 24L * 3650;
 80             //
 81             X509Certificate x509Certificate = certAndKeyGen.getSelfCertificate(x500Name, interval);
 82 
 83             X509Certificate[] x509Certificates = new X509Certificate[]{x509Certificate};
 84 
 85             IssueCertUtils.createKeyStore(ROOT_ISSUE_ALIAS, certAndKeyGen.getPrivateKey(), ROOT_ISSUE_PFX_PASSWORD.toCharArray(), x509Certificates, ROOT_ISSUE_PFX_FILE);
 86             //根據私鑰導出公鑰
 87             OutputStream outputStream = new FileOutputStream(new File(ROOT_ISSUE_CRT_FILE));
 88             outputStream.write(x509Certificate.getEncoded());
 89             outputStream.close();
 90         } catch (NoSuchAlgorithmException e) {
 91             e.printStackTrace();
 92         } catch (NoSuchProviderException e) {
 93             e.printStackTrace();
 94         } catch (InvalidKeyException e) {
 95             e.printStackTrace();
 96         } catch (CertificateException e) {
 97             e.printStackTrace();
 98         } catch (SignatureException e) {
 99             e.printStackTrace();
100         } catch (FileNotFoundException e) {
101             e.printStackTrace();
102         } catch (IOException e) {
103             e.printStackTrace();
104         }
105     }
106 }

 

2.2、創建服務端證書

  1 ature.util.IssueCertUtils;
  2   2 import sun.security.tools.keytool.CertAndKeyGen;
  3   3 import sun.security.x509.*;
  4   4 
  5   5 import java.io.*;
  6   6 import java.security.*;
  7   7 import java.security.cert.CertificateException;
  8   8 import java.security.cert.CertificateFactory;
  9   9 import java.security.cert.X509Certificate;
 10  10 import java.util.Date;
 11  11 import java.util.Random;
 12  12 
 13  13 /**
 14  14  * fileName:IssueCert
 15  15  *
 16  16  * @author :zyz
 17  17  * Date    :2020/1/15 12:46
 18  18  * -------------------------
 19  19  * 功能和描述:頒發證書
 20  20  **/
 21  21 public class IssueCert {
 22  22     private static SecureRandom secureRandom;
 23  23 
 24  24     static {
 25  25         try {
 26  26             secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");
 27  27         } catch (NoSuchAlgorithmException e) {
 28  28             e.printStackTrace();
 29  29         } catch (NoSuchProviderException e) {
 30  30             e.printStackTrace();
 31  31         }
 32  32     }
 33  33 
 34  34     /**
 35  35      * 私鑰證書-用於簽名
 36  36      */
 37  37     public static final String ISSUE_PFX_FILE = "D:\\signverify\\mycert\\ISSUE.pfx";
 38  38     /**
 39  39      * 公鑰證書-用於驗簽
 40  40      */
 41  41     public static final String ISSUE_CRT_FILE = "D:\\signverify\\mycert\\ISSUE.cer";
 42  42 
 43  43     /**
 44  44      * 定義pfx根證書文件
 45  45      */
 46  46     public static final String ROOT_ISSUE_PFX_FILE = "D:\\signverify\\rootcert\\ROOTCA.pfx";
 47  47 
 48  48     /**
 49  49      * 定義私鑰證書的密碼
 50  50      */
 51  51     public static final String ROOT_ISSUE_PFX_PASSWORD = "123456";
 52  52     /**
 53  53      * 定義crt根證書文件
 54  54      */
 55  55     public static final String ROOT_ISSUE_CRT_FILE = "D:\\signverify\\rootcert\\ROOTCA.cer";
 56  56 
 57  57     /**
 58  58      * 定義根證書的別名
 59  59      */
 60  60     public static final String ROOT_ISSUE_ALIAS = "rootca";
 61  61 
 62  62     /**
 63  63      * 證書別名
 64  64      */
 65  65     public static final String ISSUE_ALIAS = "subject";
 66  66 
 67  67     /**
 68  68      * 私鑰證書密碼
 69  69      */
 70  70     public static final String ISSUE_PASSWORD = "123456";
 71  71 
 72  72 
 73  73     /**
 74  74      * 簽名算法
 75  75      */
 76  76     public static final String SIG_ALG = "MD5WithRSA";
 77  77 
 78  78     public static void main(String[] args) {
 79  79         try {
 80  80 
 81  81             X500Name issue = new X500Name("CN=RootCA,OU=ISI,O=BenZeph,L=CD,ST=SC,C=CN");
 82  82             X500Name subject = new X500Name(
 83  83                     "CN=subject,OU=ISI,O=BenZeph,L=CD,ST=SC,C=CN");
 84  84             createIssueCert(issue, subject);
 85  85         } catch (IOException e) {
 86  86             e.printStackTrace();
 87  87         }
 88  88     }
 89  89 
 90  90     /**
 91  91      *
 92  92      */
 93  93     public static void createIssueCert(X500Name rootX500name, X500Name subjectX500Name) {
 94  94         try {
 95  95             CertAndKeyGen certAndKeyGen = new CertAndKeyGen("RSA", SIG_ALG, null);
 96  96 
 97  97             //生成密鑰時候使用的隨機數的來源
 98  98             certAndKeyGen.setRandom(secureRandom);
 99  99 
100 100             //設置密鑰的大小
101 101             certAndKeyGen.generate(1024);
102 102 
103 103 
104 104             //設置時間,設置證書有效期的時候需要使用到
105 105             long validity = 60L * 60L * 24L * 3650;
106 106             Date startDate = new Date();
107 107             Date endDate = new Date();
108 108             endDate.setTime(startDate.getTime() + validity * 1000);
109 109             //設置證書有效期
110 110             CertificateValidity interval = new CertificateValidity(startDate, endDate);
111 111 
112 112             //獲取X509CertInfo對象,並為其添加所有的強制屬性
113 113             X509CertInfo info = new X509CertInfo();
114 114 
115 115             info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
116 116             info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new Random().nextInt() & 0x7fffffff));
117 117 
118 118             AlgorithmId algID = AlgorithmId.get(SIG_ALG);
119 119             info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algID));
120 120 
121 121             info.set(X509CertInfo.SUBJECT, subjectX500Name);
122 122 
123 123             info.set(X509CertInfo.KEY, new CertificateX509Key(certAndKeyGen.getPublicKey()));
124 124 
125 125             info.set(X509CertInfo.VALIDITY, interval);
126 126 
127 127             info.set(X509CertInfo.ISSUER, rootX500name);
128 128 
129 129             PrivateKey privateKey = getPrivateKey();
130 130 
131 131             X509CertImpl cert = new X509CertImpl(info);
132 132             cert.sign(privateKey, SIG_ALG);
133 133 
134 134             //X509Certificate certificate = (X509Certificate) cert;
135 135 
136 136             X509Certificate x509Certificate = readX509Certificate();
137 137 
138 138             X509Certificate[] x509Certificates = new X509Certificate[]{cert, x509Certificate};
139 139 
140 140             IssueCertUtils.createKeyStore(ISSUE_ALIAS, certAndKeyGen.getPrivateKey(), ISSUE_PASSWORD.toCharArray(), x509Certificates, ISSUE_PFX_FILE);
141 141 
142 142             OutputStream outputStream = new FileOutputStream(new File(ISSUE_CRT_FILE));
143 143             outputStream.write(cert.getEncoded());
144 144             outputStream.close();
145 145 
146 146         } catch (NoSuchAlgorithmException e) {
147 147             e.printStackTrace();
148 148         } catch (NoSuchProviderException e) {
149 149             e.printStackTrace();
150 150         } catch (InvalidKeyException e) {
151 151             e.printStackTrace();
152 152         } catch (CertificateException e) {
153 153             e.printStackTrace();
154 154         } catch (IOException e) {
155 155             e.printStackTrace();
156 156         } catch (SignatureException e) {
157 157             e.printStackTrace();
158 158         }
159 159     }
160 160 
161 161 
162 162     /**
163 163      * 獲取私鑰
164 164      *
165 165      * @return
166 166      */
167 167     private static PrivateKey getPrivateKey() {
168 168         try {
169 169             //后去指定類型的KeyStore對象
170 170             KeyStore keyStore = KeyStore.getInstance("PKCS12");
171 171             InputStream in = null;
172 172             in = new FileInputStream(ROOT_ISSUE_PFX_FILE);
173 173             keyStore.load(in, ROOT_ISSUE_PFX_PASSWORD.toCharArray());
174 174             in.close();
175 175             //使用指定的密碼來獲取指定的別名對應的私鑰
176 176             Key key = keyStore.getKey(ROOT_ISSUE_ALIAS, ROOT_ISSUE_PFX_PASSWORD.toCharArray());
177 177             return (PrivateKey) key;
178 178         } catch (KeyStoreException e) {
179 179             e.printStackTrace();
180 180         } catch (FileNotFoundException e) {
181 181             e.printStackTrace();
182 182         } catch (CertificateException e) {
183 183             e.printStackTrace();
184 184         } catch (NoSuchAlgorithmException e) {
185 185             e.printStackTrace();
186 186         } catch (IOException e) {
187 187             e.printStackTrace();
188 188         } catch (UnrecoverableKeyException e) {
189 189             e.printStackTrace();
190 190         }
191 191         return null;
192 192     }
193 193 
194 194     /**
195 195      * 讀取crt根證書信息
196 196      *
197 197      * @return
198 198      */
199 199     private static X509Certificate readX509Certificate() {
200 200         InputStream inputStream = null;
201 201         try {
202 202             inputStream = new FileInputStream(ROOT_ISSUE_CRT_FILE);
203 203             CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
204 204             X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
205 205 
206 206             inputStream.close();
207 207             return certificate;
208 208         } catch (FileNotFoundException e) {
209 209             e.printStackTrace();
210 210         } catch (CertificateException e) {
211 211             e.printStackTrace();
212 212         } catch (IOException e) {
213 213             e.printStackTrace();
214 214         }
215 215         return null;
216 216     }
217 217 }
218  

 

2.3、創建客戶端證書

和創建客戶端方式一模一樣

2.4、工具類

 1 import java.io.FileOutputStream;
 2 import java.io.IOException;
 3 import java.io.OutputStream;
 4 import java.security.Key;
 5 import java.security.KeyStore;
 6 import java.security.KeyStoreException;
 7 import java.security.NoSuchAlgorithmException;
 8 import java.security.cert.Certificate;
 9 import java.security.cert.CertificateException;
10 
11 /**
12  * fileName:IssueCertUtils
13  *
14  * @author :zyz
15  * Date    :2020/1/15 14:30
16  * -------------------------
17  * 功能和描述:
18  **/
19 public class IssueCertUtils {
20 
21     private IssueCertUtils() {
22     }
23 
24     /**
25      * 證書私鑰的存儲設施
26      *
27      * @param alias        別名(會與對應的privateKey關聯)
28      * @param privateKey   密鑰-私鑰
29      * @param password     密碼(用來保護私鑰的密碼)
30      * @param certificates 證書鏈
31      * @param pfxFile      pfx文件
32      */
33     public static void createKeyStore(String alias, Key privateKey, char[] password, Certificate[] certificates, String pfxFile) {
34         try {
35             //獲取指定類型的KeyStore對象
36             KeyStore keyStore = KeyStore.getInstance("PKCS12");
37             //加載KeyStore
38             keyStore.load(null, password);
39 
40             //將給定的密鑰分配給指定的別名,並用給定的密碼來保護它
41             keyStore.setKeyEntry(alias, privateKey, password, certificates);
42 
43             //以下幾步就是想私鑰證書導出
44             OutputStream outputStream = new FileOutputStream(pfxFile);
45             keyStore.store(outputStream, password);
46             outputStream.close();
47         } catch (KeyStoreException e) {
48             e.printStackTrace();
49         } catch (CertificateException e) {
50             e.printStackTrace();
51         } catch (NoSuchAlgorithmException e) {
52             e.printStackTrace();
53         } catch (IOException e) {
54             e.printStackTrace();
55         }
56     }
57 }

 

2.5、測試簽名驗簽

 
         
  1 import sun.misc.BASE64Decoder;
  2 import sun.misc.BASE64Encoder;
  3 
  4 import java.io.FileInputStream;
  5 import java.io.IOException;
  6 import java.io.InputStream;
  7 import java.security.KeyStore;
  8 import java.security.PrivateKey;
  9 import java.security.PublicKey;
 10 import java.security.Signature;
 11 import java.security.cert.Certificate;
 12 import java.security.cert.CertificateFactory;
 13 
 14 /**
 15  * fileName:SignVerifyDemo
 16  *
 17  * @author :Miles zhu
 18  * Date    :2020/1/15 14:35
 19  * -------------------------
 20  * 功能和描述:
 21  **/
 22 public class SignVerifyDemo {
 23     public static final String PRIVATE_KEY_PASSWORD = "123456";
 24 
 25     public static final String PUBLIC_KEY_FILE_PATH = "D:\\signverify\\mycert\\ISSUE.cer";
 26     //public static final String PUBLIC_KEY_FILE_PATH = "D:\\signature\\mykey.cer";
 27     public static final String PRIVATE_KEY_FILE_PATH = "D:\\signverify\\mycert\\ISSUE.pfx";
 28     //public static final String PRIVATE_KEY_FILE_PATH = "D:\\signature\\mykey.pfx";
 29     public static final String ALIAS_NAME = "subject";
 30     public static final String DEFAULT_UTF8 = "UTF-8";
 31 
 32     public static void main(String[] args) {
 33         String originalData = "Hello我是原始的數據World";
 34         String sign = sign(originalData);
 35         System.out.println(sign);
 36         boolean verify = verify(sign, originalData);
 37         if (verify) {
 38             System.out.println("驗簽通過");
 39         } else {
 40             System.out.println("驗簽失敗");
 41         }
 42 
 43     }
 44 
 45     /**
 46      * 簽名
 47      */
 48     public static String sign(String originalData) {
 49         String base64Sign = "";
 50         try {
 51 
 52             //返回與此給定的別名的密碼,並用給定的密鑰來恢復它
 53             PrivateKey privateKey = getPrivateKey();
 54 
 55             //返回指定簽名的Signature對象
 56             Signature sign = Signature.getInstance("SHA1withRSA");
 57 
 58             //初始化這個用於簽名的對象
 59             sign.initSign(privateKey);
 60 
 61             byte[] bysData = originalData.getBytes(DEFAULT_UTF8);
 62 
 63             //使用指定的byte數組更新要簽名的數據
 64             sign.update(bysData);
 65             //返回所有已經更新數據的簽名字節
 66             byte[] signByte = sign.sign();
 67             //對其進行Base64編碼
 68             BASE64Encoder encoder = new BASE64Encoder();
 69             base64Sign = encoder.encode(signByte);
 70         } catch (Exception e) {
 71             System.out.println("簽名異常");
 72             e.printStackTrace();
 73         }
 74         return base64Sign;
 75     }
 76 
 77 
 78     /**
 79      * 驗簽
 80      *
 81      * @param signStr      簽名數據
 82      * @param originalData 原始數據
 83      * @return
 84      */
 85     public static boolean verify(String signStr, String originalData) {
 86         System.out.println("開始進行驗簽,原始數據為:" + originalData);
 87         try {
 88             //從此證書對象中獲取公鑰
 89             PublicKey publicKey = getPublicKey();
 90 
 91             //將簽名數據
 92             BASE64Decoder decoder = new BASE64Decoder();
 93             byte[] signed = decoder.decodeBuffer(signStr);
 94 
 95             //通過Signature的getInstance方法,獲取指定簽名算法的Signature對象
 96             Signature signature = Signature.getInstance("SHA1withRSA");
 97             //初始化用於驗證的對象
 98             signature.initVerify(publicKey);
 99             //使用指定的byte[]更新要驗證的數據
100             signature.update(originalData.getBytes(DEFAULT_UTF8));
101             //驗證傳入的簽名
102             return signature.verify(signed);
103         } catch (Exception e) {
104             return false;
105         }
106 
107     }
108 
109     /**
110      * 獲取公鑰
111      *
112      * @return
113      */
114     private static PublicKey getPublicKey() {
115         InputStream in = null;
116         try {
117             in = new FileInputStream(PUBLIC_KEY_FILE_PATH);
118             //獲取實現指定證書類型的CertificateFactory對象
119             CertificateFactory cf = CertificateFactory.getInstance("x509");
120             //生成一個證書對象,並從執行的輸入流中讀取數據對它進行初始化
121             Certificate certificate = cf.generateCertificate(in);
122             //從此證書中獲取公鑰
123             return certificate.getPublicKey();
124         } catch (Exception e) {
125             e.printStackTrace();
126             return null;
127         } finally {
128             if (null != in) {
129                 try {
130                     in.close();
131                 } catch (IOException e) {
132                     e.printStackTrace();
133                 }
134             }
135         }
136     }
137 
138 
139     /**
140      * 獲取私鑰
141      *
142      * @return
143      */
144     private static PrivateKey getPrivateKey() {
145         InputStream in = null;
146         try {
147             in = new FileInputStream(PRIVATE_KEY_FILE_PATH);
148             //返回指定類型的KeyStore對象
149             KeyStore keyStore = KeyStore.getInstance("PKCS12");
150 
151             char[] pscs = PRIVATE_KEY_PASSWORD.toCharArray();
152             //從給定的輸入流中加載此keyStore
153             keyStore.load(in, pscs);
154             //返回與給定別名關聯的密鑰,並用給定的密碼來恢復它
155             return (PrivateKey) keyStore.getKey(ALIAS_NAME, pscs);
156         } catch (Exception e) {
157             e.printStackTrace();
158             return null;
159         } finally {
160             if (null != in) {
161                 try {
162                     in.close();
163                 } catch (IOException e) {
164                     e.printStackTrace();
165                 }
166             }
167         }
168     }
169 }
 
         

 

 
        

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM