第一部分:什么是签名验签?
私钥:可以解密公钥加密的数据
公钥:可以解密私钥加密的数据
也就是说公钥和私钥之间可以互相加解密
公钥加密私钥解密称之为——加解密
私钥加密公钥解密称之为——签名验签
签名:使用私钥对数据进行加密,该操作称之为——签名
验签:使用与私钥对应的公钥进行解密,该操作称之为——验签
到此知道什么是公钥什么是私钥,以及区别和可以用来干嘛的了。那么下面开始进入正题(如果公钥私钥和签名验签的概念还有不明白的朋友请自行百度,这篇博客的重点是关于如何通过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 }