本文通過類比情境,介紹SSL雙向認證的java實現
預設的情況下,我認為讀者已經對SSL原理有一定的瞭解,所以文章中對SSL的原理,不做詳細的介紹。
如果有這個需要,那麼通過GOOGLE,可以搜尋到很多這樣的文章。
類比情境:
Server端和Client端通訊,需要進行授權和身份的驗證,即Client只能接受Server的訊息,Server只能接受Client的訊息。
實現技術:
JSSE(Java Security Socket Extension)
是Sun為瞭解決在Internet上的安全通訊而推出的解決方案。它實現了SSL和TSL(傳輸層安全)協議。在JSSE中包含了資料加密,伺服器驗證,訊息完整性和用戶端驗證等技術。通過使用JSSE,開發人員可以在客戶機和伺服器之間通過TCP/IP協議安全地傳輸資料
為了實現訊息認證。
Server需要:
1)KeyStore: 其中儲存服務端的私密金鑰
2)Trust KeyStore:其中儲存用戶端的授權認證
同樣,Client需要:
1)KeyStore:其中儲存用戶端的私密金鑰
2)Trust KeyStore:其中儲存服務端的授權認證
我們可以使用Java內建的keytool命令,去產生這樣資訊檔
1)產生服務端私密金鑰,並且匯入到服務端KeyStore檔案中
keytool -genkey -alias serverkey -keystore kserver.keystore
過程中,分別需要填寫,根據需求自己設定就行
keystore密碼:123456
名字和姓氏:stone
組織單位名稱:eulic
組織名稱:eulic
城市或地區名稱:HZ
州或省份名稱:ZJ
國家代碼:CN
serverkey私密金鑰的密碼,不填寫和keystore的密碼一致:123456
就可以產生kserver.keystore檔案
server.keystore是給服務端用的,其中儲存著自己的私密金鑰
2)根據私密金鑰,匯出服務端認證
keytool -export -alias serverkey -keystore kserver.keystore -file server.crt
server.crt就是服務端的認證
3)將服務端認證,匯入到用戶端的Trust KeyStore中
keytool -import -alias serverkey -file server.crt -keystore tclient.keystore
tclient.keystore是給用戶端用的,其中儲存著受信任的認證
採用同樣的方法,產生用戶端的私密金鑰,用戶端的認證,並且匯入到服務端的Trust KeyStore中
1)keytool -genkey -alias clientkey -keystore kclient.keystore
2)keytool -export -alias clientkey -keystore kclient.keystore -file client.crt
3)keytool -import -alias clientkey -file client.crt -keystore tserver.keystore
如此一來,產生的檔案分成兩組
服務端儲存:kserver.keystore tserver.keystore
用戶端儲存:kclient.keystore tclient.kyestore
接下來,就採用JSSE,分別產生SSLServerSocket,SSLSocket
服務端,產生SSLServerSocket代碼SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("data/kserver.keystore"), SERVER_KEY_STORE_PASSWORD.toCharArray());
tks.load(new FileInputStream("data/tserver.keystore"), SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray());
kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(DEFAULT_PORT);
用戶端,產生SSLSocket的代碼,大同小異SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("data/kclient.keystore"), CLIENT_KEY_STORE_PASSWORD.toCharArray());
tks.load(new FileInputStream("data/tclient.keystore"), CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray());
kmf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return (SSLSocket) ctx.getSocketFactory().createSocket(DEFAULT_HOST, DEFAULT_PORT);
如此,就完成了服務端和用戶端之間的基於身份認證的互動。
client採用kclient.keystore中的clientkey私密金鑰進行資料加密,發送給server
server採用tserver.keystore中的client.crt認證(包含了clientkey的公開金鑰)對資料解密,如果解密成功,證明訊息來自client,進行邏輯處理
server採用kserver.keystore中的serverkey私密金鑰進行資料叫米,發送給client
client採用tclient.keystore中的server.crt認證(包含了serverkey的公開金鑰)對資料解密,如果解密成功,證明訊息來自server,進行邏輯處理
如果過程中,解密失敗,那麼證明訊息來源錯誤。不進行邏輯處理。這樣就完成了雙向的身份認證。
下面我附上簡單的SSLServer.java SSLClient.java,供大家示範用。
啟動服務端的時候,大家不妨採用telnet 127.0.0.1 7777串連,看看能不能實現訊息傳遞。
ssl demo
備忘:
demo是採用maven構建項目的
demo檔案的編碼是用utf8,為了避免中文亂碼,請把workspace設定成utf8編碼
後註:
我用你的方法試了,的確可以實現ssl加密傳輸,但是我重建用戶端認證後,並沒有載入到伺服器端的信任庫tserver.keystore中,但是仍然可以通訊,這樣就是我隨便產生個用戶端認證只要有伺服器的認證就可以通訊了,沒有真正意義上達到雙向認證,如何解決這個問題,請指教
謝謝wangpeng朋友,找到上原先demo中的一個問題。
因為在Server端程式中,初始化的SSLServreSocket
serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(DEFAULT_PORT);
少寫了條語句:serverSocket.setNeedClientAuth(true); //表明需要驗證用戶端的身份。
由於原demo程式,不需要用戶端驗證,所以即使服務端沒有用戶端認證,也能完成通訊。
受限於自己對jsse理解非常的淺,上面的文章僅僅是覆蓋了jsse很表層的內容。
推薦ibm網站上的一篇文章,對jsse和ssl寫得很深入淺出。
為進階 JSSE 開發人員定製 :http://www.ibm.com/developerworks/cn/java/j-customssl/