Home > Java > javaTutorial > body text

Sample code sharing of SSL communication principles under Java

黄舟
Release: 2017-03-25 10:51:00
Original
1930 people have browsed it

There are already many principles and introductions to SSL on the Internet. There are also many tutorials on using keytool to generate certificates under Java and configure SSL communication. But if we can't make an SSL Sever and SSL Client ourselves, we may never be able to deeply understand how SSL communication is implemented in the Java environment. Knowledge of various concepts in SSL may also be limited to the extent that they can be used. This article explains the communication principle of SSL in the Java environment by constructing a simple SSL Server and SSL Client.

First, let’s review conventional Java Socket programming. It is relatively simple to write an example of a Socket server and client in Java.

The server is very simple: listen to port 8080 and return the string sent by the client. The following is the server code:

package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server extends Thread {
    private Socket socket;
    public Server(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter writer = new PrintWriter(socket.getOutputStream());
            String data = reader.readLine();
            writer.println(data);
            writer.close();
            socket.close();
        } catch (IOException e) {

        }
    }
    public static void main(String[] args) throws Exception {
        while (true) {
            new Server((new ServerSocket(8080)).accept()).start();
        }
    }
}
Copy after login

The client is also very simple: initiate a request to the server, send a "hello" string, and then get the return from the server. The following is the client code:

package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
    public static void main(String[] args) throws Exception {
        Socket s = new Socket("localhost", 8080);
        PrintWriter writer = new PrintWriter(s.getOutputStream());
        BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
        writer.println("hello");
        writer.flush();
        System.out.println(reader.readLine());
        s.close();
    }
}
Copy after login

After running the server and executing the client, we will get a "hello" return.

This is such a simple set of network communication code. Let's transform it into using SSL communication. In the SSL communication protocol, we all know that first the server must have a digital certificate. When the client connects to the server, it will get this certificate. Then the client will judge whether the certificate is trusted. If so, exchange Channel encryption key for communication. If the certificate is not trusted, the connection fails.

Therefore, we first need to generate a digital certificate for the server. In the Java environment, digital certificates are generated using keytool, and these certificates are stored in the concept of store, which is the certificate warehouse. Let's call the keytool command to generate a digital certificate for the server and save the certificate warehouse it uses:

keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks 
-dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123
Copy after login

In this way, we save the server certificate bluedash-ssl-demo-server in the server_ksy store file. The usage of keytool will not be described in detail in this article. Execute the above command to get the following results:

Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
        for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./server_ks]
Copy after login

Then, transform our server code to let the server use this certificate and provide SSL communication:

package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;

import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;

public class SSLServer extends Thread {
    private Socket socket;

    public SSLServer(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter writer = new PrintWriter(socket.getOutputStream());

            String data = reader.readLine();
            writer.println(data);
            writer.close();
            socket.close();
        } catch (IOException e) {

        }
    }

    private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks";
    private static String SERVER_KEY_STORE_PASSWORD = "123123";

    public static void main(String[] args) throws Exception {
        System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);
        SSLContext context = SSLContext.getInstance("TLS");

        KeyStore ks = KeyStore.getInstance("jceks");
        ks.load(new FileInputStream(SERVER_KEY_STORE), null);
        KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
        kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
        context.init(kf.getKeyManagers(), null, null);
        ServerSocketFactory factory = context.getServerSocketFactory();
        ServerSocket _socket = factory.createServerSocket(8443);
        ((SSLServerSocket) _socket).setNeedClientAuth(false);

        while (true) {
            new SSLServer(_socket.accept()).start();
        }
    }
}
Copy after login

You can see that the Socket of the server The preparation and setup work has been greatly increased, and the added code is mainly used to import and use the certificate. In addition, the Socket used became SSLServerSocket, and the port was changed to 8443 (this is not mandatory, just to comply with custom). In addition, the most important point is that the CN in the server certificate must be consistent with the domain name of the server. The domain name of our certificate service is localhost, so our client must also use localhost when connecting to the server, otherwise it will be connected according to SSL The protocol standard, the domain name and the CN of the certificate do not match, indicating that the certificate is not secure, and communication will not operate normally.

With the server, our original client cannot be used, and the SSL protocol must be used. Since the server's certificate is generated by ourselves and is not signed by any trusted authority, the client cannot verify the validity of the server's certificate, and communication will inevitably fail. So we need to create a warehouse for the client to save all trust certificates, and then import the server certificate into this warehouse. In this way, when the client connects to the server, it will find that the server's certificate is in its trust list, and it can communicate normally.

So what we have to do now is to generate a client certificate warehouse. Because keytool cannot only generate a blank warehouse, so just like the server, we still generate a certificate plus a warehouse (client certificate plus warehouse) :

keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks 
-dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456
Copy after login

The results are as follows:

Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days
        for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
[Storing ./client_ks]
Copy after login

Next, we need to export the server’s certificate and import it into the client’s warehouse. The first step is to export the server's certificate:

keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer
Copy after login

The execution results are as follows:

Enter keystore password:  server
Certificate stored in file <server_key.cer>
Copy after login

Then import the exported certificate into the client certificate warehouse:

keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks
Copy after login

The results are as follows:

Enter keystore password:  client
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c7de
Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010
Certificate fingerprints:
         MD5:  FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C
         SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4
         Signature algorithm name: SHA1withRSA
         Version: 3
Trust this certificate? [no]:  yes
Certificate was added to keystore
Copy after login

Okay, the preparation work is done, let’s write the client code:

package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

public class SSLClient {

    private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";

    public static void main(String[] args) throws Exception {
        // Set the key store to use for validating the server cert.
        System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);

