Heim >Java >javaLernprogramm >Beispielcode für die gemeinsame Nutzung von SSL-Kommunikationsprinzipien unter Java

Beispielcode für die gemeinsame Nutzung von SSL-Kommunikationsprinzipien unter Java

黄舟
黄舟Original
2017-03-25 10:51:001947Durchsuche

Es gibt bereits viele Prinzipien und Einführungen zu SSL im Internet. Außerdem gibt es viele Tutorials zur Verwendung von Keytool zum Generieren von Zertifikaten unter Java und zum Konfigurieren der SSL-Kommunikation. Aber wenn wir nicht selbst einen SSL-Server und einen SSL-Client erstellen können, werden wir möglicherweise nie wirklich verstehen können, wie die SSL-Kommunikation in der Java-Umgebung implementiert wird. Auch das Wissen über verschiedene Konzepte in SSL kann in dem Umfang eingeschränkt sein, in dem sie verwendet werden können. In diesem Artikel wird das Kommunikationsprinzip von SSL in der Java-Umgebung erläutert, indem ein einfacher SSL-Server und ein SSL-Client erstellt werden.

Lassen Sie uns zunächst einen Blick auf die reguläre Java-Socket-Programmierung werfen. Es ist relativ einfach, ein Beispiel für einen Socket-Server und -Client in Java zu schreiben.

Der Server ist sehr einfach: Hören Sie auf Port 8080 und geben Sie den vom Client gesendeten String zurück. Das Folgende ist der Servercode:

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();
        }
    }
}

Der Client ist ebenfalls sehr einfach: Initiieren Sie eine Anfrage an den Server, senden Sie eine „Hallo“-Zeichenfolge und erhalten Sie dann die Antwort vom Server. Das Folgende ist der Code des Clients:

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();
    }
}

Nachdem wir den Server ausgeführt und den Client ausgeführt haben, erhalten wir als Antwort „Hallo“.

Dies ist ein so einfacher Satz Netzwerkkommunikationscode. Lassen Sie uns ihn so umwandeln, dass er SSL-Kommunikation verwendet. Beim SSL-Kommunikationsprotokoll wissen wir alle, dass der Server zunächst über ein digitales Zertifikat verfügen muss. Wenn er eine Verbindung zum Server herstellt, beurteilt der Client, ob das Zertifikat vertrauenswürdig ist Schlüssel zur Kommunikation. Wenn das Zertifikat nicht vertrauenswürdig ist, schlägt die Verbindung fehl.

Daher müssen wir zunächst ein digitales Zertifikat für den Server generieren. In der Java-Umgebung werden digitale Zertifikate mit Keytool generiert und diese Zertifikate im Concept Store, dem Zertifikatslager, gespeichert. Rufen wir den Befehl keytool auf, um ein digitales Zertifikat für den Server zu generieren und das von ihm verwendete Zertifikatslager zu speichern:

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

Auf diese Weise speichern wir das Serverzertifikat bluedash-ssl-demo-server im Speicher server_ksy Datei unter. Die Verwendung von keytool wird in diesem Artikel nicht im Detail beschrieben. Wenn Sie den obigen Befehl ausführen, erhalten Sie die folgenden Ergebnisse:

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]

Dann transformieren Sie unseren Servercode, damit der Server dieses Zertifikat verwenden und SSL-Kommunikation bereitstellen kann:

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();
        }
    }
}

wie Sie sehen können Der Aufwand für die Socket-Vorbereitung und -Einstellung auf der Serverseite wurde erheblich erhöht. Die Funktion des hinzugefügten Codes besteht hauptsächlich darin, das Zertifikat zu importieren und zu verwenden. Darüber hinaus wurde der verwendete Socket zu SSLServerSocket und der Port wurde in 8443 geändert (dies ist nicht zwingend erforderlich, nur um den Gewohnheiten zu entsprechen). Darüber hinaus ist der wichtigste Punkt, dass der CN im Serverzertifikat mit dem Domänennamen des Servers übereinstimmen muss. Der Domänenname unseres Zertifikatsdienstes lautet localhost, daher muss unser Client andernfalls auch localhost verwenden, wenn er eine Verbindung zum Server herstellt Die Verbindung erfolgt über SSL. Wenn der Protokollstandard und der Domänenname nicht mit dem CN des Zertifikats übereinstimmen, bedeutet dies, dass das Zertifikat nicht sicher ist und die Kommunikation nicht ordnungsgemäß funktioniert.

