主頁 > 後端開發 > Java 網路編程 —— 安全網路通信

Java 網路編程 —— 安全網路通信

2023-06-27 07:55:47 後端開發

SSL 簡介

SSL(Secure Socket Layer,安全套接字層)是一種保證網路上的兩個節點進行安全通信的協議,IETF(Interet Engineering Task Force)國際組織對 SSL 作了標準化,制定了 RFC2246 規范,并將其稱為傳輸層安全(Transport Layer Security,TLS)

SSL 和 TLS 都建立在 TCP/IP 的基礎上,一些應用層協議,如 HTTP 和 IMAP,都可以采用 SSL 來保證安全通信,建立在 SSL 協議上的 HTTP 被稱為 HTTPS 協議,HTTP 使用的默認埠為 80,而 HTTPS 使用的默認埠為 443

SSL 采用加密技術來實作安全通信,保證通信資料的保密性和完整性,并且保證通信雙方可以驗證對方的身份

1. 加密通信

當客戶與服務器進行通信時,通信資料有可能被網路上的其他計算機非法監聽,SSL 使用加密技術實作會話雙方資訊的安全傳遞,加密技術的基本原理是,資料從一端發送到另一端時,發送者先對資料加密,然后把它發送給接收者,這樣,在網路上傳輸的是經過加密的資料,如果有人在網路上非法截獲了這批資料,由于沒有解密的密鑰,數無法獲取真正的原始資料,接收者接收到加密的資料后,先對資料解密,然后處理

2. 安全證書

除了對資料加密通信,SSL 還采用了身份認證機制,確保通信雙方都可以驗證對方的真實身份,這和生活中我們使用身份證來證明自己的身份很相似,比如你到銀行去取錢,你自稱自己是張三,如何讓對方相信你的身份呢?最有效的辦法就是出示身份證,每人都擁有唯一的身份證,這個身份證上記錄了你的真實資訊,身份證由國家權威機構頒發,不允許偽造,在身份證不能被別人假冒復制的前提下,只要你出示身份證,就可以證明你自己的身份

個人可以通過身份證來證明自己的身份,對于一個單位,比如商場,可以通過營業執照來證明身份,營業執照也由國家權威機構頒發,不允許偽造,它保證了營業執照的可信性

SSL 通過安全證書來證明客戶或服務器的身份,當客戶通過安全的連接和服務器通信時,服務器會先向客戶出示它的安全證書,這個證書宣告該服務器是安全的,而且的確是這個服務器,每一個證書在全世界范圍內都是唯一的,其他非法服務器無法假冒,可以把安全證書比作電子身份證

對于單個客戶來說,到公認的權威機構去獲取安全證書是云件麻煩的事,為了擴大客戶群并且便于客戶的訪問,許多服務器不要求客戶出示安全證書,在某些情況下,服務器也會要求客戶出示安全證書,以便核實該客戶的身份,這主要是在 B2B 事務中

獲取安全證書有兩種方式,一種方式是從權威機構獲得證書,還有一種方式是創建自我簽名證書

2.1 從權威機構獲取證書

安全證書由國際權威的證書機構頒發,它們保證了證書的可信性,申請安全證書時,必須支付一定的費用,一個安全證書只對一個 IP 地址有效,如果用戶的系統環境中有多個 IP 地址須為每個 IP 地址都購買安全證書

2.2 創建自我簽名證書

在某些場合,通信雙方只關心資料在網路上可以被安全傳輸,并不需要對方進行身份驗證,在這種情況下,可以創建自我簽名的證書,就像用戶自己制作的名片,缺乏權威性,達不到身份認證的目的,當你向對方遞交名片時,名片上聲稱你是某個大公司的老總,信不信只能由對方自己去判斷

既然自我簽名證書不能有效地證明自己的身份,那么有何意義呢?在技術上,無論是從權威機構獲得的證書,還是自己制作的證書,采用的加密技術都是一樣的,使用這些證書,都可以實作安全地加密通信

3. SSL 握手

安全證書既包含了用于加密資料的密鑰,又包含了用于證實身份的數字簽名,安全證書采用公鑰加密技術,公鑰加密指使用一對非對稱的密鑰進行加密或解密,每一對密鑰由公鑰和私鑰組成,公鑰被廣泛發布,私鑰是隱藏的,不公開,用公鑰加密的資料只能夠私鑰解密,反過來,使用私鑰加密的資料只能被公鑰解密

