|
| 1 | +package com.yale.test.ps; |
| 2 | + |
| 3 | +import java.io.IOException; |
| 4 | +import java.io.InputStream; |
| 5 | +import java.io.UnsupportedEncodingException; |
| 6 | +import java.math.BigInteger; |
| 7 | +import java.security.InvalidKeyException; |
| 8 | +import java.security.KeyStore; |
| 9 | +import java.security.KeyStoreException; |
| 10 | +import java.security.NoSuchAlgorithmException; |
| 11 | +import java.security.PrivateKey; |
| 12 | +import java.security.Signature; |
| 13 | +import java.security.SignatureException; |
| 14 | +import java.security.UnrecoverableKeyException; |
| 15 | +import java.security.cert.CertificateException; |
| 16 | +import java.security.cert.X509Certificate; |
| 17 | + |
| 18 | +import javax.crypto.BadPaddingException; |
| 19 | +import javax.crypto.Cipher; |
| 20 | +import javax.crypto.IllegalBlockSizeException; |
| 21 | +import javax.crypto.NoSuchPaddingException; |
| 22 | + |
| 23 | + |
| 24 | +/* |
| 25 | + * 数字证书 |
| 26 | + * 我们知道,摘要算法用来确保数据没有被篡改,非对称加密算法可以对数据进行加解密,签名算法可以确保数据完整性和抗否认性,把这些算法集合到一起,并搞一套完善的标准,这就是数字证书。 |
| 27 | + * 因此,数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。 |
| 28 | + * 数字证书可以防止中间人攻击,因为它采用链式签名认证,即通过根证书(Root CA)去签名下一级证书,这样层层签名,直到最终的用户证书。而Root CA证书内置于操作系统中,所以,任何经过CA认证的数字证书都可以对其本身进行校验,确保证书本身不是伪造的。 |
| 29 | + * 我们在上网时常用的HTTPS协议就是数字证书的应用。浏览器会自动验证证书的有效性: |
| 30 | + * 要使用数字证书,首先需要创建证书。正常情况下,一个合法的数字证书需要经过CA签名,这需要认证域名并支付一定的费用。开发的时候,我们可以使用自签名的证书, |
| 31 | + * 这种证书可以正常开发调试,但不能对外作为服务使用,因为其他客户端并不认可未经CA签名的证书。 |
| 32 | + * 在Java程序中,数字证书存储在一种Java专用的key store文件中,JDK提供了一系列命令来创建和管理key store。我们用下面的命令创建一个key store,并设定口令123456: |
| 33 | + * keytool -storepass 123456 -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 3650 -alias mycert -keystore my.keystore -dname "CN=www.sample.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN" |
| 34 | + * 几个主要的参数是: |
| 35 | + keyalg:指定RSA加密算法; |
| 36 | + sigalg:指定SHA1withRSA签名算法; |
| 37 | + validity:指定证书有效期3650天; |
| 38 | + alias:指定证书在程序中引用的名称; |
| 39 | + dname:最重要的CN=www.sample.com指定了Common Name,如果证书用在HTTPS中,这个名称必须与域名完全一致。 |
| 40 | + * 执行上述命令,JDK会在当前目录创建一个my.keystore文件,并存储创建成功的一个私钥和一个证书,它的别名是mycert。 |
| 41 | + * 有了key store存储的证书,我们就可以通过数字证书进行加解密和签名: |
| 42 | + * 在上述代码中,我们从key store直接读取了私钥-公钥对,私钥以PrivateKey实例表示,公钥以X509Certificate表示,实际上数字证书只包含公钥,因此,读取证书并不需要口令,只有读取私钥才需要。如果部署到Web服务器上,例如Nginx,需要把私钥导出为Private Key格式,把证书导出为X509Certificate格式。 |
| 43 | + * 以HTTPS协议为例,浏览器和服务器建立安全连接的步骤如下: |
| 44 | + * 1.浏览器向服务器发起请求,服务器向浏览器发送自己的数字证书; |
| 45 | + * 2.浏览器用操作系统内置的Root CA来验证服务器的证书是否有效,如果有效,就使用该证书加密一个随机的AES口令并发送给服务器; |
| 46 | + * 3.服务器用自己的私钥解密获得AES口令,并在后续通讯中使用AES加密。 |
| 47 | + * 上述流程只是一种最常见的单向验证。如果服务器还要验证客户端,那么客户端也需要把自己的证书发送给服务器验证,这种场景常见于网银等。 |
| 48 | + * 注意:数字证书存储的是公钥,以及相关的证书链和算法信息。私钥必须严格保密,如果数字证书对应的私钥泄漏,就会造成严重的安全威胁。如果CA证书的私钥泄漏,那么该CA证书签发的所有证书将不可信。 |
| 49 | + * 数字证书服务商DigiNotar(https://en.wikipedia.org/wiki/DigiNotar)就发生过私钥泄漏导致公司破产的事故。 |
| 50 | + */ |
| 51 | +public class CASignDemo { |
| 52 | + public static void main(String[] args) |
| 53 | + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, SignatureException { |
| 54 | + byte[] message = "Hello, use X:509 cert!".getBytes("UTF-8"); |
| 55 | + //读取KeyStore |
| 56 | + KeyStore ks = loadKeyStore("/my.keystore", "123456"); |
| 57 | + |
| 58 | + //读取私钥: |
| 59 | + PrivateKey privateKey = (PrivateKey)ks.getKey("mycert", "123456".toCharArray()); |
| 60 | + |
| 61 | + //读取证书: |
| 62 | + X509Certificate certificate = (X509Certificate)ks.getCertificate("mycert"); |
| 63 | + |
| 64 | + //加密: |
| 65 | + byte[] encrypted = encrypt(certificate, message); |
| 66 | + System.out.println(String.format("加密后的字符串,encrypted:%x", new BigInteger(1, encrypted))); |
| 67 | + |
| 68 | + //解密 |
| 69 | + byte[] decrypted = decrypt(privateKey, encrypted); |
| 70 | + System.out.println("解密,decrypted:" + new String(decrypted, "UTF-8")); |
| 71 | + |
| 72 | + //签名: |
| 73 | + byte[] sign = sign(privateKey, certificate, message); |
| 74 | + System.out.println(String.format("signature:%x", new BigInteger(1, sign))); |
| 75 | + |
| 76 | + //验证签名 |
| 77 | + boolean verified = verify(certificate, message, sign); |
| 78 | + System.out.println("验证签名是否通过:" + verified); |
| 79 | + } |
| 80 | + |
| 81 | + static KeyStore loadKeyStore(String keyStoreFile, String password) |
| 82 | + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { |
| 83 | + try(InputStream input = CASignDemo.class.getResourceAsStream(keyStoreFile)) { |
| 84 | + if (input == null) { |
| 85 | + throw new RuntimeException("file not found in classpath:" + keyStoreFile); |
| 86 | + } |
| 87 | + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 88 | + ks.load(input, password.toCharArray()); |
| 89 | + return ks; |
| 90 | + } catch (KeyStoreException e) { |
| 91 | + e.printStackTrace(); |
| 92 | + throw e; |
| 93 | + } catch (NoSuchAlgorithmException e) { |
| 94 | + e.printStackTrace(); |
| 95 | + throw e; |
| 96 | + } catch (CertificateException e) { |
| 97 | + e.printStackTrace(); |
| 98 | + throw e; |
| 99 | + } catch (IOException e) { |
| 100 | + e.printStackTrace(); |
| 101 | + throw e; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + static byte[] encrypt(X509Certificate certificate, byte[] message) |
| 106 | + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { |
| 107 | + Cipher cipher = Cipher.getInstance(certificate.getPublicKey().getAlgorithm()); |
| 108 | + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); |
| 109 | + return cipher.doFinal(message); |
| 110 | + } |
| 111 | + |
| 112 | + static byte[] decrypt(PrivateKey privateKey, byte[] data) |
| 113 | + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { |
| 114 | + Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); |
| 115 | + cipher.init(Cipher.DECRYPT_MODE, privateKey); |
| 116 | + return cipher.doFinal(data); |
| 117 | + } |
| 118 | + |
| 119 | + static byte[] sign(PrivateKey privateKey, X509Certificate certificate, byte[] message) |
| 120 | + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { |
| 121 | + Signature signature = Signature.getInstance(certificate.getSigAlgName()); |
| 122 | + signature.initSign(privateKey); |
| 123 | + signature.update(message); |
| 124 | + return signature.sign(); |
| 125 | + } |
| 126 | + |
| 127 | + static boolean verify(X509Certificate certificate, byte[] message, byte[] sig) |
| 128 | + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { |
| 129 | + Signature signature = Signature.getInstance(certificate.getSigAlgName()); |
| 130 | + signature.initVerify(certificate); |
| 131 | + signature.update(message); |
| 132 | + return signature.verify(sig); |
| 133 | + } |
| 134 | +} |
0 commit comments