第一部分:什么是簽名驗簽?
私鑰:可以解密公鑰加密的數據
公鑰:可以解密私鑰加密的數據
也就是說公鑰和私鑰之間可以互相加解密
公鑰加密私鑰解密稱之為——加解密
私鑰加密公鑰解密稱之為——簽名驗簽
簽名:使用私鑰對數據進行加密,該操作稱之為——簽名
驗簽:使用與私鑰對應的公鑰進行解密,該操作稱之為——驗簽
到此知道什么是公鑰什么是私鑰,以及區別和可以用來干嘛的了。那么下面開始進入正題(如果公鑰私鑰和簽名驗簽的概念還有不明白的朋友請自行百度,這篇博客的重點是關於如何通過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 }