客戶與服務器通信時,首先要進行 SSL 握手,SSL 握手主要完成以下任務:

  • 協商使用的加密套件,加密套件中包括一組加密引數,這些引數指定了加密演算法和密鑰的長度等資訊
  • 驗證對方的身份,此操作是可選的
  • 確定使用的加密演算法

SSL 握手程序采用非對稱加密方法傳遞資料,由此來建立一個安全的會話,SSL 握手完成后,通信雙方將采用對稱加密方法傳遞實際的應用資料,所謂對稱加密,指通信雙方使用同樣的密鑰來加密資料

SSL 握手的具體流程如下:

  1. 客戶將自己的 SSL 版本號、加密引數、與會話有關的資料以及其他一些必要資訊發送到服務器
  2. 服務器將自己的 SSL 版本號、加密引數、與會話有關的資料以及其他一些必要資訊發送給客戶,同時發送給客戶的還有服務器的證書,如果服務器需要驗證客戶身份,那么服務器還會發出要求客戶提供安全證書的請求
  3. 客戶端驗證服務器證書,如果驗證失敗,就提示不能建立 SSL 連接,如果成功,就繼續下一步驟
  4. 客戶端為本次會話生成預備主密碼(pre-master secret),并將其用服務器安全證書附帶的公鑰加密后發送給服務器
  5. 如果服務器要求驗證客戶身份,那么客戶端還要再對另外一些資料簽名后,將其與客戶端證書一起發送給服務器
  6. 如果服務器要求驗證客戶身份,則檢查簽署客戶證書的 CA 是否可信,如果不在信任串列中,則結束本次會話,如果檢查通過,那么服務器用自己的私鑰解密收到的預備主密碼,并用它通過某些演算法生成本次會話的主密碼(master secret)
  7. 客戶端與服務器均使用此主密碼生成本次會話的會話密鑰(對稱密鑰),在雙方 SSL 握手結束后傳遞任何訊息均使用此會話密鑰,這樣做的主要原因是對稱加密比非對稱加密的運算量低一個數量級以上,能夠顯著提高雙方會話時的運算速度
  8. 客戶端通知服務器此后發送的訊息都使用這個會話密鑰進行加密,并通知服務器客戶端已經完成本次 SSL 握手
  9. 服務器通知客戶端此后發的訊息都使用這個會話密鑰進行加密,并通如服務器已經完成本次 SSL 握手
  10. 本次握手程序結束,會話已經建立,在接下來的會話程序中,雙方使用同一個會話密鑰分別對發送以及接收的資訊進行加密和解密

4. 創建自我簽名的安全證書

JDK 提供了制作證書的工具 keytool,它的位置為:<JDK根目錄>\bin\keytool.exe

keytool 工具提出了密鑰庫的概念,密鑰庫中可以包含多個條目,每個條目包括一個自我簽名的安全證書以及一對非對稱密鑰

通過 keytool 工具創建密鑰庫的命令為:

keytool -genkeypair -alias weiqin -keyalg RSA -keystore C:\chapter15\test.keystore
  • genkeypair:生成一對非對稱密鑰
  • alias:指定條目以及密鑰對的別名,該別名是公開的
  • keyalg:指定加密演算法,本例中采用通用的 RSA 演算法
  • keystore:設定密鑰庫檔案的存放路徑以及檔案名字

命令的運行程序首先會提示輸入密鑰庫的密碼,然后提示輸入個人資訊,如姓名、組織單位和所在城市等,只要輸入真實資訊即可

以下命令查看 test.keystore 密庫的資訊,會列出所包含的條目的資訊

keytool -list -v -keystore C:\chapter15\test.keystore -storepass "123456”

以下命令把 test.keystore 密鑰庫中別名為 weiqin 的條目匯出到一個安全證書,檔案名為 weiqin.crt,weiqin.crt 文中包含了自我簽名的安全證書,以及密鑰對中的公鑰,但不包含密鑰對中的私鑰

