程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 簡介Java SSL/TLS的安全通訊協議

簡介Java SSL/TLS的安全通訊協議

編輯:關於JAVA

在人類建立了通信系統之後,如何保證通信的安全始終是一個重要的問題。伴隨著現代化通信系統的建立,人們利用數 學理論找到了一些行之有效的方法來保證數字通信的安全。簡單來說就是把兩方通信的過程進行保密處理,比如對雙方通信 的內容進行加密,這樣就可以有效防止偷聽者輕易截獲通信的內容。目前 SSL(Secure Sockets Layer) 及其後續版本 TLS(Transport Layer Security)是比較成熟的通信加密協議,它們常被用於在客戶端和服務器之間建立加密通信通道。 各種開發語言都給出 SSL/TLS 協議的具體實現,Java 也不例外。在 JDK 中有一個 JSSE(javax.net.ssl)包,提供了對 SSL 和 TLS 的支持。通過其所提供的一系列 API,開發者可以像使用普通 Socket 一樣使用基於 SSL 或 TLS 的安全套接 字,而不用關心 SSL 和 TLS 協議的細節,例如握手的流程等等。這使得利用 Java 開發安全的 SSL/TLS 服務器或客戶端 非常容易,本文將通過具體的例子來說明如何用 Java 語言來開發 SSL/TLS 應用。

SSL/TLS 協議的介紹

SSL/TLS 協議(RFC2246 RFC4346)處於 TCP/IP 協議與各種應用層協議之間,為數據通訊提供安全支持。

從協議內部的功能層面上來看,SSL/TLS 協議可分為兩層:

1. SSL/TLS 記錄協議(SSL/TLS Record Protocol), 它建立在可靠的傳輸層協議(如 TCP)之上,為上層協議提供數據封裝、壓縮、加密等基本功能。

2. SSL/TLS 握手 協議(SSL/TLS Handshake Protocol),它建立在 SSL/TLS 記錄協議之上,用於在實際的數據傳輸開始前,通訊雙方進行 身份認證、協商加密算法、交換加密密鑰等初始化協商功能。

從協議使用方式來看,又可以分成兩種類型:

1. SSL/TLS 單向認證,就是用戶到服務器之間只存在單方面的認證,即客戶端會認證服務器端身份,而服務器端不 會去對客戶端身份進行驗證。首先,客戶端發起握手請求,服務器收到握手請求後,會選擇適合雙方的協議版本和加密方式 。然後,再將協商的結果和服務器端的公鑰一起發送給客戶端。客戶端利用服務器端的公鑰,對要發送的數據進行加密,並 發送給服務器端。服務器端收到後,會用本地私鑰對收到的客戶端加密數據進行解密。然後,通訊雙方都會使用這些數據來 產生雙方之間通訊的加密密鑰。接下來,雙方就可以開始安全通訊過程了。

2.SSL/TLS 雙向認證,就是雙方都會互 相認證,也就是兩者之間將會交換證書。基本的過程和單向認證完全一樣,只是在協商階段多了幾個步驟。在服務器端將協 商的結果和服務器端的公鑰一起發送給客戶端後,會請求客戶端的證書,客戶端則會將證書發送給服務器端。然後,在客戶 端給服務器端發送加密數據後,客戶端會將私鑰生成的數字簽名發送給服務器端。而服務器端則會用客戶端證書中的公鑰來 驗證數字簽名的合法性。建立握手之後過程則和單向通訊完全保持一致。

SSL/TLS 協議建立通訊的基本流程如圖 1 所示,

圖 1. SSL/TLS 基本流程圖

步驟 1. ClientHello – 客戶端發送所支持的 SSL/TLS 最高協議版本號和所支持的加密算法集合及壓縮方法集合 等信息給服務器端。

步驟 2. ServerHello – 服務器端收到客戶端信息後,選定雙方都能夠支持的 SSL/TLS 協議 版本和加密方法及壓縮方法,返回給客戶端。

(可選)步驟 3. SendCertificate – 服務器端發送服務端證書給客 戶端。

(可選)步驟 4. RequestCertificate – 如果選擇雙向驗證,服務器端向客戶端請求客戶端證書。

步驟 5. ServerHelloDone – 服務器端通知客戶端初始協商結束。

(可選)步驟 6. ResponseCertificate – 如 果選擇雙向驗證,客戶端向服務器端發送客戶端證書。

步驟 7. ClientKeyExchange – 客戶端使用服務器端的公鑰 ,對客戶端公鑰和密鑰種子進行加密,再發送給服務器端。