        System.setProperty("javax.net.debug", "ssl,handshake");

        SSLClient client = new SSLClient();
        Socket s = client.clientWithoutCert();

        PrintWriter writer = new PrintWriter(s.getOutputStream());
        BufferedReader reader = new BufferedReader(new InputStreamReader(s
                .getInputStream()));
        writer.println("hello");
        writer.flush();
        System.out.println(reader.readLine());
        s.close();
    }

    private Socket clientWithoutCert() throws Exception {
        SocketFactory sf = SSLSocketFactory.getDefault();
        Socket s = sf.createSocket("localhost", 8443);
        return s;
    }
}
Copy after login

As you can see, in addition to turning some classes into SSL communication classes, the client also has more uses Code for trusting the certificate repository. Above, we have completed the SSL one-way handshake communication. That is: the client verifies the server's certificate, and the server does not verify the client's certificate.
The above is the entire process of SSL one-way handshake in Java environment. Because we set the log output level on the client to DEBUG:

System.setProperty("javax.net.debug", "ssl,handshake");
Copy after login

, we can see the entire process of SSL communication. These logs can help us understand more specifically the entire process of establishing a network connection through the SSL protocol. .
Combined with the log, let’s take a look at the whole process of SSL two-way authentication:

The first step: The client sends a ClientHello message, initiates an SSL connection request, and tells the server the SSL options it supports (encryption method, etc.).

*** ClientHello, TLSv1
Copy after login

Step 2: The server responds to the request, replies with the ServerHello message, and confirms the SSL encryption method with the client:

*** ServerHello, TLSv1
Copy after login

Step 3: The server publishes its public key to the client.

第四步: 客户端与服务端的协通沟通完毕,服务端发送ServerHelloDone消息:

*** ServerHelloDone
Copy after login

第五步: 客户端使用服务端给予的公钥,创建会话用密钥(SSL证书认证完成后,为了提高性能,所有的信息交互就可能会使用对称加密算法),并通过ClientKeyExchange消息发给服务器:

*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
Copy after login

第六步: 客户端通知服务器改变加密算法,通过ChangeCipherSpec消息发给服务端:

main, WRITE: TLSv1 Change Cipher Spec, length = 1
Copy after login

第七步: 客户端发送Finished消息,告知服务器请检查加密算法的变更请求:

*** Finished
Copy after login
Copy after login

第八步:服务端确认算法变更,返回ChangeCipherSpec消息

main, READ: TLSv1 Change Cipher Spec, length = 1
Copy after login

第九步:服务端发送Finished消息,加密算法生效:

*** Finished
Copy after login
Copy after login

那么如何让服务端也认证客户端的身份,即双向握手呢?其实很简单,在服务端代码中,把这一行:

((SSLServerSocket) _socket).setNeedClientAuth(false);
Copy after login

改成:

((SSLServerSocket) _socket).setNeedClientAuth(true);
Copy after login

就可以了。但是,同样的道理,现在服务端并没有信任客户端的证书,因为客户端的证书也是自己生成的。所以,对于服务端,需要做同样的工作:把客户端的证书导出来,并导入到服务端的证书仓库:

keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer
Enter keystore password:  client
Certificate stored in file <client_key.cer>

keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks
Enter keystore password:  server
Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn
Serial number: 4c57c80b
Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010
Certificate fingerprints:
         MD5:  DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79
         SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2
         Signature algorithm name: SHA1withRSA
         Version: 3
Trust this certificate? [no]:  yes
Certificate was added to keystore
Copy after login

完成了证书的导入,还要在客户端需要加入一段代码,用于在连接时,客户端向服务端出示自己的证书:

package org.bluedash.tryssl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

public class SSLClient {
    private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks";
    private static String CLIENT_KEY_STORE_PASSWORD = "456456";

    public static void main(String[] args) throws Exception {
        // Set the key store to use for validating the server cert.
        System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
        System.setProperty("javax.net.debug", "ssl,handshake");
        SSLClient client = new SSLClient();
        Socket s = client.clientWithCert();

        PrintWriter writer = new PrintWriter(s.getOutputStream());
        BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
        writer.println("hello");
        writer.flush();
        System.out.println(reader.readLine());
        s.close();
    }

    private Socket clientWithoutCert() throws Exception {
        SocketFactory sf = SSLSocketFactory.getDefault();
        Socket s = sf.createSocket("localhost", 8443);
        return s;
    }

    private Socket clientWithCert() throws Exception {
        SSLContext context = SSLContext.getInstance("TLS");
        KeyStore ks = KeyStore.getInstance("jceks");

        ks.load(new FileInputStream(CLIENT_KEY_STORE), null);
        KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
        kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
        context.init(kf.getKeyManagers(), null, null);

        SocketFactory factory = context.getSocketFactory();
        Socket s = factory.createSocket("localhost", 8443);
        return s;
    }
}
Copy after login

通过比对单向认证的日志输出,我们可以发现双向认证时,多出了服务端认证客户端证书的步骤:

*** CertificateRequest
Cert Types: RSA, DSS
Cert Authorities:
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn>
*** ServerHelloDone

*** CertificateVerify
main, WRITE: TLSv1 Handshake, length = 134
main, WRITE: TLSv1 Change Cipher Spec, length = 1
Copy after login

在 @*** ServerHelloDone@ 之前,服务端向客户端发起了需要证书的请求 @*** CertificateRequest@ 。
在客户端向服务端发出 @Change Cipher Spec@ 请求之前,多了一步客户端证书认证的过程 @*** CertificateVerify@ 。
客户端与服务端互相认证证书的情景,可参考下图:

The above is the detailed content of Sample code sharing of SSL communication principles under Java. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template