keytool -export -alias weigin -keystore C:\chapter15\test.keystore
	-file C:\chapter15\weigin.crt -storepass “123456”

以下命令洗掉 test.keystore 密鑰庫中的別名為 weiqin 的條目

keytool -delete -alias weigin -keystore C:\chapter15\test.keystore -storepass “123456”

以下命令把 weiqin.crt 安全證書匯入 testTrust.keystore 密庫中生成別名為 weiqin 的條目,這個條目中包含密鑰對中的公鑰,但不包含密鑰對中的私鑰

keytool -import -alias weiqin
		-keystore C:\chapter15\testTrust.keystore
		-file C:\chapter15\weiqin.crt
		-storepass "123456"

JSSE 簡介

JSSE 封裝了底層復雜的安全通信細節,使得開發人員能方便地用它來開發安全的網路應用程式

1. KeyStore、KeyManager 與 TrustManager 類

在進行安全通信時,要求客戶端與服務器端都支持 SSL 或 TCL 協議,客戶端與服務器端可能都需要設定用于證實自身身份的安全證書,還要設定信任對方的哪些安全證書,更常見的情況是,服務器端只需要設定用于證實自身身份的安全證書,而客戶端只需要設定信任服務器的哪些安全證書

KeyStore 類用于存放包含安全證書的密鑰庫,以下程式代碼創建了一個 KeyStore 物件,它從 test.keystore 密鑰庫檔案中加載安全證書

String passphrase = "123456";
//JKS是JDK 支持的KeyStore的型別
KeyStore keyStore = KeyStore.getInstance("JKS");
char[] password = passphrase.toCharArray();
//password引數用于打開密鑰庫
keyStore.load(new FileInputStream("test.keystore"), password);

KeyManager 介面的任務是選擇用于證實自身身份的安全證書,把它發送給對方,KeyManagerFactory 負責創建 KeyManager 物件,例如

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, password);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

