Maison > Java > javaDidacticiel > le corps du texte

Exemple de partage de code des principes de communication SSL sous Java

黄舟
Libérer: 2017-03-25 10:51:00
original
1928 Les gens l'ont consulté

Il existe déjà de nombreux principes et introductions sur SSL sur Internet. Il existe également de nombreux tutoriels sur l'utilisation de keytool pour générer des certificats sous Java et configurer la communication SSL. Mais si nous ne pouvons pas créer nous-mêmes un serveur SSL et un client SSL, nous ne pourrons peut-être jamais comprendre en profondeur comment la communication SSL est implémentée dans l'environnement Java. La connaissance de divers concepts SSL peut également être limitée dans la mesure où ils peuvent être utilisés. Cet article explique le principe de communication de SSL dans l'environnement Java en construisant un simple serveur SSL et un client SSL.

Tout d’abord, passons en revue la programmation Java Socket standard. Il est relativement simple d'écrire un exemple de serveur et de client Socket en Java.

Le serveur est très simple : écoutez le port 8080 et retournez la string envoyée par le client. Voici le code du serveur :

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();
        }
    }
}
Copier après la connexion

Le client est également très simple : lancez une requête au serveur, envoyez une chaîne "bonjour", puis obtenez la réponse du serveur. Voici le code client :

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();
    }
}
Copier après la connexion

Après avoir exécuté le serveur et exécuté le client, nous obtiendrons un retour "bonjour".

Il s'agit d'un ensemble si simple de code de communication réseau. Transformons-le pour utiliser la communication SSL. Dans le protocole de communication SSL, nous savons tous que le serveur doit d'abord avoir un certificat numérique. Lorsque le client se connecte au serveur, il obtiendra ce certificat. Si tel est le cas, le cryptage du canal d'échange. clé pour communiquer. Si le certificat n'est pas approuvé, la connexion échoue.

Par conséquent, nous devons d’abord générer un certificat numérique pour le serveur. Dans l'environnement Java, les certificats numériques sont générés à l'aide de keytool, et ces certificats sont stockés dans le concept de magasin, qui est l'entrepôt de certificats. Appelons la commande keytool pour générer un certificat numérique pour le serveur et enregistrons l'entrepôt de certificats qu'il utilise :

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
Copier après la connexion

De cette façon, nous enregistrons le certificat du serveur bluedash-ssl-demo-server dans le magasin server_ksy fichier parmi. L'utilisation de keytool ne sera pas décrite en détail dans cet article. L'exécution de la commande ci-dessus obtiendra les résultats suivants :

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]
Copier après la connexion

Ensuite, transformez notre code serveur pour permettre au serveur d'utiliser ce certificat et fournir une communication SSL :

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();
        }
    }
}
Copier après la connexion

comme vous pouvez le voir , le travail de préparation et de configuration du socket côté serveur a été considérablement augmenté. La fonction du code ajouté est principalement d'importer et d'utiliser le certificat. De plus, le Socket utilisé est devenu SSLServerSocket, et le port a été changé en 8443 (ce n'est pas obligatoire, juste pour respecter les usages). De plus, le point le plus important est que le CN dans le certificat du serveur doit être cohérent avec le nom de domaine du serveur. Le nom de domaine de notre service de certificat est localhost, notre client doit donc également utiliser localhost lors de la connexion au serveur, sinon. il sera connecté selon SSL Si le standard du protocole et le nom de domaine ne correspondent pas au CN du certificat, cela signifie que le certificat n'est pas sécurisé et la communication ne fonctionnera pas correctement.

Avec le serveur, notre client d'origine ne peut pas être utilisé, et le protocole SSL doit être utilisé. Étant donné que le certificat du serveur est généré par nous-mêmes et n'est signé par aucune autorité de confiance, le client ne peut pas vérifier la validité du certificat du serveur et la communication échouera inévitablement. Nous devons donc créer un entrepôt pour que le client stocke tous les certificats de confiance, puis importer le certificat du serveur dans cet entrepôt. De cette façon, lorsque le client se connecte au serveur, il constatera que le certificat du serveur est dans sa liste de confiance et il pourra communiquer normalement.

Donc, ce que nous devons faire maintenant, c'est générer un entrepôt de certificats client. Parce que keytool ne peut pas générer simplement un entrepôt vide, donc tout comme le serveur, nous générons toujours un certificat plus un entrepôt (certificat client plus entrepôt) :

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
Copier après la connexion

Les résultats sont les suivants :

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]
Copier après la connexion

Ensuite, nous devons exporter le certificat du serveur et l'importer dans l'entrepôt du client. La première étape consiste à exporter le certificat du serveur :

keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer
Copier après la connexion

Le résultat de l'exécution est le suivant :

Enter keystore password:  server
Certificate stored in file <server_key.cer>
Copier après la connexion

Importez ensuite le certificat exporté dans l'entrepôt de certificats client :

keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks
Copier après la connexion

Les résultats sont les suivants :

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
Copier après la connexion

Bon, les préparatifs sont terminés, écrivons le code client :

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;
    }
}
Copier après la connexion

Comme vous pouvez le voir, en plus de transformer certaines classes en communication SSL En plus de la classe, le client dispose également d'un code supplémentaire pour utiliser le magasin de certificats de confiance. Ci-dessus, nous avons terminé la communication SSL unidirectionnelle. Autrement dit : le client vérifie le certificat du serveur et le serveur ne vérifie pas le certificat du client.
Ce qui précède représente l'ensemble du processus de prise de contact SSL unidirectionnelle dans un environnement Java. Parce que nous définissons le niveau de sortie des journaux sur le client sur DEBUG :

System.setProperty("javax.net.debug", "ssl,handshake");
Copier après la connexion

, nous pouvons voir l'ensemble du processus de communication SSL. Ces journaux peuvent nous aider à comprendre plus spécifiquement lors de l'établissement d'une connexion réseau via le protocole SSL. .L'ensemble du processus.
En combinaison avec le journal, examinons l'ensemble du processus d'authentification bidirectionnelle SSL :

Étape 1 : Le client envoie un message ClientHello, initie une demande de connexion SSL et indique le serveur les options SSL (cryptage) qu'il supporte, etc.).

*** ClientHello, TLSv1
Copier après la connexion

Étape 2 : Le serveur répond à la requête, répond au message ServerHello et confirme la méthode de cryptage SSL avec le client :

*** ServerHello, TLSv1
Copier après la connexion

Étape 3 : Le serveur publie sa propre clé publique client.

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

*** ServerHelloDone
Copier après la connexion

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

*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
Copier après la connexion

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

main, WRITE: TLSv1 Change Cipher Spec, length = 1
Copier après la connexion

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

*** Finished
Copier après la connexion
Copier après la connexion

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

main, READ: TLSv1 Change Cipher Spec, length = 1
Copier après la connexion

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

*** Finished
Copier après la connexion
Copier après la connexion

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

((SSLServerSocket) _socket).setNeedClientAuth(false);
Copier après la connexion

改成:

((SSLServerSocket) _socket).setNeedClientAuth(true);
Copier après la connexion

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

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
Copier après la connexion

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

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;
    }
}
Copier après la connexion

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

*** 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
Copier après la connexion

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!