(可選)步驟 8. CertificateVerify – 如果選擇雙向 驗證,客戶端用本地私鑰生成數字簽名,並發送給服務器端,讓其通過收到的客戶端公鑰進行身份驗證。

步驟 9. CreateSecretKey – 通訊雙方基於密鑰種子等信息生成通訊密鑰。

步驟 10. ChangeCipherSpec – 客戶端通知服 務器端已將通訊方式切換到加密模式。

步驟 11. Finished – 客戶端做好加密通訊的准備。

步驟 12. ChangeCipherSpec – 服務器端通知客戶端已將通訊方式切換到加密模式。

步驟 13. Finished – 服務器做好加密 通訊的准備。

步驟 14. Encrypted/DecryptedData – 雙方使用客戶端密鑰,通過對稱加密算法對通訊內容進行加 密。

步驟 15. ClosedConnection – 通訊結束後,任何一方發出斷開 SSL 連接的消息。

除了以上的基本流 程,SSL/TLS 協議本身還有一些概念需要在此解釋說明一下。

Key:Key 是一個比特(bit)字符串,用來加密解密 數據的,就像是一把開鎖的鑰匙。

對稱算法(symmetric cryptography):就是需要雙方使用一樣的 key 來加密解 密消息算法,常用密鑰算法有 Data Encryption Standard(DES)、triple-strength DES(3DES)、Rivest Cipher 2 (RC2)和 Rivest Cipher 4(RC4)。因為對稱算法效率相對較高,因此 SSL 會話中的敏感數據都用通過密鑰算法加密。

非對稱算法(asymmetric cryptography):就是 key 的組成是公鑰私鑰對 (key-pair),公鑰傳遞給對方私鑰自 己保留。公鑰私鑰算法是互逆的,一個用來加密,另一個可以解密。常用的算法有 Rivest Shamir Adleman(RSA)、 Diffie-Hellman(DH)。非對稱算法計算量大比較慢,因此僅適用於少量數據加密,如對密鑰加密,而不適合大量數據的通 訊加密。

公鑰證書(public key certificate):公鑰證書類似數字護照,由受信機構頒發。受信組織的公鑰證書 就是 certificate authority(CA)。多證書可以連接成證書串,第一個是發送人,下一個是給其頒發證書實體,往上到根 證書是世界范圍受信組織,包括 VeriSign, Entrust, 和 GTE CyberTrust。公鑰證書讓非對稱算法的公鑰傳遞更安全,可 以避免身份偽造,比如 C 創建了公鑰私鑰,對並冒充 A 將公鑰傳遞給 B,這樣 C 與 B 之間進行的通訊會讓 B 誤認是 A 與 B 之間通訊。

加密哈希功能(Cryptographic Hash Functions):加密哈希功能與 checksum 功能相似。不同之 處在於,checksum 用來偵測意外的數據變化而前者用來偵測故意的數據篡改。數據被哈希後產生一小串比特字符串,微小 的數據改變將導致哈希串的變化。發送加密數據時,SSL 會使用加密哈希功能來確保數據一致性,用來阻止第三方破壞通訊 數據完整性。SSL 常用的哈希算法有 Message Digest 5(MD5)和 Secure Hash Algorithm(SHA)。

消息認證碼( Message Authentication Code):消息認證碼與加密哈希功能相似,除了它需要基於密鑰。密鑰信息與加密哈希功能產生 的數據結合就是哈希消息認證碼(HMAC)。如果 A 要確保給 B 發的消息不被 C 篡改,他要按如下步驟做 --A 首先要計算 出一個 HMAC 值,將其添加到原始消息後面。用 A 與 B 之間通訊的密鑰加密消息體,然後發送給 B。B 收到消息後用密鑰 解密,然後重新計算出一個 HMAC,來判斷消息是否在傳輸中被篡改。SSL 用 HMAC 來保證數據傳輸的安全。

數字簽 名(Digital Signature):一個消息的加密哈希被創建後,哈希值用發送者的私鑰加密,加密的結果就是叫做數字簽名。

JSSE(Java Secure Socket Extension)使用介紹

在 Java SDK 中有一個叫 JSSE(javax.net.ssl)包,這 個包中提供了一些類來建立 SSL/TLS 連接。通過這些類,開發者就可以忽略復雜的協議建立流程,較為簡單地在網絡上建 成安全的通訊通道。JSSE 包中主要包括以下一些部分:

安全套接字(secure socket)和安全服務器端套接字

非阻塞式 SSL/TLS 數據處理引擎(SSLEngine)

