1. 程式人生 > >SSL/TLS 單雙向認證程式碼示例

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);