Mit dem Server kann unser ursprünglicher Client nicht verwendet werden und es muss das SSL-Protokoll verwendet werden. Da das Serverzertifikat von uns selbst generiert und nicht von einer vertrauenswürdigen Behörde signiert wird, kann der Client die Gültigkeit des Serverzertifikats nicht überprüfen und die Kommunikation schlägt zwangsläufig fehl. Wir müssen also ein Warehouse für den Client erstellen, um alle Vertrauenszertifikate zu speichern, und dann das Serverzertifikat in dieses Warehouse importieren. Auf diese Weise stellt der Client beim Herstellen einer Verbindung mit dem Server fest, dass sich das Zertifikat des Servers in seiner Vertrauensliste befindet, und kann normal kommunizieren.

Was wir jetzt tun müssen, ist, ein Client-Zertifikat-Warehouse zu generieren. Da Keytool nicht nur ein leeres Warehouse generieren kann, generieren wir, genau wie der Server, immer noch ein Zertifikat plus ein Warehouse (Client-Zertifikat 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

Die Ergebnisse sind wie folgt:

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]

Als nächstes müssen wir das Zertifikat des Servers exportieren und in das Warehouse des Clients importieren. Der erste Schritt besteht darin, das Serverzertifikat zu exportieren:

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

Das Ausführungsergebnis ist wie folgt:

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

Dann importieren Sie das exportierte Zertifikat in das Client-Zertifikat-Warehouse:

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

Die Ergebnisse sind wie folgt:

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

Okay, die Vorbereitungen sind abgeschlossen, schreiben wir den 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;
    }
}

Wie Sie sehen können, zusätzlich zu Einige Klassen in SSL-Kommunikation umwandeln Zusätzlich zur Klasse verfügt der Client auch über zusätzlichen Code für die Verwendung des Vertrauenszertifikatspeichers. Oben haben wir die SSL-Einweg-Handshake-Kommunikation abgeschlossen. Das heißt: Der Client überprüft das Zertifikat des Servers, der Server überprüft jedoch nicht das Zertifikat des Clients.
Das Obige ist der gesamte Prozess des unidirektionalen SSL-Handshakes in einer Java-Umgebung. Da wir die Protokollausgabestufe auf dem Client auf DEBUG:

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

setzen, können wir den gesamten Prozess der SSL-Kommunikation sehen. Diese Protokolle können uns dabei helfen, den Aufbau einer Netzwerkverbindung über das SSL-Protokoll genauer zu verstehen . Der ganze Prozess.
In Kombination mit dem Protokoll werfen wir einen Blick auf den gesamten Prozess der bidirektionalen SSL-Authentifizierung:

Schritt 1: Der Client sendet eine ClientHello-Nachricht, initiiert eine SSL-Verbindungsanforderung und teilt dies mit der Server die SSL-Optionen (Verschlüsselung), die von ihm unterstützte Methode usw.).

*** ClientHello, TLSv1

Schritt 2: Der Server antwortet auf die Anfrage, antwortet auf die ServerHello-Nachricht und bestätigt die SSL-Verschlüsselungsmethode mit dem Client:

*** ServerHello, TLSv1

Schritt 3: Der Server veröffentlicht seinen eigenen öffentlichen Schlüssel für den Client.

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

*** ServerHelloDone

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

*** ClientKeyExchange, RSA PreMasterSecret, TLSv1

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

main, WRITE: TLSv1 Change Cipher Spec, length = 1

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

*** Finished

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

main, READ: TLSv1 Change Cipher Spec, length = 1

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

*** Finished

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

((SSLServerSocket) _socket).setNeedClientAuth(false);

改成:

((SSLServerSocket) _socket).setNeedClientAuth(true);

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

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

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

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;
    }
}

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

*** 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

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

Das obige ist der detaillierte Inhalt vonBeispielcode für die gemeinsame Nutzung von SSL-Kommunikationsprinzipien unter Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn