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 }
