• 07-HTTPS双向认证及Java案例


    1.双向认证流程

    1. 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务端;
    2. 服务器端将本机的公钥证书(server.crt)发送给客户端;
    3. 客户端读取公钥证书(server.crt),取出了服务端公钥;
    4. 客户端将客户端公钥证书(client.crt)发送给服务器端;
    5. 服务器端使用根证书(root.crt)解密客户端公钥证书,拿到客户端公钥;
    6. 客户端发送自己支持的加密方案给服务器端;
    7. 服务器端根据自己和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密后发送给客户端;
    8. 客户端使用自己的私钥解密加密方案,生成一个随机数R,使用服务器公钥加密后传给服务器端;
    9. 服务端用自己的私钥去解密这个密文,得到了密钥R
    10. 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信了。

    2. 证书准备

    从上一章内容中,我们可以总结出来,整个双向认证的流程需要六个证书文件:

    • 服务器端公钥证书:server.crt
    • 服务器端私钥文件:server.key
    • 根证书:root.crt
    • 客户端公钥证书:client.crt
    • 客户端私钥文件:client.key
    • 客户端集成证书(包括公钥和私钥,用于浏览器访问场景):client.p12

    所有的这些证书,我们都可以向证书机构去申请签发,一般需要收取一定的证书签发费用,此时我们需要选择大型的证书机构去购买。如果只是企业内部使用,不是给公众使用,也可以自行颁发自签名证书

    3. 自签名证书

    3.1 、根证书

    (1)创建根证书私钥

    openssl genrsa -out root.key 1024
    
    • 1

    (2)创建根证书请求文件:

    openssl req -new -out root.csr -key root.key
    
    • 1

    后续参数请自行填写,下面是一个例子:

    Country Name (2 letter code) [XX]:cn
    State or Province Name (full name) []:bj
    Locality Name (eg, city) [Default City]:bj
    Organization Name (eg, company) [Default Company Ltd]:alibaba
    Organizational Unit Name (eg, section) []:test
    Common Name (eg, your name or your servers hostname) []:root
    Email Address []:a.alibaba.com
    A challenge password []:
    An optional company name []:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3)创建根证书

    openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650
    
    • 1

    在创建证书请求文件的时候需要注意三点,下面生成服务器请求文件和客户端请求文件均要注意这三点:

    • 根证书的Common Name填写root就可以,所有客户端和服务器端的证书这个字段需要填写域名,一定要注意的是,根证书的这个字段和客户端证书、服务器端证书不能一样;
    • 其他所有字段的填写,根证书、服务器端证书、客户端证书需保持一致
    • 最后的密码可以直接回车跳过。

    经过上面三个命令行,我们最终可以得到一个签名有效期为10年的根证书root.crt,后面我们可以用这个根证书去颁发服务器证书和客户端证书。

    3.2、 生成自签名服务器端证书

    (1)生成服务器端证书私钥

    openssl genrsa -out server.key 1024
    
    • 1

    (2) 生成服务器证书请求文件,过程和注意事项参考根证书,本节不详述:

    openssl req -new -out server.csr -key server.key
    
    • 1

    (3) 生成服务器端公钥证书

    openssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
    
    • 1

    经过上面的三个命令,我们得到:
    server.key:服务器端的密钥文件 server.crt:有效期十年的服务器端公钥证书,使用根证书和服务器端私钥文件一起生成

    3.3、 生成自签名客户端证书

    (1)生成客户端证书密钥:

    openssl genrsa -out client.key 1024
    openssl genrsa -out client2.key 1024
    
    • 1
    • 2

    (2) 生成客户端证书请求文件,过程和注意事项参考根证书,本节不详述:

    openssl req -new -out client.csr -key client.key
    openssl req -new -out client2.csr -key client2.key
    
    • 1
    • 2

    (3) 生客户端证书

    openssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
    openssl x509 -req -in client2.csr -out client2.crt -signkey client2.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
    
    • 1
    • 2

    (4) 生客户端p12格式证书,需要输入一个密码,选一个好记的,比如123456

    openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
    openssl pkcs12 -export -clcerts -in client2.crt -inkey client2.key -out client2.p12
    
    • 1
    • 2

    重复使用上面的命令,我们得到两套客户端证书:

    • client.key / client2.key:客户端的私钥文件
    • client.crt / client2.key:有效期十年的客户端证书
      使用根证书和客户端私钥一起生成 client.p12/client2.p12,这个证书文件包含客户端的公钥和私钥,主要用来给浏览器访问使用

    4. keytool生成证书

    使用双向认证的SSL/TLS协议通信,客户端和服务器端都要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。
    理论上一共需要准备四个文件,两个keystore文件和两个truststore文件。
    通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。

    keytool -genkey -alias catserver -keyalg rsa -keysize 1024 -sigalg sha256withrsa -keypass huawei -keystore catserver.keystore -storepass huawei
    
    
    • 1
    • 2
    keytool -genkey -alias foxclient -keyalg dsa -keysize 512 -sigalg sha1withdsa -keypass huawei -keystore foxclient.keystore -storepass huawei
    
    • 1
    keytool -export -alias catserver -keystore catserver.keystore -storepass huawei -file catserver.cer
    
    • 1
    keytool -export -alias foxclient -keystore foxclient.keystore -storepass huawei -file foxclient.cer
    
    • 1
    keytool -import -alias foxclient -keystore catservertrust.keystore -storepass huawei -file foxclient.cer
    
    • 1
    keytool -import -alias catserver -keystore foxclienttrust.keystore -storepass huawei -file catserver.cer
    
    • 1

    5. 使用Java调用

    5.1、server代码

    public class CatServer implements Runnable, HandshakeCompletedListener {
    
        public static final int SERVER_PORT = 11123;
    
        private final Socket _s;
        private String peerCerName;
    
        public CatServer(Socket s) {
            _s = s;
        }
    
        public static void main(String[] args) throws Exception {
            String serverKeyStoreFile = "D:\\code\\mycode\\java-study\\https\\src\\main\\resources\\double\\catserver.keystore";
            String serverKeyStorePwd = "huawei";
            String catServerKeyPwd = "huawei";
            String serverTrustKeyStoreFile = "D:\\code\\mycode\\java-study\\https\\src\\main\\resources\\double\\catservertrust.keystore";
            String serverTrustKeyStorePwd = "huawei";
    
            KeyStore serverKeyStore = KeyStore.getInstance("JKS");
            serverKeyStore.load(new FileInputStream(serverKeyStoreFile), serverKeyStorePwd.toCharArray());
    
            KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");
            serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());
    
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(serverKeyStore, catServerKeyPwd.toCharArray());
    
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(serverTrustKeyStore);
    
            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(SERVER_PORT);
            sslServerSocket.setNeedClientAuth(true);
    
            while (true) {
                SSLSocket s = (SSLSocket) sslServerSocket.accept();
                CatServer cs = new CatServer(s);
                s.addHandshakeCompletedListener(cs);
                new Thread(cs).start();
            }
        }
    
        @Override
        public void run() {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(_s.getInputStream()));
                PrintWriter writer = new PrintWriter(_s.getOutputStream(), true);
    
                writer.println("Welcome~, enter exit to leave.");
                String s;
                while ((s = reader.readLine()) != null && !s.trim().equalsIgnoreCase("exit")) {
                    writer.println("Echo: " + s);
                }
                writer.println("Bye~, " + peerCerName);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    _s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void handshakeCompleted(HandshakeCompletedEvent event) {
            try {
                X509Certificate cert = (X509Certificate) event.getPeerCertificates()[0];
                peerCerName = cert.getSubjectX500Principal().getName();
                    } catch (SSLPeerUnverifiedException ex) {
                    ex.printStackTrace();
                    }
                    }
    
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    5.2、client代码

    public class FoxClient {
        public static void main(String[] args) throws Exception {
            String clientKeyStoreFile = "D:\\code\\mycode\\java-study\\https\\src\\main\\resources\\double\\foxclient.keystore";
            String clientKeyStorePwd = "huawei";
            String foxclientKeyPwd = "huawei";
            String clientTrustKeyStoreFile = "D:\\code\\mycode\\java-study\\https\\src\\main\\resources\\double\\foxclienttrust.keystore";
            String clientTrustKeyStorePwd = "huawei";
    
            KeyStore clientKeyStore = KeyStore.getInstance("JKS");
            clientKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray());
    
            KeyStore clientTrustKeyStore = KeyStore.getInstance("JKS");
            clientTrustKeyStore.load(new FileInputStream(clientTrustKeyStoreFile), clientTrustKeyStorePwd.toCharArray());
    
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(clientKeyStore, foxclientKeyPwd.toCharArray());
    
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(clientTrustKeyStore);
    
            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            Socket socket = socketFactory.createSocket("localhost", CatServer.SERVER_PORT);
    
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    
            send("hello", out);
            send("exit", out);
            receive(in);
            socket.close();
        }
    
        public static void send(String s, PrintWriter out) throws IOException {
            System.out.println("Sending: " + s);
            out.println(s);
        }
    
        public static void receive(BufferedReader in) throws IOException {
            String s;
            while ((s = in.readLine()) != null) {
                System.out.println("Reveived: " + s);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    参考文章:

    更多内容关注微信公众号 ”前后端技术精选“,或者语雀,里面有更多知识:https://www.yuque.com/riverzmm/uu60c9?# 《安全》

  • 相关阅读:
    Spring Boot中配置文件生效位置
    【1656. 设计有序流】
    转铁蛋白Tf功能化β-榄香烯-雷公藤红素/紫杉醇PLGA纳米粒/雷公藤甲素脂质体(化学试剂)
    字符串笔记-字符串哈希
    c++ map/multimap
    《Effective STL》读书笔记(四):迭代器
    ZCMU--1379: The Black Hole of Numbers(C语言)
    20220929-ArrayList扩容机制源码分析
    《计算机视觉中的多视图几何》笔记(0)
    14:第二章:架构后端项目:10:封装“返回结果”;(也就是定义API统一返回对象)(同时,使用枚举类统一管理错误信息)
  • 原文地址:https://blog.csdn.net/weixin_40304387/article/details/127969463