套接字創建工廠 , 用來產生 SSL 套接字和服務器端套接字

套接字上下文 , 用來保存用於創建和數據引擎處理過程中的信息

符合 X.509 規范密碼匙和安全管理接口

下面將通過一個簡單的例子來展示如何通過 JSSE,在客戶端和服務器端建立一個 SSL/TLS 連接。設計兩個類 SSLClient 和 SSLServer,分別來表示客戶端和服務器端。客戶端將會向服務器端發起連接請求,在通過服務器端驗證建立 SSL 連接後,服務器端將會向客戶端發送一串內容,客戶端將會把收到的內容打印出來。樣例代碼如下,

SSLClient Source code:

package example.ssl.codes; 
    
import java.io.*; 
import javax.net.ssl.SSLSocket; 
import javax.net.ssl.SSLSocketFactory; 
    
class SSLClient { 
    
private SSLSocket socket = null; 
    
public SSLClient() throws IOException { 
    // 通過套接字工廠,獲取一個客戶端套接字
    SSLSocketFactory socketFactory = (SSLSocketFactory) 
SSLSocketFactory.getDefault(); 
    socket = (SSLSocket) socketFactory.createSocket("localhost", 7070); 
} 
    
public void connect() { 
    try { 
        // 獲取客戶端套接字輸出流
        PrintWriter output = new PrintWriter( 
              new OutputStreamWriter(socket.getOutputStream())); 
    // 將用戶名和密碼通過輸出流發送到服務器端
        String userName = "principal"; 
        output.println(userName); 
        String password = "credential"; 
        output.println(password); 
        output.flush(); 
           
        // 獲取客戶端套接字輸入流
        BufferedReader input = new BufferedReader( 
               new InputStreamReader(socket.getInputStream())); 
    // 從輸入流中讀取服務器端傳送的數據內容,並打印出來
        String response = input.readLine(); 
        response += "n " + input.readLine(); 
        System.out.println(response); 
       
    // 關閉流資源和套接字資源
        output.close(); 
        input.close(); 
        socket.close(); 
    } catch (IOException ioException) { 
        ioException.printStackTrace(); 
    } finally { 
        System.exit(0); 
    } 
} 
    
public static void main(String args[]) throws IOException { 
    new SSLClient().connect(); 
} 
}

SSLServer Source code:

package example.ssl.codes; 
    
 import java.io.*; 
 import javax.net.ssl.SSLServerSocket; 
 import javax.net.ssl.SSLServerSocketFactory; 
 import javax.net.ssl.SSLSocket; 
    
 class SSLServer { 
    
 // 服務器端授權的用戶名和密碼
 private static final String USER_NAME = "principal"; 
 private static final String PASSWORD = "credential"; 
 // 服務器端保密內容
 private static final String SECRET_CONTENT = 
"This is confidential content from server X, for your eye!"; 
    
 private SSLServerSocket serverSocket = null; 
    
 public SSLServer() throws Exception { 
     // 通過套接字工廠,獲取一個服務器端套接字
     SSLServerSocketFactory socketFactory = (SSLServerSocketFactory) 
 SSLServerSocketFactory.getDefault(); 
     serverSocket = (SSLServerSocket)socketFactory.createServerSocket(7070); 
 } 
    
 private void runServer() { 
     while (true) { 
         try { 
             System.out.println("Waiting for connection..."); 
             // 服務器端套接字進入阻塞狀態,等待來自客戶端的連接請求
             SSLSocket socket = (SSLSocket) serverSocket.accept(); 
                
             // 獲取服務器端套接字輸入流
             BufferedReader input = new BufferedReader( 
                    new InputStreamReader(socket.getInputStream())); 
         // 從輸入流中讀取客戶端用戶名和密碼
             String userName = input.readLine(); 
             String password = input.readLine(); 
                
             // 獲取服務器端套接字輸出流
             PrintWriter output = new PrintWriter( 
                    new OutputStreamWriter(socket.getOutputStream())); 
    
         // 對請求進行認證,如果通過則將保密內容發送給客戶端
             if (userName.equals(USER_NAME) && password.equals(PASSWORD)) { 
                 output.println("Welcome, " + userName); 
                 output.println(SECRET_CONTENT); 
             } else { 
                 output.println("Authentication failed, you have no 
 access to server X..."); 
             } 
            
         // 關閉流資源和套接字資源
             output.close(); 
             input.close(); 
             socket.close(); 
    
         } catch (IOException ioException) { 
             ioException.printStackTrace(); 
         } 
     } 
 } 
    
 public static void main(String args[]) throws Exception { 
     SSLServer server = new SSLServer(); 
     server.runServer(); 
 } 
 }

SSL 樣例程序:

java -cp ./build/classes example.ssl.codes.SSLServer 
java -cp ./build/classes example.ssl.codes.SSLClient

執行結果如下:

服務器端輸出:

Waiting for connection... 
javax.net.ssl.SSLHandshakeException: no cipher suites in common 
Waiting for connection... 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) 
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1836) 
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276) 
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:266)

