1.数字证书简介
数字证书具备常规加密解密必要的信息,包含签名算法,可用于网络数据加密解密交互,标识网络用户(计算机)身份。数字证书为发布公钥提供了一种简便的途径,其数字证书则成为加密算法以及公钥的载体。依靠数字证书,我们可以构建一个简单的加密网络应用平台。
数字证书类似于个人身份证,由数字证书颁发认证机构(Certificate Authority, CA)签发。只有经过CA签发的证书在网络中才具备可认证性。CA颁发给自己的证书叫根证书。
VeriSign, GeoTrust和Thawte是国际权威数字证书颁发认证机构的三巨头。其中应用最广泛的是VeriSign签发的电子商务用数字证书。
最为常用的非对称加密算法是RSA,与之配套的签名算法是SHA1withRSA,最常用的消息摘要算法是SHA1.
除了RSA,还可以使用DSA算法。只是使用DSA算法无法完成加密解密实现,即这样的证书不包括加密解密功能。
数字证书有多种文件编码格式,主要包含CER编码,DER编码等。
CER(Canonical Encoding Rules, 规范编码格式),DER(Distinguished Encoding Rules 卓越编码格式),两者的区别是前者是变长模式,后者是定长模式。
所有证书都符合公钥基础设施(PKI, Public Key Infrastructure)制定的ITU-T X509国际标准(X.509标准)。
2.模型分析
在实际应用中,很多数字证书都属于自签名证书,即证书申请者为自己的证书签名。这类证书通常应用于软件厂商内部发放的产品中,或约定使用该证书的数据交互双方。数字证书完全充当加密算法的载体,为必要数据做加密解密和签名验签等操作。在我司的开发过程中,数字证书更多是用来做加密和解密。
1)证书签发
2)加密交互,图略。
当客户端获取到服务器下发的数字证书后,就可以进行加密交互了。具体做法是:
客户端使用公钥,加密后发送给服务端,服务端用私钥进行解密验证。
服务端使用私钥进行加密和数字签名。
3. KeyTool 管理证书
KeyTool与本地密钥库相关联,将私钥存于密钥库,公钥则以数字证书输出。KeyTool位于JDK目录下的bin目录中,需要通过命令行进行相应的操作。
1)构建自签名证书
申请数字证书之前,需要在密钥库中以别名的方式生成本地数字证书,建立相应的加密算法,密钥,有效期等信息。
keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 3600 -alias myCertificate -keystore myKeystore.keystore
各参数含义如下:
-genkeypair 表示生成密钥对
-keyalg 指定密钥算法,这里是RSA
-keysize 指定密钥长度,默认1024,这里指定2048
-sigal 指定签名算法,这里是SHA1withRSA
-validity 指定有效期,单位为天
-alias 指定别名
-keystore 指定密钥库存储位置
这里我输入参数Changeme123作为密钥库的密码,也可通过参数-storepass指定密码。可以用-dname "CN=xxx...."这样的形式,避免更多交互。
注意:一个keystore应该是可以存储多套<私钥-数字证书>的信息,通过别名来区分。通过实践,调用上述命令两次(别名不同),生成同一个keystore,用不同别名进行加密解密和签名验签,没有任何问题。
更多命令可参考:http://blog.chinaunix.net/uid-17102734-id-2830223.html
经过上述操作后,密钥库中已经创建了数字证书。虽然这时的数字证书并没有经过CA认证,但并不影响我们使用。我们仍可将证书导出,发送给合作伙伴进行加密交互。
keytool -exportcert -alias myCertificate -keystore myKeystore.keystore -file myCer.cer -rfc
各参数含义如下:
-exportcert 表示证书导出操作
-alias 指定别名
-keystore 指定密钥库文件
-file 指定导出证书的文件路径
-rfc 指定以Base64编码格式输出
打印证书
keytool -printcert -file myCer.cer
2)构建CA签发证书
如果要获取CA机构谁的数字证书,需要将数字证书签发申请(CSR)导出,经由CA机构认证并颁发,将认证后的证书导入本地密钥库和信息库。
keytool -certreq -alias myCertificate -keystore myKeystore.keystore -file myCsr.csr -v
各参数含义如下:
-certreq 表示数字证书申请操作
-alias 指定别名
-keystore 指定密钥库文件路径
-file 指定导出申请的路径
-v 详细信息
获得签发的数字证书后,需要将其导入信任库。
keytool -importcert -trustcacerts -alias myCertificate -file myCer.cer -keystore myKeystore.keystore
参数不作详细讲解,如果是原来的证书文件,那么会报错:
查看证书
keytool -list -alias myCertificate -keystore myKeystore.keystore
经过上述的所有操作后,可以得到下面几个文件
4. 证书使用
终于到了激动人心的时刻,可以用代码通过keystore进行加解密操作了!
Java 6提供了完善的数字证书管理实现,我们几乎无需关注,仅通过操作密钥库和数字证书就可完成相应的加密解密和签名验签过程。
密钥库管理私钥,数字证书管理公钥,公钥和私钥分属消息传递双方,进行加密消息传递。
考虑一个场景。
A机器某模块需要将数据导出到一个文件中,将文件发送到B机器,由B将数据导入。
在这个场景中,A就相当于服务端,需要将证书给B,同时用私钥加密数据,生成签名,导出到文件中。
B相当于客户端,用收到的数字证书进行解密和验签。
1 package jdbc.pro.lin; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.InputStream; 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.PublicKey; 13 import java.security.Signature; 14 import java.security.SignatureException; 15 import java.security.UnrecoverableKeyException; 16 import java.security.cert.Certificate; 17 import java.security.cert.CertificateException; 18 import java.security.cert.CertificateFactory; 19 import java.security.cert.X509Certificate; 20 21 import javax.crypto.BadPaddingException; 22 import javax.crypto.Cipher; 23 import javax.crypto.IllegalBlockSizeException; 24 import javax.crypto.NoSuchPaddingException; 25 26 public class MyCertifacate { 27 private static final String STORE_PASS = "Changeme123"; 28 private static final String ALIAS = "myCertificate"; 29 private static final String KEYSTORE_PATH = "D:\JavaDemo\Certifacate\myKeystore.keystore"; 30 private static final String CERT_PATH = "D:\JavaDemo\Certifacate\myCer.cer"; 31 private static final String PLAIN_TEXT = "MANUTD is the most greatest club in the world."; 32 /** JDK6只支持X.509标准的证书 */ 33 private static final String CERT_TYPE = "X.509"; 34 35 public static void main(String[] args) throws IOException { 36 /** 37 * 假设现在有这样一个场景 。A机器上的数据,需要加密导出,然后将导出文件放到B机器上导入。 在这个场景中,A相当于服务器,B相当于客户端 38 */ 39 40 /** A */ 41 KeyStore keyStore = getKeyStore(STORE_PASS, KEYSTORE_PATH); 42 PrivateKey privateKey = getPrivateKey(keyStore, ALIAS, STORE_PASS); 43 X509Certificate certificate = getCertificateByKeystore(keyStore, ALIAS); 44 45 /** 加密和签名 */ 46 byte[] encodedText = encode(PLAIN_TEXT.getBytes(), privateKey); 47 byte[] signature = sign(certificate, privateKey, PLAIN_TEXT.getBytes()); 48 49 /** 现在B收到了A的密文和签名,以及A的可信任证书 */ 50 X509Certificate receivedCertificate = getCertificateByCertPath( 51 CERT_PATH, CERT_TYPE); 52 PublicKey publicKey = getPublicKey(receivedCertificate); 53 byte[] decodedText = decode(encodedText, publicKey); 54 System.out.println("Decoded Text : " + new String(decodedText)); 55 System.out.println("Signature is : " 56 + verify(receivedCertificate, decodedText, signature)); 57 } 58 59 /** 60 * 加载密钥库,与Properties文件的加载类似,都是使用load方法 61 * 62 * @throws IOException 63 */ 64 public static KeyStore getKeyStore(String storepass, String keystorePath) 65 throws IOException { 66 InputStream inputStream = null; 67 try { 68 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 69 inputStream = new FileInputStream(keystorePath); 70 keyStore.load(inputStream, storepass.toCharArray()); 71 return keyStore; 72 } catch (KeyStoreException | NoSuchAlgorithmException 73 | CertificateException | IOException e) { 74 // TODO Auto-generated catch block 75 e.printStackTrace(); 76 } finally { 77 if (null != inputStream) { 78 inputStream.close(); 79 } 80 } 81 return null; 82 } 83 84 /** 85 * 获取私钥 86 * 87 * @param keyStore 88 * @param alias 89 * @param password 90 * @return 91 */ 92 public static PrivateKey getPrivateKey(KeyStore keyStore, String alias, 93 String password) { 94 try { 95 return (PrivateKey) keyStore.getKey(alias, password.toCharArray()); 96 } catch (UnrecoverableKeyException | KeyStoreException 97 | NoSuchAlgorithmException e) { 98 // TODO Auto-generated catch block 99 e.printStackTrace(); 100 } 101 return null; 102 } 103 104 /** 105 * 获取公钥 106 * 107 * @param certificate 108 * @return 109 */ 110 public static PublicKey getPublicKey(Certificate certificate) { 111 return certificate.getPublicKey(); 112 } 113 114 /** 115 * 通过密钥库获取数字证书,不需要密码,因为获取到Keystore实例 116 * 117 * @param keyStore 118 * @param alias 119 * @return 120 */ 121 public static X509Certificate getCertificateByKeystore(KeyStore keyStore, 122 String alias) { 123 try { 124 return (X509Certificate) keyStore.getCertificate(alias); 125 } catch (KeyStoreException e) { 126 // TODO Auto-generated catch block 127 e.printStackTrace(); 128 } 129 return null; 130 } 131 132 /** 133 * 通过证书路径生成证书,与加载密钥库差不多,都要用到流。 134 * 135 * @param path 136 * @param certType 137 * @return 138 * @throws IOException 139 */ 140 public static X509Certificate getCertificateByCertPath(String path, 141 String certType) throws IOException { 142 InputStream inputStream = null; 143 try { 144 // 实例化证书工厂 145 CertificateFactory factory = CertificateFactory 146 .getInstance(certType); 147 // 取得证书文件流 148 inputStream = new FileInputStream(path); 149 // 生成证书 150 Certificate certificate = factory.generateCertificate(inputStream); 151 152 return (X509Certificate) certificate; 153 } catch (CertificateException | IOException e) { 154 // TODO Auto-generated catch block 155 e.printStackTrace(); 156 } finally { 157 if (null != inputStream) { 158 inputStream.close(); 159 } 160 } 161 return null; 162 163 } 164 165 /** 166 * 从证书中获取加密算法,进行签名 167 * 168 * @param certificate 169 * @param privateKey 170 * @param plainText 171 * @return 172 */ 173 public static byte[] sign(X509Certificate certificate, 174 PrivateKey privateKey, byte[] plainText) { 175 /** 如果要从密钥库获取签名算法的名称,只能将其强制转换成X509标准,JDK 6只支持X.509类型的证书 */ 176 try { 177 Signature signature = Signature.getInstance(certificate 178 .getSigAlgName()); 179 signature.initSign(privateKey); 180 signature.update(plainText); 181 return signature.sign(); 182 } catch (NoSuchAlgorithmException | InvalidKeyException 183 | SignatureException e) { 184 // TODO Auto-generated catch block 185 e.printStackTrace(); 186 } 187 188 return null; 189 } 190 191 /** 192 * 验签,公钥包含在证书里面 193 * 194 * @param certificate 195 * @param decodedText 196 * @param receivedignature 197 * @return 198 */ 199 public static boolean verify(X509Certificate certificate, 200 byte[] decodedText, final byte[] receivedignature) { 201 try { 202 Signature signature = Signature.getInstance(certificate 203 .getSigAlgName()); 204 /** 注意这里用到的是证书,实际上用到的也是证书里面的公钥 */ 205 signature.initVerify(certificate); 206 signature.update(decodedText); 207 return signature.verify(receivedignature); 208 } catch (NoSuchAlgorithmException | InvalidKeyException 209 | SignatureException e) { 210 // TODO Auto-generated catch block 211 e.printStackTrace(); 212 } 213 return false; 214 } 215 216 /** 217 * 加密。注意密钥是可以获取到它适用的算法的。 218 * 219 * @param plainText 220 * @param privateKey 221 * @return 222 */ 223 public static byte[] encode(byte[] plainText, PrivateKey privateKey) { 224 try { 225 Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); 226 cipher.init(Cipher.ENCRYPT_MODE, privateKey); 227 return cipher.doFinal(plainText); 228 } catch (NoSuchAlgorithmException | NoSuchPaddingException 229 | InvalidKeyException | IllegalBlockSizeException 230 | BadPaddingException e) { 231 // TODO Auto-generated catch block 232 e.printStackTrace(); 233 } 234 235 return null; 236 237 } 238 239 /** 240 * 解密,注意密钥是可以获取它适用的算法的。 241 * 242 * @param encodedText 243 * @param publicKey 244 * @return 245 */ 246 public static byte[] decode(byte[] encodedText, PublicKey publicKey) { 247 try { 248 Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm()); 249 cipher.init(Cipher.DECRYPT_MODE, publicKey); 250 return cipher.doFinal(encodedText); 251 } catch (NoSuchAlgorithmException | NoSuchPaddingException 252 | InvalidKeyException | IllegalBlockSizeException 253 | BadPaddingException e) { 254 // TODO Auto-generated catch block 255 e.printStackTrace(); 256 } 257 258 return null; 259 } 260 }