java SSLSocket的詳解

 

1. 什麼是SSLSocket

JDK文檔指出,SSLSocket擴展Socket並提供使用SSL或TLS協議的安全套接字。

這種套接字是正常的流套接字,但是它們在基礎網絡傳輸協議(如TCP)上添加了安全保護層。

具體安全方面的討論見下一篇。本篇重點關注SSLSocket及相關幾個類的使用。

 

2. SSLSocket和相關類

SSLSocket來自jsse(Java Secure Socket Extension)。

 

 

(1)SSLContext: 此類的實例表示安全套接字協議的實現, 它是SSLSocketFactory、SSLServerSocketFactory和SSLEngine的工廠。

 

(2)SSLSocket: 擴展自Socket

 

(3)SSLServerSocket: 擴展自ServerSocket

 

(4)SSLSocketFactory: 抽象類,擴展自SocketFactory, SSLSocket的工廠

 

(5)SSLServerSocketFactory: 抽象類,擴展自ServerSocketFactory, SSLServerSocket的工廠

 

(6)KeyStore: 表示密鑰和證書的存儲設施

 

(7)KeyManager: 接口,JSSE密鑰管理器

 

(8)TrustManager: 接口,信任管理器(?翻譯得很拗口)

 

(9)X590TrustedManager: TrustManager的子接口,管理X509證書,驗證遠程安全套接字

 

3. SSLContext的使用


    public static void main(String[] args) throws Exception {  
        X509TrustManager x509m = new X509TrustManager() {  
      
            @Override  
            public X509Certificate[] getAcceptedIssuers() {  
                return null;  
            }  
      
            @Override  
            public void checkServerTrusted(X509Certificate[] chain,  
                    String authType) throws CertificateException {  
            }  
      
            @Override  
            public void checkClientTrusted(X509Certificate[] chain,  
                    String authType) throws CertificateException {  
            }  
        };  
        // 獲取一個SSLContext實例  
        SSLContext s = SSLContext.getInstance("SSL");  
        // 初始化SSLContext實例  
        s.init(null, new TrustManager[] { x509m },  
                new java.security.SecureRandom());  
        // 打印這個SSLContext實例使用的協議  
        System.out.println("缺省安全套接字使用的協議: " + s.getProtocol());  
        // 獲取SSLContext實例相關的SSLEngine  
        SSLEngine e = s.createSSLEngine();  
        System.out  
                .println("支持的協議: " + Arrays.asList(e.getSupportedProtocols()));  
        System.out.println("啓用的協議: " + Arrays.asList(e.getEnabledProtocols()));  
        System.out.println("支持的加密套件: "  
                + Arrays.asList(e.getSupportedCipherSuites()));  
        System.out.println("啓用的加密套件: "  
                + Arrays.asList(e.getEnabledCipherSuites()));  
    }  

 運行結果如下:

 

 

SSLContext.getProtocol(): 返回當前SSLContext對象的協議名稱

SSLContext.init():  初始化當前SSLContext對象。 三個參數均可以爲null。 詳見JDK文檔。

SSLEngine.getSupportedProtocols()等幾個方法可以返回些 Engine上支持/已啓用的協議、支持/已啓用的加密套件

 

4. SSLSocket和SSLServerSocket的使用

這兩個類的用法跟Socket/ServerSocket的用法比較類似。看下面的例子(主要爲了驗證SSLSocket的用法 ,I/O和多線程處理比較隨意)

 

4.1 SSLServerSocket

(1)新建一個SSLServerSocket,並開始監聽來自客戶端的連接

    // 拋出異常  
    // javax.net.ssl.SSLException: No available certificate or key corresponds  
    // to the SSL cipher suites which are enabled.  
    public static void notOk() throws IOException {  
        SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory  
                .getDefault();  
        SSLServerSocket server = (SSLServerSocket) factory  
                .createServerSocket(10000);  
        System.out.println("ok");  
        server.accept();  
    }  

server.accept()處拋出異常, 提示缺少證書。與ServerSocket不同, SSLServerSocket需要證書來進行安全驗證。

 

使用keytool工具生成一個證書。 步驟如下, 得到一個名爲cmkey的證書文件

 

 

