SSL/TLS 單雙向認證程式碼示例
採用SSL/TLS形式的安全連結,能防止資料傳輸過程中被截獲修改等問題。
SSL演算法簡短說明:
對稱加密演算法:DES 3DES AES 客戶端和服務端使用同一個金鑰對訊息進行加密解密。
非對稱加密演算法:RSA, 生成公鑰和私鑰,採用公鑰加密,私鑰解密。客戶端和伺服器端各自持有自己的私鑰證書,相互信任對方的證書(證書都匯入到了對方的私鑰倉庫)
速度上,對稱加密演算法比非對稱加密演算法要快。下面使用jdk工具keytool生成服務端和客戶端的金鑰和證書,採用的是RSA的非對稱加密形式。
生成伺服器端證書倉庫
keytool -genkey -alias serverks -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass serverpwd -storepass serverpwd -keystore D:/serverks.jks
生成伺服器端證書
keytool -export -alias servercert -keystore D:/serverks.jks -storepass serverpwd -file D:/servercert.cer
生成客戶端證書倉庫
keytool -genkey -alias clientks -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass clientpwd -storepass clientpwd -keystore D:/clientks.jks
生成客戶端證書
keytool -export -alias clientcert -keystore D:/clientks.jks -storepass clientpwd -file D:/clientcert.cer
上面四個命令就可以生成客戶端和伺服器端的證書和倉庫了。
對於單項認證,客戶端驗證伺服器端,伺服器端不做驗證情況,只需要將伺服器端證書匯入到客戶端證書倉庫。
對於雙向認證,除了上面的匯入操作外,還需要將客戶端證書匯入到伺服器端證書倉庫。
命令如下:
伺服器端證書匯入客戶端證書倉庫
keytool -import -trustcacerts -alias servercert2clientks -file D:/servercert.cer -storepass clientpwd -keystore D:/clientks.jks
客戶端證書匯入到伺服器端證書倉庫
keytool -import -trustcacerts -alias clientcert2serverks -file D:/clientcert.cer -storepass serverks -keystore D:/serverks.jks
上述命令解釋:
keytool //java jdk自帶工具
-genkey //生成key的命令引數
-alias //生成key別名
-keysize // key大小,2048
-validity //有效期,單位天
-keyalg //key的演算法,本文采用RSA
-dname //key的資訊串,CN=這裡是域名,此外還有OU / O / L / S / C=CH 代表擁有者/歸屬/地區/城市/國家
-keypass // 生成key的密碼,
-storepass //儲存的密碼
-keystore //生成key輸出的路徑和檔名
-export //匯出證書
-import //匯入證書
-file //匯出的檔案
下面是Java中SSLContext的生成過程:
單向SSL認證服務端程式碼(客戶端對服務端證書進行驗證,伺服器端接受所有證書)
//獲取X509演算法的key管理工廠類 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); //獲取keystore型別例項,jks型別 KeyStore ks = KeyStore.getInstance("JKS"); //讀取伺服器端的證書庫 InputStream in = ClassLoader.getSystemResourceAsStream("serverks.jks"); //載入到keystore,第二個引數密碼 ks.load(in, "serverpwd".toCharArray()); in.close(); //初始化keymanager通過keystore和密碼 kmf.init(ks, "serverpwd".toCharArray()); //獲取sslcontext,SSL/TLSv1.x SSLContext serverContext = SSLContext.getInstance("TLSv1.2"); //單項認證,客戶端認證伺服器,trustmanager為空,就是伺服器端信任所有客戶端證書 serverContext.init(kmf.getKeyManagers(), null, null);
雙向認證相比上面,只是讓sslcontext的第二個信任證書引數傳遞不為空即可,代表伺服器端新人的證書物件。//獲取X509演算法的key管理工廠類 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); //獲取keystore型別例項,jks型別 KeyStore ks = KeyStore.getInstance("JKS"); //讀取伺服器端的證書庫 InputStream in = ClassLoader.getSystemResourceAsStream("serverks.jks"); //載入到keystore,第二個引數密碼 ks.load(in, "serverpwd".toCharArray()); in.close(); //初始化keymanager通過keystore和密碼 kmf.init(ks, "serverpwd".toCharArray()); //獲取sslcontext,SSL/TLSv1.x SSLContext serverContext = SSLContext.getInstance("TLSv1.2"); //獲取x509信任證書工廠,並根據keystore初始化, 伺服器端驗證客戶端證書是否有效 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ks); //雙向認證,此處多trustManagers引數 serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
對於TrustManager可以自己實現,但是一般很少自己實現,實現方式如下://獲取X509演算法的key管理工廠類 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); //獲取keystore型別例項,jks型別 KeyStore ks = KeyStore.getInstance("JKS"); //讀取伺服器端的證書庫 InputStream in = ClassLoader.getSystemResourceAsStream("serverks.jks"); //載入到keystore,第二個引數密碼 ks.load(in, "serverpwd".toCharArray()); in.close(); //初始化keymanager通過keystore和密碼 kmf.init(ks, "serverpwd".toCharArray()); //獲取sslcontext,SSL/TLSv1.x SSLContext serverContext = SSLContext.getInstance("TLSv1.2"); //自定義實現證書驗證 X509TrustManager x509 = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } /** * 伺服器端檢查 */ public void checkServerTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { if (chain == null || chain.length == 0) { throw new IllegalArgumentException("X509Certificate chain cannot be null"); } if (authType == null || authType.equals("")) { throw new IllegalArgumentException("X509Certificate authType cannot be null"); } boolean isSucc = false; Principal p = null; for (X509Certificate x : chain) { p = x.getSubjectDN(); //Principal getName 為keytool 中的-dname引數對應的值 if (p != null && p.getName().contains("localhost")) {//這裡只驗證了-dName那個引數是否能識別 isSucc = true; return ; } } if (!isSucc) { throw new java.security.cert.CertificateException("no authorizication"); } } /** * 客戶端檢查 */ public void checkClientTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { if (chain == null || chain.length == 0) { throw new IllegalArgumentException("X509Certificate chain cannot be null"); } if (authType == null || authType.equals("")) { throw new IllegalArgumentException("X509Certificate authType cannot be null"); } boolean isSucc = false; Principal p = null; for (X509Certificate x : chain) { p = x.getSubjectDN(); if (p != null && p.getName().contains("localhost")) { isSucc = true; return ; } } if (!isSucc) { throw new java.security.cert.CertificateException("no authorizication"); } } }; //雙向認證,自定義x509驗證實現 serverContext.init(kmf.getKeyManagers(), new TrustManager[]{x509}, null);