客戶端輸出:

javax.net.ssl.SSLException: Connection has been shutdown: 
    javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 
    at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1426) 
    at sun.security.ssl.AppInputStream.read(AppInputStream.java:92) 
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283) 
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325) 
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177) 
    at java.io.InputStreamReader.read(InputStreamReader.java:184) 
    at java.io.BufferedReader.fill(BufferedReader.java:154) 
    at java.io.BufferedReader.readLine(BufferedReader.java:317) 
    at java.io.BufferedReader.readLine(BufferedReader.java:382) 
    at example.ssl.codes.SSLClient.connect(SSLClient.java:29) 
    at example.ssl.codes.SSLClient.main(SSLClient.java:44) 
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154) 
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1911) 
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1027) 
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake 
    (SSLSocketImpl.java:1262) 
    at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:680) 
    at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:85) 
    at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) 
    at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) 
    at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295) 
    at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) 
    at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) 
    at java.io.PrintWriter.flush(PrintWriter.java:320) 
    at example.ssl.codes.SSLClient.connect(SSLClient.java:25) 
    ... 1 more

通過程序的錯誤輸出,我們能夠發現 SSL 建立失敗了,在握手階段雙方沒有能夠協商出加密方法 等信息。這是因為默認情況下,java 虛擬機沒有與 SSL 相關的配置,需要開發者自己按照文檔進行一些配置。在 JDK 中 提供了一個安全鑰匙與證書的管理工具 Keytool。Keytool 把鑰匙,證書以及和與它們相關聯的證書鏈儲存到一個 keystore 中,默任的實現 keystore 的是一個文件,它本身有一個訪問密碼來保護存儲在其中的內容。就本樣例程序而言 ,只需要配置客戶端和服務器端雙方信任就可以了。可以按照如下幾步來完成:

1. 進入本地的 java 安裝位置的 bin 目錄中 cd /java/bin

2. 創建一個客戶端 keystore 文件,如圖 2 所示

keytool -genkey -alias sslclient -keystore sslclientkeys

圖 2. 創建 keystore 文件

3. 將客戶端 keystore 文件導出成證書格式

keytool -export -alias sslclient -keystore sslclientkeys -file sslclient.cer

4. 創建一個服務器端 keystore 文件

keytool -genkey -alias sslserver -keystore sslserverkeys

5. 將服務器端 keystore 文件導出成證書格式

keytool -export - alias sslserver -keystore sslserverkeys -file sslserver.cer

6. 將客戶端證書導入到服務器端受信任的 keystore 中

keytool -import -alias sslclient -keystore sslservertrust -file sslclient.cer

7. 將服務器端證書導入到客戶端受信任的 keystore 中

keytool -import -alias sslserver -keystore sslclienttrust -file sslserver.cer

以上所有步驟都完成後,還可以通過命令來查看 keystore 文件基本信息, 如圖 3 所示

keytool -list -keystore sslclienttrust

圖 3. 查看 keystore 文件

將前面創建的所有 keystore 文件從 java 的 bin 目錄中剪切出來,移動到樣例程序的執行目錄中,通過運行程序 時候的系統屬性來指定這些文件,重新執行一遍樣例程序。

java -cp ./build/classes 
    -Djavax.net.ssl.keyStore=sslserverkeys 
    -Djavax.net.ssl.keyStorePassword=123456
    -Djavax.net.ssl.trustStore=sslservertrust 
    -Djavax.net.ssl.trustStorePassword=123456
example.ssl.codes.SSLServer 
java -cp ./build/classes 
    -Djavax.net.ssl.keyStore=sslclientkeys 
    -Djavax.net.ssl.keyStorePassword=123456
    -Djavax.net.ssl.trustStore=sslclienttrust
    -Djavax.net.ssl.trustStorePassword=123456
example.ssl.codes.SSLClient

執行結果如下:

客戶端輸出

Welcome, principal

This is confidential content from server X, for your eye!

客戶端與服務器端成功建立起 SSL 的連接,然後服務器端 成功將字符串發送給客戶端,客戶端將其打印出來。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved