SSL 在tcp socket中应用及优化

写在前面的话


在rpc 通信过程中,往往使用nio channel
完成通信,如果想获取一个安全的信道,需要使用SSL/TLS。接下来我们从socket出发,然后在简要介绍nio的ssl使用以及常见问题处理。

socket ssl 使用

首先,我们先查看未使用安全套接字的通信

  • server 端实现:

    import java.io.*;
    import java.net.*;
    
    . . .
    
    int port = availablePortNumber;
    
    ServerSocket s;
    
    try {
        s = new ServerSocket(port);
        Socket c = s.accept();
    
        OutputStream out = c.getOutputStream();
        InputStream in = c.getInputStream();
    
        // Send messages to the client through
        // the OutputStream
        // Receive messages from the client
        // through the InputStream
    }
    
    catch (IOException e) {
    }
  • client 端实现;

    import java.io.*;
    import java.net.*;
    
    . . .
    
    int port = availablePortNumber;
    String host = "hostname";
    
    try {
        s = new Socket(host, port);
    
        OutputStream out = s.getOutputStream();
        InputStream in = s.getInputStream();
    
        // Send messages to the server through
        // the OutputStream
        // Receive messages from the server
        // through the InputStream
    }
    
    catch (IOException e) {
    }

    接着,我们看下ssl加密的代码

  • server 端:

    import java.io.*;
    import javax.net.ssl.*;
    
    . . .
    
    int port = availablePortNumber;
    
    SSLServerSocket s;
    
    try {
        SSLServerSocketFactory sslSrvFact =
            (SSLServerSocketFactory)
            SSLServerSocketFactory.getDefault();
        s =(SSLServerSocket)sslSrvFact.createServerSocket(port);
    
        SSLSocket c = (SSLSocket)s.accept();
    
        OutputStream out = c.getOutputStream();
        InputStream in = c.getInputStream();
    
        // Send messages to the client through
        // the OutputStream
        // Receive messages from the client
        // through the InputStream
    }
    
    catch (IOException e) {
    }
  • client 端:

    import java.io.*;
    import javax.net.ssl.*;
    
    . . .
    
    int port = availablePortNumber;
    String host = "hostname";
    
    try {
        SSLSocketFactory sslFact =
          (SSLSocketFactory)SSLSocketFactory.getDefault();
        SSLSocket s =
          (SSLSocket)sslFact.createSocket(host, port);
    
        OutputStream out = s.getOutputStream();
        InputStream in = s.getInputStream();
    
        // Send messages to the server through
        // the OutputStream
        // Receive messages from the server
        // through the InputStream
    }
    
    catch (IOException e) {
    }

简单来看,并没有太多的改动,使用参考网上的示例跑了,需要导入证书。 使用keytool
生成的证书。

至此,建立完链接,接下来我们通过wireshark来分析ssl 数据包:

从图中不难看出handshake 过程:

  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 连接的消息。

我们可以看到步骤2:

Server Hello 过程会带着握手的加密套件来回应

常见问题:

1
2
3
Waiting for connection... 
javax.net.ssl.SSLHandshakeException: no cipher suites in common
Waiting for connection...

解决方式:生成公私

Runtime Exception: No Cipher Suites in Common Problem 1: When
handshaking, the client and/or server throw this exception.

Cause 1: Both sides of an SSL connection must agree on a common
ciphersuite. If the intersection of the client’s ciphersuite set with
the server’s ciphersuite set is empty, then you will see this exception.

Solution 1: Configure the enabled cipher suites to include common
ciphersuites, and be sure to provide an appropriate keyEntry for
asymmetric ciphersuites. (See Exception, “No available certificate…”
in this section.)

Problem 2: When using Netscape Navigator or Microsoft Internet Explorer
(IE) to access files on a server that only has DSA-based certificates, a
runtime exception occurs indicating that there are no cipher suites in
common.

Cause 2: By default, keyEntries created with keytool use DSA public
keys. If only DSA keyEntries exist in the keystore, only DSA-based
ciphersuites can be used. By default, Navigator and IE send only
RSA-based ciphersuites. Since the intersection of client and server
ciphersuite sets is empty, this exception is thrown.

Solution 2: To interact with Navigator or IE, you should create
certificates that use RSA-based keys. To do this, you need to specify
the -keyalg RSA option when using keytool. For example:

keytool -genkey -alias duke -keystore testkeys -keyalg rsa

NIO 中 SSL 使用,设置使用加密套件

1
2
3
4
5
if (supportedCipherSuitesWithEncryption == null) {
cipherSuites =Arrays.stream(supportedCipherSuites).filter(s -> s.contains("256")).collect(Collectors.toList());

}
sslEngine.setEnabledCipherSuites(supportedCipherSuites);

最后遇到的问题,设置加密套件不生效:

保证设置加密套件的逻辑是在handshake 之前就可以。

参考

  1. https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#NoSSLSocket
  2. https://blog.csdn.net/loganyang123/article/details/45567825 ssl
    socket 示例

休息一下

坚持原创技术分享,您的支持将鼓励我继续创作!