TrustManager 介面的任務是,決定是否信任對方的安全證書,TruesManagerFactory 負責創建 TrustManager 物件,例如

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init (keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

2. SSLContext 類

SSLContext 類負責設定與安全通信有關的各種資訊,比如使用的協議(SSL 或者 TLS),自身的安全證書以及對方的安全證書,SSLContext 還負責構造 SSLServerSocketFactory、SSLSocketFactory 和 SSLEngine 物件

以下程式代碼創建并初始化了一個 SSLContext 物件,然后由它創建了一個 SSLServerSocketFactory 物件

SSLContext sslCtx = SSLContext.getInstance("TLS");//采用TLS協議
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory ssf = sslCtx.getServerSocketFactory();

SSLContext 的 init 方法的定義如下

public final void init(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws KeyManagementException
  • 引數 random 用于設定安全亂數,如果該引數為 null,init 方法就會采用默認的 SecureRandom 實作
  • 如果引數 km 為 null,那 init 方法會創建默認的 KeyManager 物件以及與之關聯的 KeyStore 物件,KeyStore 物件從系統屬性 javax.net.ssl.keyStore 取安全證書,如果不存在這樣的系統屬性,那么KeyStore 物件內容為空
  • 如果引數 tm 為 null,那么 init 方法會創建一個默認的 TrustManager 物件以及與之關聯的 KeyStore 物件,KeyStore 物件按照如下步驟獲取安全證書:
    • 先嘗試從系統屬性 javax.net.ssl.trustStore 獲取安全證書
    • 如果上一步失敗,就把 <JDK 根目錄>/lib/security/jssecacerts 檔案作為安全證書
    • 如果上一步失敗,就把 <JDK 根目錄>/lib/security/cacerts 檔案作為安全證書
    • 如果上一步失敗,那么 KeyStore 物件的內容為空

3. SSLServerSocketFactory類

SSLServerSocketFactory 類負責創建 SSLServerSocket 物件

SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(8000); // 監聽埠8000

SSLServerSocketFactory 物件有兩種創建方法:

  • 呼叫 SSLContext 類的 getServerSocketFactory 方法
  • 呼叫 SSLServerSocketFactory 類的靜態 getDefault 方法

SSLServerSocketFactory 類的靜態 getDefault 方法回傳一個默認的 SSLServerSocketFactory 物件,它與一個默認的 SSLContext 物件關聯,getDefault 方法的實作按照如下方式初始化這個默認的 SSLContext 物件

sslContext.init(null,null,null);

4. SSLSocketFactory 類

SSLSocketFactory 類負責創建 SSLSocket 物件

SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("localhost",8000);

SSLSocketFactory 物件有兩種創建方法

  • 呼叫 SSLContext 類的 getSocketFactory 方法
  • 呼叫 SSLSocketFactory 類的靜態 getDefault 方法

SSLSocketFactory 類的靜態 getDefault 方法回傳一個默認的 SSLSocketFactory 物件,它與一個默認的 SSLContext 物件關聯,getDefault 方法的實作按照如下方式初始化這個默認的 SSLContext 物件

sslContext.init(null,null,null);

5. SSLSocket類

SSLSocket 類是 Socket 類的子類,因此兩者的用法有許多相似之處,此外,SSLSocket 類還具有與安全通信有關的方法

5.1 設定加密套件

客戶與服務器在握手階段需要協商實際使用的加密套件,以下兩種情況都會導致握手失敗:

  • 不存在雙方都可以使用的相同加密套件
  • 盡管存在這樣的加套件,但是有一方或雙方沒有使用該加密套件的安全證書

SSLSocket 類的 getSupportedCipherSuites 方法回傳一個字串陣列,它包含當 SSLSocket 物件所支持的加索套件組,SSLSocket 類的 setEnabledCipherSuites(String[] suites) 方法設定當前 SSLSocket 物件的可使用的加密套件組,可使用的加密件組應該是所支持的加密套件組的子集

以下代碼僅僅啟用了具有高加密強度的加密套件,這可以提高該通信端的安全性,禁止那些不支持強加密的通信端連接當前通信端

String[] strongSuites = {
	"SSL_RSA_WITH_RC4_128_MD5",
	"SSL_RSA_WITH_RC4_128_SHA",    
	"SSL_RSA_WITH_3DES_EDE_CBC_SHA"
};
sslSocket.setEnabledCipherSuites(strongSuites);
5.2 處理握手結束事件

SSL 握手需要花很長的時間,當 SSL 握手完成,會發出一個 HandshakeCompletedEvent 事件,該事件由 HandshakeCompletedListener 負責監聽,SSLSocket 類的 addHandshakeCompletedListener 方法負責注冊 HandshakeCompletedListener 監聽器

下例為 SSLSocket 注冊了 HandshakeCompletedListener

socket.addHandshakeCompletedListener {
    new HandshakeCompletedListener() {
        public void handshakeCompleted(HandshakeCompletedEvent event) {
            System.out.println("握手結束");
            System.out.println("加密套件為:" + event.getCipherSuite());
            System.out.println("會話為:" + event.getSession());
            System.out.println("通信對方為:" + event.getSession().getPeerHost());
        }
    });
}
5.3 管理 SSL 會話

一個客戶程式可能會向一個服務器的同一個埠打開多個安全套接字,如果對于每一安全連接都進行 SSL 握手,就會大大降低通信效率,為了提高安全通信的效率,SSL 協議允許多個 SSLSocket 共享同一個 SSL 會話,在同一個會話中,只有第一個打開的 SSLSocket 要進行 SSL 握手,負責生成密鑰以及交換密鑰,其余的 SSLSocket 都共享密鑰資訊,在一段合理的時間范圍內,如果客戶程式向一個服務器的同一個埠打開多個安全套接字,JSSE 就會自動重用會話

SSLSocket 的 getSession 方法回傳 SSLSocket 所屬的會話,SSLSocket 的 setEnableSessionCreation(boolean flag) 方法決定 SSLSocket 是否允許創建新的會話,flag 的默認值為 true,如果 flag 引數為 true,那么對于新創建的 SSLSocket,如果當前已經有可用的會話,就直接加入該會話,如果沒有可用的會話,就創建一個新的會話,如果 flag 為 false 引數,那么對于新創建的 SSLSocket,如果當前已經有可用的會話,就直接加入該會話,如果沒有可用的會話,那么該 SSLSocket 無法與對方進行安全通信

SSLSocket的 startHandshake 方法顯式地執行一次 SSL 握手,該方法具有以下用途:

  • 使得會話使用新的密鑰
  • 使得會話使用新的加密套件
  • 重新開始一個會話,為了保證不重用原先的會話,應該先將原先的會話失效、、
socket.getSession().invalidate();
socket.startHandshake();
5.4 客戶端模式

多數情況下客戶端無須向服務器證實自己的身份,因此當一個通信端無須向對方證實自己身份時,就稱它處于客戶模式,否則稱它處于服務器模式,通信雙方只能有一方處于服務器模式,另一方則處于客戶模式

SSLSocket 的 setUseClientMode(boolean mode) 方法被用來設定客戶模式或者服務器模式,如果 mode 引數為 true,就表示處于客戶模式,即無須向對方證實自己的身份;如果 mode 為 false,就表示處于服務器模式,即需要向對方證實自己的身價

當 SSL 初始握手已經開始,就不允許再呼叫 SSLSocket 的 seUseClientMode(boolean mode) 方法,否則會導致例外

當 SSLSocket 處于服務器模式,還可以通過以下方法來決定是否要求對方提供身份認證:

  • setWantClientAuth(boolean want)::當 want 引數為 true 時,表示希望對方提供身份認證,如果對方未出示安全證書,則連接不會中斷,通信可繼續進行
    setNeedClientAuth(boolean need):當 need 引數為 true 時,表示要求對方必須提供身份認證,如果對方未出示安全證書,則連接中斷,通信無法繼續

6. SSLServerSocket類

SSLServerSocket 類是 ServerSocket 類的子類,因此兩者的用法有許多相似之處,此外,SSLServerSocket 類還具有與安全通信有關的方法,這些方法與 SSLSocket 類中的同名方法具有相同的作用

7. SSLEngine 類

SSLEngine 類與 SocketChannel 類聯合使用,就能實作非阻塞的安全通信,SSLEngine 類封裝了與安全通信有關的細節,把應用程式發送的應用資料打包為網路資料,打包指對應用資料進行加密,加入 SSL 握手資料,把它變為網路資料,SSLEngine 類還能把接收的網路資料展開為應用資料,展開指對網路資料解密,并且去除其中的 SSL 握手資料,從而還原為應用程式可以處理的應用資料,SSLEngine 類的 wrap 方法負責打包應用資料,unwrap 方法負責展開網路資料

public class SSLEngineDemo {
    
    private static boolean logging = true;
    private SSLContext sslc;
    
    private SSLEngine clientEngine; //客戶端Engine
    private ByteBuffer clientOut; //存放客戶端發送的應用資料
    private ByteBuffer clientIn; //存放客戶端接收到的應用資料
    
    private SSLEngine serverEngine; //服務器端Engine
    private ByteBuffer serverOut; //存放服務器端發送的應用資料
    private ByteBuffer serverIn; //存放服務器端接收到的應用資料
    
    private ByteBuffer cTOs;//存放客戶端向服務器端發送的網路資料
    private ByteBuffer sTOc;//存放服務器向客戶海發送的網路資料
        
    //設定密鑰庫檔案和信任庫檔案以及口令
    private static String keyStoreFile = "test.keystore";
    private static String trustStorefile = "test.keystore";
    private static String passphrase = "123456";
    
    public static void main(String args[]) throws Exception {
        SSLEngineDemo demo = new SSLEngineDemo();
        demo.runDemo();
    }
    
    /**初始化SSLContext*/
    public SSLEngineDemo() throws Exception {
        KeyStore ks = KeyStore.getInstance("JKS");
        KeyStore ts = KeyStore.getInstance("JKS");
        
        char[] password = passphrase.toCharArray();
        ks.load(new FileInputStream(keyStoreFile), password);
        ts.load(new FileInputStream(trustStoreFile), password);
        
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, password);
        
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ts);
        
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        sslc = sslCtx;
    }
    
    private void runDemo() throws Exception {
        boolean dataDone = false;
        
        /*創建客戶端以及服務器端的SSLEngine*/
        serverEngine = sslc.createSSLEnqine();
        serverEngine.setUseClientMode(false);
        serverEngine.setNeedClientAuth(true);
        clientEngine = sslc.createSSLEngine("client", 80);
        clientEngine.setUseClientMode(true);
        
        /*創建客戶端以及服務器的應用沖區和網路緩沖區*/
        SSLSession session = clientEngine.getsSession();
        int appBufferMax = session.getApplicationBufferSize();
        int netBufferMax = session.getPacketBufferSize();
        clientIn = ByteBuffer.allocate(appBufferMax + 50);
        serverIn = ByteBuffer.allocate(appBufferMax + 50)
            
        cTOs = ByteBuffer.allocateDirect(netBufferMax);
		sTOc = ByteBuffer.allocateDirect(netBufferMax);
        
        clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
        serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes());
        
        SSLEngineResult clientResult;
        SSLEngineResult serverResult;
        
        while (!isEnqineClosed(clientEngine) || !isEngineClosed(serverEngine)) {
            log("==================");
            //客戶端打包應用資料
            clientResult = clientEngine.wrap(clientOut, cTOs);
            log("client wrap:", clientResult);
            //完成握手任務
            runDelegatedTasks(clientResult, clientEngine);
            //服務器端打包應用資料
            serverResult = serverEngine.wrap(serverOut, sTOc);
            log("server wrap:", serverResult);
            //完成握手任務
            runDelegatedTasks(serverResult, serverEngine);
            
            cTOs.flip();
            sTOc.flip();
            
            log("---------------");
            //客戶端展開網路資料
            clientResult = clientEngine.unwrap(sTOc, clientIn);
            log("client unwrap:", clientResult);
            //完成握手任務
            runDelegatedTasks(clientResult, clientEngine);
            //服務器端展開網路資料
            serverResult = serverEngine.unwrap(cTOs, serverIn);
            log("server unwrap:", serverResult);
            //完成握手任務
            runDeleqatedTasks(serverResult, serverEngine);
            
            cTOs.compact();
            sTOc.compact();
            
            if (!dataDone && (clientOut.limit() == serverIn.position()) && (serverOut,limit()=m clientIn.position())) {
                checkTransfer(serverOut, clientIn);
                checkTransfer(clientOut, serverIn);
                log("\tClosing clientEngine's *OUTBOUND*...");
                clientEngine.closeOutbound();
                dataDone = true;
            }
        }
    }
    
    /**當SSLEngine的輸出與輸入都關閉時,意味著SSLEngine被關閉*/
    private static boolean isEngineClosed(SSLEngine engine) {
        return(engine.isOutboundDone() && engine.isInboundDone());
    }
    
    /**執行SSL握手任務*/
    private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) throws Exception {
        if(result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
            Runnable runnable;
            while((runnable = engine.getDelegatedTask()) != null) {
                log("\trunning delegated task...");
                runnable.run();
            }
            HandshakeStatus hsStatus = engine.getHandshakeStatus();
            if(hsStatus == HandshakeStatus.NEED_TASK) {
                throw new Exception("handshake shouldn't need additional tasks");
            }
            log("\tnew HandshakeStatus:" + hsStatus);
        }
    }
    
    /**判斷兩個緩沖區內容是否相同*/
    private static void checkTransfer(ByteBuffer a, ByteBuffer b) throws Exception {
        a.flip();
        b.flip();
        if(!a.equals(b)) {
            throw new Exception("pata didn't transfer cleanly");
        } else {
            log("\tData transferred cleanly");
        }
        a.position(a.limit());
        b.position(b.limit());
        a.limit(a.capacity());
        b.limit(b.capacity());
    }
    
    private static boolean resultOnce = true;
    
    /**輸出日志,列印SSLEngineResult的結果*/
    private static void log(String str, SSLEngineResult result) {
        if(resultOnce) {
            resultOnce = false;
            System.out.println("The format of the SSLEngineResult is: \n" 
                               +"\t\"getStatus() / getHandshakeStatus()\”+\n"
                               +"\t\"bytesConsumed() / bytesProduced()\"\n");
        }
        HandshakeStatus hsStatus = result.getHandshakeStatus();
        log(str + result,getStatus() + "/" + hsStatus + "," + result,bytesConsumed() + "/"
+ result.bytesProduced() + " bytes");
        if (hsStatus == HandshakeStatus.FINISHED) {
            log("\t...ready for application data");
        }
    }
    
    /**輸出日志*/
    private static void log(String str) {
        System.out.printin(str);
    }
}

