<code id="qf3hh"></code>
  • <menuitem id="qf3hh"></menuitem>
  • <strike id="qf3hh"><label id="qf3hh"></label></strike>

  • ?
      開發(fā)技術(shù) / Technology

      Java SSL/TLS 安全通訊協(xié)議介紹

      日期:2015年2月12日  作者:zhjw  來源:互聯(lián)網(wǎng)    點(diǎn)擊:955

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

      SSL/TLS 協(xié)議的介紹

      SSL/TLS 協(xié)議(RFC2246 RFC4346)處于 TCP/IP 協(xié)議與各種應(yīng)用層協(xié)議之間,為數(shù)據(jù)通訊提供安全支持。

      從協(xié)議內(nèi)部的功能層面上來看,SSL/TLS 協(xié)議可分為兩層:

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

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

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

      1. SSL/TLS 單向認(rèn)證,就是用戶到服務(wù)器之間只存在單方面的認(rèn)證,即客戶端會(huì)認(rèn)證服務(wù)器端身份,而服務(wù)器端不會(huì)去對(duì)客戶端身份進(jìn)行驗(yàn)證。首先,客戶端發(fā)起握手請(qǐng)求,服務(wù)器收到握手請(qǐng)求后,會(huì)選擇適合雙方的協(xié)議版本和加密方式。然后,再將協(xié)商的結(jié)果和服務(wù)器端的公鑰一起發(fā)送給客戶端??蛻舳死梅?wù)器端的公鑰,對(duì)要發(fā)送的數(shù)據(jù)進(jìn)行加密,并發(fā)送給服務(wù)器端。服務(wù)器端收到后,會(huì)用本地私鑰對(duì)收到的客戶端加密數(shù)據(jù)進(jìn)行解密。然后,通訊雙方都會(huì)使用這些數(shù)據(jù)來產(chǎn)生雙方之間通訊的加密密鑰。接下來,雙方就可以開始安全通訊過程了。

      2.SSL/TLS 雙向認(rèn)證,就是雙方都會(huì)互相認(rèn)證,也就是兩者之間將會(huì)交換證書?;镜倪^程和單向認(rèn)證完全一樣,只是在協(xié)商階段多了幾個(gè)步驟。在服務(wù)器端將協(xié)商的結(jié)果和服務(wù)器端的公鑰一起發(fā)送給客戶端后,會(huì)請(qǐng)求客戶端的證書,客戶端則會(huì)將證書發(fā)送給服務(wù)器端。然后,在客戶端給服務(wù)器端發(fā)送加密數(shù)據(jù)后,客戶端會(huì)將私鑰生成的數(shù)字簽名發(fā)送給服務(wù)器端。而服務(wù)器端則會(huì)用客戶端證書中的公鑰來驗(yàn)證數(shù)字簽名的合法性。建立握手之后過程則和單向通訊完全保持一致。

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

      圖 1. SSL/TLS 基本流程圖

      圖 1. SSL/TLS 基本流程圖

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

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

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

      (可選)步驟 4. RequestCertificate – 如果選擇雙向驗(yàn)證,服務(wù)器端向客戶端請(qǐng)求客戶端證書。

      步驟 5. ServerHelloDone – 服務(wù)器端通知客戶端初始協(xié)商結(jié)束。

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

      步驟 7. ClientKeyExchange – 客戶端使用服務(wù)器端的公鑰,對(duì)客戶端公鑰和密鑰種子進(jìn)行加密,再發(fā)送給服務(wù)器端。

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

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

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

      步驟 11. Finished – 客戶端做好加密通訊的準(zhǔn)備。

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

      步驟 13. Finished – 服務(wù)器做好加密通訊的準(zhǔn)備。

      步驟 14. Encrypted/DecryptedData – 雙方使用客戶端密鑰,通過對(duì)稱加密算法對(duì)通訊內(nèi)容進(jìn)行加密。

      步驟 15. ClosedConnection – 通訊結(jié)束后,任何一方發(fā)出斷開 SSL 連接的消息。

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

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

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

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

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

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

      消息認(rèn)證碼(Message Authentication Code): 消息認(rèn)證碼與加密哈希功能相似,除了它需要基于密鑰。密鑰信息與加密哈希功能產(chǎn)生的數(shù)據(jù)結(jié)合就是哈希消息認(rèn)證碼(HMAC)。如果 A 要確保給 B 發(fā)的消息不被 C 篡改,他要按如下步驟做 --A 首先要計(jì)算出一個(gè) HMAC 值,將其添加到原始消息后面。用 A 與 B 之間通訊的密鑰加密消息體,然后發(fā)送給 B。B 收到消息后用密鑰解密,然后重新計(jì)算出一個(gè) HMAC,來判斷消息是否在傳輸中被篡改。SSL 用 HMAC 來保證數(shù)據(jù)傳輸?shù)陌踩?/p>

      數(shù)字簽名(Digital Signature):一個(gè)消息的加密哈希被創(chuàng)建后,哈希值用發(fā)送者的私鑰加密,加密的結(jié)果就是叫做數(shù)字簽名。

       

      回頁首

      JSSE(Java Secure Socket Extension)使用介紹

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

      • 安全套接字(secure socket)和安全服務(wù)器端套接字
      • 非阻塞式 SSL/TLS 數(shù)據(jù)處理引擎(SSLEngine)
      • 套接字創(chuàng)建工廠 , 用來產(chǎn)生 SSL 套接字和服務(wù)器端套接字
      • 套接字上下文 , 用來保存用于創(chuàng)建和數(shù)據(jù)引擎處理過程中的信息
      • 符合 X.509 規(guī)范密碼匙和安全管理接口

      下面將通過一個(gè)簡單的例子來展示如何通過 JSSE,在客戶端和服務(wù)器端建立一個(gè) SSL/TLS 連接。設(shè)計(jì)兩個(gè)類 SSLClient 和 SSLServer,分別來表示客戶端和服務(wù)器端??蛻舳藢?huì)向服務(wù)器端發(fā)起連接請(qǐng)求,在通過服務(wù)器端驗(yàn)證建立 SSL 連接后,服務(wù)器端將會(huì)向客戶端發(fā)送一串內(nèi)容,客戶端將會(huì)把收到的內(nèi)容打印出來。樣例代碼如下,

      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 { 
           // 通過套接字工廠,獲取一個(gè)客戶端套接字
           SSLSocketFactory socketFactory = (SSLSocketFactory) 
       SSLSocketFactory.getDefault(); 
           socket = (SSLSocket) socketFactory.createSocket("localhost", 7070); 
       } 
      
       public void connect() { 
      	 try { 
      		 // 獲取客戶端套接字輸出流
      		 PrintWriter output = new PrintWriter( 
      		       new OutputStreamWriter(socket.getOutputStream())); 
      	 // 將用戶名和密碼通過輸出流發(fā)送到服務(wù)器端
      		 String userName = "principal"; 
      		 output.println(userName); 
      		 String password = "credential"; 
      		 output.println(password); 
      		 output.flush(); 
      		
      		 // 獲取客戶端套接字輸入流
      		 BufferedReader input = new BufferedReader( 
      		        new InputStreamReader(socket.getInputStream())); 
      	 // 從輸入流中讀取服務(wù)器端傳送的數(shù)據(jù)內(nèi)容,并打印出來
      		 String response = input.readLine(); 
      		 response += "n " + input.readLine(); 
      		 System.out.println(response); 
      	
      	 // 關(guān)閉流資源和套接字資源
      		 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 { 
      
       // 服務(wù)器端授權(quán)的用戶名和密碼
       private static final String USER_NAME = "principal"; 
       private static final String PASSWORD = "credential"; 
       // 服務(wù)器端保密內(nèi)容
       private static final String SECRET_CONTENT = 
      "This is confidential content from server X, for your eye!"; 
      
       private SSLServerSocket serverSocket = null; 
      
       public SSLServer() throws Exception { 
      	 // 通過套接字工廠,獲取一個(gè)服務(wù)器端套接字
      	 SSLServerSocketFactory socketFactory = (SSLServerSocketFactory) 
       SSLServerSocketFactory.getDefault(); 
      	 serverSocket = (SSLServerSocket)socketFactory.createServerSocket(7070); 
       } 
      
       private void runServer() { 
      	 while (true) { 
      		 try { 
      			 System.out.println("Waiting for connection..."); 
      			 // 服務(wù)器端套接字進(jìn)入阻塞狀態(tài),等待來自客戶端的連接請(qǐng)求
      			 SSLSocket socket = (SSLSocket) serverSocket.accept(); 
      			
      			 // 獲取服務(wù)器端套接字輸入流
      			 BufferedReader input = new BufferedReader( 
      			        new InputStreamReader(socket.getInputStream())); 
      		 // 從輸入流中讀取客戶端用戶名和密碼
      			 String userName = input.readLine(); 
      			 String password = input.readLine(); 
      			
      			 // 獲取服務(wù)器端套接字輸出流
      			 PrintWriter output = new PrintWriter( 
      			        new OutputStreamWriter(socket.getOutputStream())); 
      
      		 // 對(duì)請(qǐng)求進(jìn)行認(rèn)證,如果通過則將保密內(nèi)容發(fā)送給客戶端
      			 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..."); 
      			 } 
      		
      		 // 關(guān)閉流資源和套接字資源
      			 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

      執(zhí)行結(jié)果如下:

      服務(wù)器端輸出:

       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

      通過程序的錯(cuò)誤輸出,我們能夠發(fā)現(xiàn) SSL 建立失敗了,在握手階段雙方?jīng)]有能夠協(xié)商出加密方法等信息。這是因?yàn)槟J(rèn)情況下,java 虛擬機(jī)沒有與 SSL 相關(guān)的配置,需要開發(fā)者自己按照文檔進(jìn)行一些配置。在 JDK 中提供了一個(gè)安全鑰匙與證書的管理工具 Keytool。Keytool 把鑰匙,證書以及和與它們相關(guān)聯(lián)的證書鏈儲(chǔ)存到一個(gè) keystore 中,默任的實(shí)現(xiàn) keystore 的是一個(gè)文件,它本身有一個(gè)訪問密碼來保護(hù)存儲(chǔ)在其中的內(nèi)容。就本樣例程序而言,只需要配置客戶端和服務(wù)器端雙方信任就可以了。可以按照如下幾步來完成:

      1. 進(jìn)入本地的 java 安裝位置的 bin 目錄中 cd /java/bin

      2. 創(chuàng)建一個(gè)客戶端 keystore 文件,如圖 2 所示

      keytool -genkey -alias sslclient -keystore sslclientkeys
      圖 2. 創(chuàng)建 keystore 文件

      圖 2. 創(chuàng)建 keystore 文件

      3. 將客戶端 keystore 文件導(dǎo)出成證書格式

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

      4. 創(chuàng)建一個(gè)服務(wù)器端 keystore 文件

      keytool -genkey -alias sslserver -keystore sslserverkeys

      5. 將服務(wù)器端 keystore 文件導(dǎo)出成證書格式

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

      6. 將客戶端證書導(dǎo)入到服務(wù)器端受信任的 keystore 中

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

      7. 將服務(wù)器端證書導(dǎo)入到客戶端受信任的 keystore 中

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

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

      keytool -list -keystore sslclienttrust
      圖 3. 查看 keystore 文件

      圖 3. 查看 keystore 文件

      將前面創(chuàng)建的所有 keystore 文件從 java 的 bin 目錄中剪切出來,移動(dòng)到樣例程序的執(zhí)行目錄中,通過運(yùn)行程序時(shí)候的系統(tǒng)屬性來指定這些文件,重新執(zhí)行一遍樣例程序。

        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

      執(zhí)行結(jié)果如下:

      客戶端輸出

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

      客戶端與服務(wù)器端成功建立起 SSL 的連接,然后服務(wù)器端成功將字符串發(fā)送給客戶端,客戶端將其打印出來。

      国产一级婬片AAA毛,无码中文精品视视在线观看,欧美日韩a人成v在线动漫,五月丁香青草久久
      <code id="qf3hh"></code>
    • <menuitem id="qf3hh"></menuitem>
    • <strike id="qf3hh"><label id="qf3hh"></label></strike>