(2)重新完善上面的代碼。 主要增加兩個功能: 使用名爲cmkey的證書初始化SSLContext, echo客戶端的消息。 代碼如下

    // 啓動一個ssl server socket  
    // 配置了證書, 所以不會拋出異常  
    public static void sslSocketServer() throws Exception {  
      
        // key store相關信息  
        String keyName = "cmkey";  
        char[] keyStorePwd = "123456".toCharArray();  
        char[] keyPwd = "123456".toCharArray();  
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());  
      
        // 裝載當前目錄下的key store. 可用jdk中的keytool工具生成keystore  
        InputStream in = null;  
        keyStore.load(in = Test2.class.getClassLoader().getResourceAsStream(  
                keyName), keyPwd);  
        in.close();  
      
        // 初始化key manager factory  
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory  
                .getDefaultAlgorithm());  
        kmf.init(keyStore, keyPwd);  
      
        // 初始化ssl context  
        SSLContext context = SSLContext.getInstance("SSL");  
        context.init(kmf.getKeyManagers(),  
                new TrustManager[] { new MyX509TrustManager() },  
                new SecureRandom());  
      
        // 監聽和接收客戶端連接  
        SSLServerSocketFactory factory = context.getServerSocketFactory();  
        SSLServerSocket server = (SSLServerSocket) factory  
                .createServerSocket(10002);  
        System.out.println("ok");  
        Socket client = server.accept();  
        System.out.println(client.getRemoteSocketAddress());  
      
        // 向客戶端發送接收到的字節序列  
        OutputStream output = client.getOutputStream();  
      
        // 當一個普通 socket 連接上來, 這裏會拋出異常  
        // Exception in thread "main" javax.net.ssl.SSLException: Unrecognized  
        // SSL message, plaintext connection?  
        InputStream input = client.getInputStream();  
        byte[] buf = new byte[1024];  
        int len = input.read(buf);  
        System.out.println("received: " + new String(buf, 0, len));  
        output.write(buf, 0, len);  
        output.flush();  
        output.close();  
        input.close();  
      
        // 關閉socket連接  
        client.close();  
        server.close();  
    }  

4.2 SSLSocket

(1)我們先使用一個普通的Socket嘗試連接服務器端

 

    // 通過socket連接服務器  
    public static void socket() throws UnknownHostException, IOException {  
        Socket s = new Socket("localhost", 10002);  
        System.out.println(s);  
        System.out.println("ok");  
      
        OutputStream output = s.getOutputStream();  
        InputStream input = s.getInputStream();  
      
        output.write("alert".getBytes());  
        System.out.println("sent: alert");  
        output.flush();  
      
        byte[] buf = new byte[1024];  
        int len = input.read(buf);  
        System.out.println("received:" + new String(buf, 0, len));  
    }  

結果客戶端和服務器端都出錯。 客戶端的錯誤是接收到亂碼。 


服務器則拋出異常

javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

 

(2)改成SSLSocket, 但是不使用證書。客戶端拋出sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    // 不使用證書, 通過ssl socket連接服務器  
    // 拋出異常, 提示找不到證書  
    public static void sslSocket() throws UnknownHostException, IOException {  
        SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory  
                .getDefault();  
        SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002);  
        System.out.println("ok");  
      
        OutputStream output = s.getOutputStream();  
        InputStream input = s.getInputStream();  
      
        output.write("alert".getBytes());  
        System.out.println("sent: alert");  
        output.flush();  
      
        byte[] buf = new byte[1024];  
        int len = input.read(buf);  
        System.out.println("received:" + new String(buf, 0, len));  
    }  

程序客戶在不持有證書的情況下直接進行連接,服務器端會產生運行時異常javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown,不允許進行連接。 我們可以指定像下面這樣執行客戶端,服務器端可以成功echo客戶端的發出的字符串"alert"

 

 java  -Djavax.net.ssl.trustStore=cmkey Client

 

這裏的cmkey即前面生成的證書文件。

 

(3)改成SSLSocket, 對SSLContext進行如下初始化。

    public static void sslSocket2() throws Exception {  
        SSLContext context = SSLContext.getInstance("SSL");  
                   // 初始化  
        context.init(null,  
                new TrustManager[] { new Test2.MyX509TrustManager() },  
                new SecureRandom());  
        SSLSocketFactory factory = context.getSocketFactory();  
        SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002);  
        System.out.println("ok");  
      
        OutputStream output = s.getOutputStream();  
        InputStream input = s.getInputStream();  
      
        output.write("alert".getBytes());  
        System.out.println("sent: alert");  
        output.flush();  
      
        byte[] buf = new byte[1024];  
        int len = input.read(buf);  
        System.out.println("received:" + new String(buf, 0, len));  
    }  


發佈了34 篇原創文章 · 獲贊 81 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章