創建基于 SSL 的安全服務器和安全客戶

public class EchoServer {
    
    private int port = 8000;
    private SSLServerSocket serverSocket;
    
    public EchoServer() throws Exception {
        //輸出跟蹤日志
        SSLContext context = createSSLContext();
        SSLServerSocketFactory factory = context.getServerSocketFactory();
        serverSocket = (SSLServerSocket)factory.createServerSocket(poxt);
        String[] supported = serverSocket.getSupportedCipherSuites();
        serverSocket.setEnabledCipherSuites(supported);
    }
    
    public SSLContext createSSLContext() throws Exception {
        //服務器用于證實自己身份的安全證書所在的密鑰庫
        String keyStoreFile = "test.keystore";
        String passphrase = "123456";
        KeyStore ks = KeyStore.getInstance("JKS");
        char[] password = passphrase.toCharArray();
        ks.load(new FileInputStream(keyStoreFile), password);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, password);
        
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(kmf.getKeyManagers(), null, null);
        
        //當要求客戶端提供安全證書時,服務器端可創建TrustManagerFactory,
        //并由它創建TrustManager,TrustManger根據與之關聯的KeyStore中的資訊
        //決定是否相信客戶提供的安全證書
        
        //客戶端用于證實自己身份的安全證書所在的密鑰庫
        //String trustStorefile = "test.keystore";
        //KeyStore ts = KeyStore.getInstance("JKS");
        //ts.load(new FileInputStream(trustStoreFile), password);
        //TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        //tmf.init(ts);
        //sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        
        return sslContext;
    }
    
    private PrintWriter getWriter(Socket socket) throws IOException {
        OutputStream socketOut = socket.getOutputStream();
        return new PrintWriter(socketOut, true);
    }
    
    private BufferedReader getReader(Socket socket) throws IOException {
        InputStream socketIn = socket.getInputstream();
        return new BufferedReader(new InputStreamReader(socketIn));
    }
    
    public void service() {
        while (true) {
            Socket socket = null;
            try {
                //等特客戶連接
                socket = serverSocket.accept();
                
                BufferedReader br = getReader(socket);
                PrintWriter pw = getWriter(socket);
                
                String msg = null;
                while ((msg = br.readLine()) != null) {
                    System.out.println(msg);
                    if (msg.equals("bye")) {
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(socket != null) socket.close(); //斷開連接
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String args[]) throws Exception {
        new EchoServer().service();
    }
}
public class EchoClient {
    
    private String host = "localhost";
    private int port = 8000;
    private SSLSocket socket;
    
    public EchoClient() throws IOException {
        SSLContext context = createSSLContext();
        SSLSocketFactory factory = context.getSocketFactory();
        socket = (SSLSocket)factory.createSocket(host,port);
        Stringl] supported = socket.getSupportedCipherSuites();
        socket.setEnabledCipherSuites(supported);
    }
    
    public SSLContext createSSLContext() throws Exception {
        String passphrase = "123456";
        char[] password = passphrase.toCharArray();
        
        //設定客戶端所信任的安全證書所在的密鑰庫
        String trustStoreFile = "test.keystore";
        KeyStore ts = KeyStore.getInstance("JKS");
        ts.load(new FileInputStream(trustStoreFile), password));
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ts);
        
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, tmf.getTrustManagers(), null);
        return sslContext;
    }
    
    public static void main(String args[]) throws IOException {
        new EchoClient().talk();
    }
    
    public void talk()throws IOException {
        try {
            BufferedReader br = getReader(socket);
            PrintWriter pw = getWriter(socket);
            
            BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));
            
            String msg = null;
            while((msg = localReader.readLine()) != null) {
                pw.println(msg);
                System.out.println(br.readline());
                if(msg.equals("bye")) {
                    break;
                }
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(socket != null) socket.close(); //斷開連接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private PrintWriter getWriter(Socket socket) throws IOException {
        OutputStream socketOut = socket.getoutputstream();
        return new PrintWriter(socketOut, true);
    }
    
    private BufferedReader getReader(Socket socket) throws IOException {
        InputStream socketIn = socket.getInputstream();
        return new BufferedReader(new InputStreamReader(socketIn));
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/556020.html

標籤:其他

上一篇:【技識訓累】Spring Boot中的基礎知識【一】

下一篇:返回列表

標籤雲
其他(161642) Python(38254) JavaScript(25514) Java(18265) C(15238) 區塊鏈(8272) C#(7972) AI(7469) 爪哇(7425) MySQL(7269) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4606) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2437) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1985) HtmlCss(1972) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Java 網路編程 —— 安全網路通信

    ## SSL 簡介 SSL(Secure Socket Layer,安全套接字層)是一種保證網路上的兩個節點進行安全通信的協議。IETF(Interet Engineering Task Force)國際組織對 SSL 作了標準化,制定了 RFC2246 規范,并將其稱為傳輸層安全(Transpor ......

    uj5u.com 2023-06-27 07:55:47 more
  • 【技識訓累】Spring Boot中的基礎知識【一】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-27 07:55:20 more
  • [滲透測驗]—2.3 密碼破解技術

    在本節中,我們將介紹一些常見的密碼破解技術。我們將涵蓋以下技術: 1. 字典攻擊 2. 暴力破解 3. 彩虹表 4. 社會工程攻擊 5. 密碼重置攻擊 6. 密碼保護存盤攻擊 7. 離線密碼破解 ### 1. 字典攻擊 字典攻擊是一種破解密碼的方法,它使用預先編譯的單詞串列(字典)作為密碼猜測的來源 ......

    uj5u.com 2023-06-27 07:54:59 more
  • Kubernetes 系列:Kubernetes 的安裝(三)

    ### 序 前面介紹了k8s組件和物件的一些基本概念,了解了k8s具體是做什么的以及架構,那么接下來我們開始介紹怎么去安裝k8s,這里我們以windows為例,其他平臺可以參考Kubernetes官方檔案,其實安裝方式都是類似的。 ### 先決條件 要在系統中安裝 Kubernetes,以下是一些需 ......

    uj5u.com 2023-06-27 07:54:43 more
  • C++面試八股文:std::deque用過嗎?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第26面: > 面試官:`deque`用過嗎? > > 二師兄:說實話,很少用,基本沒用過。 > > 面試官:為什么? > > 二師兄:因為使用它的場景很少,大部分需要性能、且需要自動擴容的時候使用`vector`,需要隨機插入和洗掉的時候可以使用` ......

    uj5u.com 2023-06-27 07:54:29 more
  • [ARM 匯編]高級部分—系統控制協處理器—3.2.3 控制暫存器的讀寫

    在這一部分,我們將學習如何使用ARM匯編指令在系統控制協處理器(CP15)的控制暫存器上執行讀寫操作。我們將通過實體來講解如何使用MCR(Move to Coprocessor Register)和MRC(Move from Coprocessor Register)指令進行讀寫操作。 1. **M ......

    uj5u.com 2023-06-27 07:54:23 more
  • 1 java獲取cpu核心數目

    ## java獲取cpu核心數目 >```java >int processors = Runtime.getRuntime().availableProcessors(); >``` ......

    uj5u.com 2023-06-27 07:54:17 more
  • celery筆記八之資料庫操作定時任務

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[celery筆記八之資料庫操作定時任務](https://mp.weixin.qq.com/s/iM0VxVMagmRNeG2VIc01pg) 前面我們介紹定時任務是在 celery.py 中的 `app.conf.beat_schedule` ......

    uj5u.com 2023-06-27 07:54:11 more
  • 【promptulate專欄】ChatGPT框架——兩行代碼構建一個強大的論文

    > 本文節選自筆者博客:[https://www.blog.zeeland.cn/archives/019hasaa](https://www.blog.zeeland.cn/archives/019hasaa) # 前言 如果你經常閱讀論文,那么你肯定會遇到以下幾個問題: - 論文晦澀難懂看不明白 ......

    uj5u.com 2023-06-27 07:54:04 more
  • spring啟動流程 (1) 流程概覽

    本文將通過閱讀AnnotationConfigApplicationContext原始碼,分析Spring啟動流程。 # 創建AnnotationConfigApplicationContext ```java AnnotationConfigApplicationContext applicatio ......

    uj5u.com 2023-06-27 07:53:58 more