• java认证与证书


    本文内容主要来自《Java加密与解密的艺术》

    目前主要有JKS和PEM两种编码格式文件。

    • JKS(Java Key Store),Java原生的密钥库/信任库文件。
    • **PEM(Privacy Enbanced Mail,隐私增强邮件)**是使用多种加密方法提供机密性、认证和信息完整性的因特网电子邮件,在因特网中却没有被广泛配置,但在OpenSSL中,却是最为常见的密钥库文件。

    如何在这两种密钥库文件中进行库文件交换呢?

    可以通过PKCS#12格式的证书文件在两种格式的密钥库中进行库文件导出/导入等。

    通常使用Base64编码格式作为数字证书文件存储格式。

    自签名证书

    自签名证书,即证书申请者为自己的证书签名。

    这类证书通常应用于软件厂商内部发放的产品中,或约定使用该证书的数据交互双方。数字证书完全充当加密算法的载体,为必要数据做加密/解密和签名/验证等操作。

    证书签发

    数字证书的颁发流程简述过程如下:

    1. 数字证书需求方产生自己的密钥对。
    2. 数字证书需求方将算法、公钥和证书申请者身份信息传送给认证机构
    3. 认证机构核实用户的身份,执行相应必要的步骤,确保请求确实由用户发送而来。
    4. 认证机构将数字证书颁发给用户。

    加密交互

    1、客户端请求服务器的流程如下:

    客户端请求服务器将按如下步骤进行:

    1. 客户端使用公钥对数据加密。
    2. 客户端向服务器端发送加密数据。
    3. 服务器端使用私钥对数据解密。

    2、服务器端完成客户端请求处理后,需经过以下几个步骤完成响应:

    1. 服务器端使用私钥对待加密数据签名。
    2. 服务器端使用私钥对数据加密。
    3. 由服务器客户端回应加密数据和数字签名。
    4. 客户端使用公钥对数据解密。
    5. 客户端使用公钥和解密数据验证签名。

    KeyTool证书管理

    KeyTool是Java中的数字证书管理工具,用于数字证书的申请、导入、导出和撤销等证书管理操作。

    官方文档https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html

    KeyTool与本地密钥库相关联,将私钥存于密钥库,公钥则以数字证书输出。KeyTool位于%JAVA_HOME%\bin目录中,需要通过命令行进行相应的操作。

    1、构建自签名证书

    前面说了证书签发的流程,一般都是需要把公钥等相关信息发送给认证机构,认证机构颁发证书给我们。

    如果我们自己给自己颁发证书这种,就被成为自签名证书。

    配置了jdk环境变量后就能直接在命令行输入keytool查看具体的命令,如果我们想看具体命令,输入相关命令后面加--help就可以了,如keytool -genkeypair --help

    密钥和证书管理工具
    
    命令:
    
     -certreq            生成证书请求
     -changealias        更改条目的别名
     -delete             删除条目
     -exportcert         导出证书
     -genkeypair         生成密钥对
     -genseckey          生成密钥
     -gencert            根据证书请求生成证书
     -importcert         导入证书或证书链
     -importpass         导入口令
     -importkeystore     从其他密钥库导入一个或所有条目
     -keypasswd          更改条目的密钥口令
     -list               列出密钥库中的条目
     -printcert          打印证书内容
     -printcertreq       打印证书请求的内容
     -printcrl           打印 CRL 文件的内容
     -storepasswd        更改密钥库的存储口令
    
    使用 "keytool -?, -h, or --help" 可输出此帮助消息
    使用 "keytool -command_name --help" 可获取 command_name 的用法。
    使用 -conf <url> 选项可指定预配置的选项文件。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在构建证书前,需要生成密钥对,也就是基于某一种非对称加密算法的公私钥。

    输入keytool -genkeypair --help就能看到genkeypair命令的相关参数。

    keytool -genkeypair [OPTION]...
    
    生成密钥对
    
    选项:
    
     -alias <alias>          要处理的条目的别名
     -keyalg <alg>           密钥算法名称
     -keysize <size>         密钥位大小
     -groupname <name>       Group name. For example, an Elliptic Curve name.
     -sigalg <alg>           签名算法名称
     -destalias <alias>      目标别名
     -dname <name>           唯一判别名
     -startdate <date>       证书有效期开始日期/时间
     -ext <value>            X.509 扩展
     -validity <days>        有效天数
     -keypass <arg>          密钥口令
     -keystore <keystore>    密钥库名称
     -storepass <arg>        密钥库口令
     -storetype <type>       密钥库类型
     -providername <name>    提供方名称
     -addprovider <name>     按名称 (例如 SunPKCS11) 添加安全提供方
       [-providerarg <arg>]    配置 -addprovider 的参数
     -providerclass <class>  按全限定类名添加安全提供方
       [-providerarg <arg>]    配置 -providerclass 的参数
     -providerpath <list>    提供方类路径
     -v                      详细输出
     -protected              通过受保护的机制的口令
    
    使用 "keytool -?, -h, or --help" 可输出此帮助消息
    
    • 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

    这里我们使用www.abc.org作为别名,使用RSA作为加密算法,并规定密钥长度为2048位,使用SHA256withRSA作为数字签名算法,欲签发有效期为36000天的数字证书。

    完整命令如下:

    keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 36000 -alias www.abc.org -keystore abc.p12 -storepass 123456 -dname "CN=www.abc.org,OU=a,O=a,L=BJ,ST=BJ,C=CN"
    
    • 1

    -genkeypair 生成密钥对。

    -keyalg 指定密钥算法,这里指定为RSA算法。

    -keysize 指定密钥长度,默认1024位,这里指定为2048位.

    -sigalg 指定数字签名算法,这里指定为SHA256withRSA算法。

    -validity 指定证书有效期,这里指定为36000天。

    -alias 指定别名,这里是www.abc.org。

    -keystore 指定密钥库存储位置,这里是abc.keystore.


    KeyTool工具支持RSA和DSA共2种算法,且DSA算法为默认算法。

    -storepass 参数可以不在命令行输入,回车后就会提示要求输入

    -dname参数也可以不在命令行输入,回车后就会提示要求输入

    这时就会在当前目录下生成一个abc.p12,这就是创建的数字证书。虽然这时的数字证书并没有经过CA认证,但并不影响我们使用。

    下来可以将数字证书导出,发送给需要通信的对方进行加密交互。KeyTool通过-exportcert命令导出证书。

    keytool -exportcert [OPTION]...
    
    导出证书
    
    选项:
    
     -rfc                    以 RFC 样式输出
     -alias <alias>          要处理的条目的别名
     -file <file>            输出文件名
     -keystore <keystore>    密钥库名称
     -cacerts                访问 cacerts 密钥库
     -storepass <arg>        密钥库口令
     -storetype <type>       密钥库类型
     -providername <name>    提供方名称
     -addprovider <name>     按名称 (例如 SunPKCS11) 添加安全提供方
       [-providerarg <arg>]    配置 -addprovider 的参数
     -providerclass <class>  按全限定类名添加安全提供方
       [-providerarg <arg>]    配置 -providerclass 的参数
     -providerpath <list>    提供方类路径
     -v                      详细输出
     -protected              通过受保护的机制的口令
    
    使用 "keytool -?, -h, or --help" 可输出此帮助消息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    完整的导出命令如下:

    keytool -exportcert -alias www.abc.org -keystore abc.p12 -file abc.cer -rfc  -storepass 123456
    
    • 1

    -exportcert 证书导出操作。

    -alias 指定导别名,这里为www.abc.org。

    -keystore 指定密钥库文件,这里为abc.keystore

    -file 指定导出文件路径,这里为abc.cer

    -rfc 指定以Base64编码格式输出

    -storepass 密钥库口令

    这时就会在当前目录下生成abc.cer

    我们可以使用keytool -printcert -file abc.cer命令查看abc.cer文件的内容。

    我们可以使用keytool -printcert -file abc.cer -rfc 已文本形式查看abc.cer文件的内容。

    我们是用notepad++之类的文本工具也可以打开abc.cer。会发现它和上面rfc参数显示的内容是一样的。

    我们可以使用keytool -list -alias www.abc.org -keystore abc.p12 -v命令查看abc.p12文件的内容。

    -alias www.abc.org指定了要看www.abc.org证书的信息。去掉这个参数,会显示证书中所有的条目。

    abc.p12这种数字证书中是可以存储多个证书条目

    通过KeyTool工具直接导出的证书,是一个自签名的X.509第三版类型的根证书,并以Base64编码保存。

    自签名证书虽然可以使用,但未经过CA机构认证,几乎没有任何法律效力,也毫无安全可言。

    abc.p12这个包含私钥和公钥。abc.cer只包含公钥。

    echo PFX证书导入JKS密钥库
    keytool -importkeystore -v -srckeystore   client.p12 -srcstoretype pkcs12 - srcstorepass 123456 -destkeystore client.keystore -deststoretype jks - deststorepass 123456
    #-importkeystore     导入密钥库,通过格式设定可以将PKCS#12文件转换为JKS格式。
    #-v                  显示详情。
    #-srckeystore        源密钥库,这里是d:\zlex.pfx。
    #-srcstoretype       源密钥库格式,这里为pkcs12。
    #-srcstorepass       源密钥库密码,这里为123456。
    #-destkeystore       目标密钥库,这里为d:\zlex.keystore。
    #-deststoretype      目标密钥库格式,这里为JKS,默认值也如此。
    #-deststorepass      目标密钥库密码,这里为123456。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2、springboot中使用证书

    2.1 在springboot的配置文件中添加如下内容:

    server.ssl.enabled=true
    #NONE?WANT?NEED
    server.ssl.client-auth=NONE
    #server.ssl.protocol=TLS
    server.ssl.key-store=classpath:certs/abc.p12
    #server.ssl.key-password=123456
    server.ssl.key-store-password=123456
    server.ssl.key-store-type=PKCS12
    server.ssl.keyAlias=www.abc.org
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 将上面生成的abc.p12放置到resources/certs/abc.p12这个位置。

    启动工程,再通过https浏览器访问url就可以了。同时我们也可以通过浏览器导出证书。我们对比浏览器导出的证书和我们之前通过命令行导出的abc.cer,会发现两者的内容是一摸一样的。

    3、在java代码是如何使用这些证书的:
    package org.example;
    
    import java.io.FileInputStream;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.Signature;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import javax.crypto.Cipher;
    /**
     * 证书组件
     * @author 梁栋
     * @version 1.0
     */
    public abstract class CertificateCoder {
        // 类型证书X.509
        public static final String CERT_TYPE = "X.509";
        /**
         * 由KeyStore获得私钥
         * @param keyStorePath 密钥库路径
         * @param alias 别名
         * @param password 密码
         * @return PrivateKey 私钥
         * @throws Exception
         */
        private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath,
        String alias, String password) throws Exception {
               // 获得密钥库
               KeyStore ks = getKeyStore(keyStorePath, password);
               // 获得私钥
               return (PrivateKey) ks.getKey(alias, password.toCharArray());
        }
        /**
         * 由Certificate获得公钥
         * @param certificatePath 证书路径
         * @return PublicKey 公钥
         * @throws Exception
         */
        private static PublicKey getPublicKeyByCertificate(String certificatePath)
        throws Exception {
               // 获得证书
               Certificate certificate = getCertificate(certificatePath);
               // 获得公钥
               return certificate.getPublicKey();
        }
        /**
         * 获得Certificate
         * @param certificatePath 证书路径
         * @return Certificate 证书
         * @throws Exception
         */
        private static Certificate getCertificate(String certificatePath) throws Exception {
               // 实例化证书工厂
               CertificateFactory certificateFactory = CertificateFactory.getInstance(CERT_TYPE);
               // 取得证书文件流
               FileInputStream in = new FileInputStream(certificatePath);
               // 生成证书
               Certificate certificate = certificateFactory.generateCertificate(in);
               // 关闭证书文件流
               in.close();
               return certificate;
        }
        /**
         * 获得Certificate
         * @param keyStorePath 密钥库路径
         * @param alias 别名
         * @param password 密码
         * @return Certificate 证书
         * @throws Exception
         */
        private static Certificate getCertificate(String keyStorePath, String alias,
        String password) throws Exception {
               // 获得密钥库
               KeyStore ks = getKeyStore(keyStorePath, password);
               // 获得证书
               return ks.getCertificate(alias);
        }
        /**
         * 获得KeyStore
         * @param keyStorePath 密钥库路径
         * @param password 密码
         * @return KeyStore 密钥库
         * @throws Exception
         */
        private static KeyStore getKeyStore(String keyStorePath, String password)
        throws Exception {
               // 实例化密钥库
               KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
               // 获得密钥库文件流
               FileInputStream is = new FileInputStream(keyStorePath);
               // 加载密钥库
               ks.load(is, password.toCharArray());
               // 关闭密钥库文件流
               is.close();
               return ks;
        }
        /**
         * 私钥加密
         * @param data 待加密数据
         * @param keyStorePath 密钥库路径
         * @param alias 别名
         * @param password 密码
         * @return byte[] 加密数据
         * @throws Exception
         */
        public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
        String alias, String password) throws Exception {
               // 取得私钥
               PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
               // 对数据加密
               Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
               cipher.init(Cipher.ENCRYPT_MODE, privateKey);
               return cipher.doFinal(data);
        }
        /**
         * 私钥解密
         * @param data 待解密数据
         * @param keyStorePath 密钥库路径
         * @param alias 别名
         * @param password 密码
         * @return byte[] 解密数据
         * @throws Exception
         */
        public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
        String alias, String password) throws Exception {
               // 取得私钥
               PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
               // 对数据加密
               Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
               cipher.init(Cipher.DECRYPT_MODE, privateKey);
               return cipher.doFinal(data);
        }
        /**
         * 公钥加密
         * @param data 待加密数据
         * @param certificatePath 证书路径
         * @return byte[] 加密数据
         * @throws Exception
         */
        public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
        throws Exception {
               // 取得公钥
               PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
               // 对数据加密
               Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
               cipher.init(Cipher.ENCRYPT_MODE, publicKey);
               return cipher.doFinal(data);
        }
        /**
         * 公钥解密
         * @param data 待解密数据
         * @param certificatePath 证书路径
         * @return byte[] 解密数据
         * @throws Exception
         */
        public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
        throws Exception {
               // 取得公钥
               PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
               // 对数据加密
               Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
               cipher.init(Cipher.DECRYPT_MODE, publicKey);
               return cipher.doFinal(data);
        }
        /**
         * 签名
         * @param keyStorePath 密钥库路径
         * @param alias 别名
         * @param password 密码
         * @return byte[] 签名
         * @throws Exception
         */
        public static byte[] sign(byte[] sign, String keyStorePath, String alias,
        String password) throws Exception {
               // 获得证书
               X509Certificate x509Certificate = (X509Certificate) getCertificate
               (keyStorePath, alias, password);
               // 构建签名,由证书指定签名算法
               Signature signature = Signature.getInstance (x509Certificate.getSigAlgName());
               // 获取私钥
               PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
               // 初始化签名,由私钥构建
               signature.initSign(privateKey);
               signature.update(sign);
               return signature.sign();
        }
        /**
         * 验证签名
         * @param data 数据
         * @param sign 签名
         * @param certificatePath 证书路径
         * @return boolean 验证通过为真
         * @throws Exception
         */
        public static boolean verify(byte[] data, byte[] sign, String certificatePath)
        throws Exception {
               // 获得证书
               X509Certificate x509Certificate = (X509Certificate)getCertificate(certificatePath);
               // 由证书构建签名
               Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
               // 由证书初始化签名,实际上是使用了证书中的公钥
               signature.initVerify(x509Certificate);
               signature.update(data);
               return signature.verify(sign);
        }
    }
    
    • 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
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208

    测试代码:

    import static org.junit.Assert.*;
    import org.apache.commons.codec.binary.Hex;
    import org.example.CertificateCoder;
    import org.testng.annotations.Test;
    /**
     * 证书校验
     * @author 梁栋
     * @version 1.0
     */
    public class CertificateCoderTest {
        private String password = "123456";
        private String alias = "www.abc.org";
        private String certificatePath = "D:\\tmp\\a\\abc.cer";  //证书位置
        private String keyStorePath = "D:\\tmp\\a\\abc.p12";     //证书位置
        /**
         * 公钥加密—私钥解密
         * @throws Exception
         */
        @Test
        public void test1() throws Exception {
               System.err.println("公钥加密—私钥解密");
               String inputStr = "数字证书";
               byte[] data = inputStr.getBytes();
               // 公钥加密
               byte[] encrypt = CertificateCoder.encryptByPublicKey(data, certificatePath);
               // 私钥解密
               byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt, keyStorePath,
               alias, password);
               String outputStr = new String(decrypt);
               System.err.println("加密前:\n" + inputStr);
               System.err.println("解密后:\n" + outputStr);
               // 验证数据一致
               assertArrayEquals(data, decrypt);
        }
        /**
         * 私钥加密—公钥解密
         * @throws Exception
         */
        @Test
        public void test2() throws Exception {
               System.err.println("私钥加密—公钥解密");
               String inputStr = "数字签名";
               byte[] data = inputStr.getBytes();
               // 私钥加密
               byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
               keyStorePath, alias, password);
               // 公钥加密
               byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
               certificatePath);
               String outputStr = new String(decodedData);
               System.err.println("加密前:\n" + inputStr);
               System.err.println("解密后:\n" + outputStr);
               assertEquals(inputStr, outputStr);
        }
        /**
         * 签名验证
         * @throws Exception
         */
        @Test
        public void testSign() throws Exception {
               String inputStr = "签名";
               byte[] data = inputStr.getBytes();
               System.err.println("私钥签名—公钥验证");
               // 产生签名
               byte[] sign = CertificateCoder.sign(data, keyStorePath, alias, password);
               System.err.println("签名:\n" + Hex.encodeHexString(sign));
               // 验证签名
               boolean status = CertificateCoder.verify(data, sign, certificatePath);
               System.err.println("状态:\n" + status);
               // 校验
               assertTrue(status);
        }
    }
    
    • 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

    2、双向验证

    一般来说只有客户端会验证服务端的证书,服务端不会验证客户端。但是一些特殊场景,安全级别要求高的地方,也会验证客户端。比如我们早些年网银用的U盾,就是将客户端证书置于U中,登录网银的时候,服务端也会验证我们客户端的证书。

    单向验证和双向验证的机制都是一样的。我们简单就客户端侧验证描述下:

    在这里插入图片描述

    1 首先服务端的证书一般都是经过CA机构使用私钥加密过的。

    2.浏览器接收服务端的证书后,会查找对应的签发机构的公钥:

    2.1 如果对应签发机构的证书在可信列表中,就会直接拿来验证服务器的证书。

    2.2 如果对应签发机构的证书不在可信列表中。如果签发机构的证书是自签名证书,这时就可以直接判断服务器证书是不可信证书,直接结束;如果不是自签名证书,继续向上虚招签发机构证书的签发机构证书,继续验证。

    如果我们使用springboot来验证客户端证书,就需要先在客户端也生成一套自签名证书,把它进行安装,同时将它的公钥证书作为可信证书添加 到springboot的可信证书列表中。

    keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 36000 -alias www.client.org -keystore client.p12 -storepass 123456 -dname "CN=www.client.org,OU=a,O=a,L=BJ,ST=BJ,C=CN"
    keytool -exportcert -keystore client.p12 -alias  www.client.org   -file client.crt  -storepass 123456
    
    • 1
    • 2

    我们使用上面两行命令生成客户端的证书,然后进行安装。

    如果没有安装证书,打开client.crt就会看到证书是不可信的。

    在这里插入图片描述

    安装client.p12client.crt后,再打开就会看到的,表明当前证书已经在可信列表。

    在这里插入图片描述

    现在我们把客户端证书导入一个可信列表。

    keytool -importcert -trustcacerts -alias  www.client.org -file client.crt -keystore abc.p12  -storepass 123456
    
    • 1

    -importcert 导入数字证书。

    -trustcacerts 将数字证书导入信任库。

    -alias 指定导别名,这里为www.abc.org。

    -file 指定导入数字证书文件路径,这里为abc.cer

    -keystore 指定密钥库文件,这里为abc.p12

    我这里是导入了之前的abc.p12,可以选择不同的文件导入,如果文件不存在,就会新创建一个文件。

    使用keytool -list -alias www.client.org -keystore abc.p12命令就会看到它是一个trustedCertEntry条目。

    在这里插入图片描述

    修改我们springboot配置文件如下:

    # ??ssl
    server.ssl.enabled=true
    #NONE?WANT?NEED
    server.ssl.client-auth=NEED
    #server.ssl.protocol=TLS
    server.ssl.key-store=classpath:certs/abc.p12
    #server.ssl.key-password=123456
    server.ssl.key-store-password=123456
    server.ssl.key-store-type=PKCS12
    server.ssl.keyAlias=www.abc.org
    
    server.ssl.trust-store=classpath:certs/abc.p12
    server.ssl.trust-store-password=123456
    server.ssl.trust-store-type=JKS
    server.ssl.trust-store-provider=SUN
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    重新拷贝abc.p12resources/certs/abc.p12,启动服务端。

    再次通过浏览器访问就会看到浏览器提示服务端要验证客户端证书。

    注意,需要先安装客户端证书client.p12client.crt

    在这里插入图片描述

    keytool -importcert -trustcacerts -alias www.abc.org -file abc.crt -keystore client.p12 -storepass 123456
    如果是工具去操作,还需要将服务端证书添加到客户端的可信列表中去。

    OPENSSL

    1、构建证书链
     
    echo 构建已发行证书存放目录 certs
    mkdir certs
    
    echo 构建新证书存放目录 newcerts
    mkdir newcerts
    
    echo 构建私钥存放目录 private
    mkdir private
    
    echo 构建证书吊销列表存放目录 crl
    mkdir crl
    
    echo 构建索引文件 index.txt
    echo 0>index.txt
    
    echo 构建序列号文件 serial
    echo 01>serial
    
    echo 构建随机数 private/.rand
    openssl rand -out private/.rand 1000
    #rand         随机数命令。
    #-out         输出文件路径,这里将随机数文件输出到private目录下。
    
    
    echo 构建根证书私钥 private/ca.key.pem
    openssl genrsa  -out private/ca.key.pem 2048
    # -aes256  参数会对生成的私钥进行加密,如果需要密码,就可以使用这个参数 
    #openssl genrsa   -aes256 -out private/ca.key.pem 2048
    #genrsa         产生RSA密钥命令。
    #-aes256        使用AES算法(256位密钥)对产生的私钥加密。可选算法包括DES、DESede、IDEA和AES。
    #-out           输出路径,这里指private/ca.key.pem。
    
    
    echo 生成根证书签发申请 private/ca.csr
    openssl req -new -key private/ca.key.pem -out private/ca.csr -subj   "/C=CN/ST=BJ/L=BJ/O=abc/OU=abc/CN=*.abc.org"
    #req     产生证书签发申请命令。
    #-new    表示新请求。
    #-key    密钥,这里为private/ca.key.pem文件。
    #-out    输出路径,这里为private/ca.csr文件。
    #-subj   指定用户信息,这里使用泛域名“*.abc.org”作为用户名。
    
    
    echo 签发根证书 private/ca.cer
    openssl   x509   -req   -days   10000   -sha1   -extensions   v3_ca   -signkey  private/ca.key.pem -in private/ca.csr -out certs/ca.cer
    #x509         签发X.509格式证书命令。
    #-req         证书输入请求。
    #-days        有效天数,这里为10000天。
    #-sha1        证书摘要算法,这里为SHA1算法。
    #-extensions  按OpenSSL配置文件v3_ca项添加扩展。
    #-signkey     自签名密钥,这里为private/ca.key.pem。
    #-in          输入文件,这里为private/ca.csr。
    #-out         输出文件,这里为certs/ca.cer。
    
    
    
    
    echo 构建服务器私钥 private/server.key.pem
    openssl genrsa -out private/server.key.pem 2048
    #参数上面已经有过介绍
    
    echo 生成服务器证书签发申请 private/server.csr
    openssl req -new -key private/server.key.pem -out private/server.csr -subj "/C=CN/ST=BJ/L=BJ/O=abc/OU=abc/CN=www.abc.org"
    #参数上面已经有过介绍
    
    echo 签发服务器证书 private/server.cer
    openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey   private/ca.key.pem -CAserial ca.srl -CAcreateserial -in private/server.csr -out   certs/server.cer
    #x509             签发X.509格式证书命令。
    #-req             证书输入请求。
    #-days            有效天数,这里为3650天。
    #-sha1            证书摘要算法,这里为SHA1算法。
    #-extensions      按OpenSSL配置文件v3_req项添加扩展。
    #-CA              CA证书,这里为certs/ca.cer。
    #-CAkey           CA证书密钥,这里为private/ca.key.pem。
    #-CAserial        CA证书序列号文件,这里为ca.srl。
    #-CAcreateserial  创建CA证书序列号。
    #-in              输入文件,这里为private/server.csr。
    #-out             输出文件,这里为certs/server.cer。
    
    
    echo 产生客户私钥 private/client.key.pem
    openssl genrsa  -out private/client.key.pem 2048
    #参数上面已经有过介绍
    
    
    echo 生成客户证书签发申请 client.csr
    openssl req -new -key private/client.key.pem -out private/client.csr -subj  "/C=CN/ST=BJ/L=BJ/O=abc/OU=abc/CN=client"
    #参数上面已经有过介绍
    
    
    echo 签发客户证书 certs/client.cer
    openssl ca -days 3650 -in private/client.csr -out certs/client.cer -cert  certs/ca.cer -keyfile private/ca.key.pem
    #ca          签发证书命令。
    #-days       证书有效期,这里为3650天。
    #-in         输入文件,这里为private/client.csr。
    #-out        输出文件,这里为certs/server.cer。
    #-cert       证书文件,这里为certs/ca.cer。
    #-keyfile    根证书密钥文件,这里为private/ca.key.pem。
    
    
    • 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
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    执行完上面的命令会在当前目录下生成如下文件

    在这里插入图片描述

    下面我们分别转化成PKCS#12编码格式

    echo 根证书转换 private/ca.p12
    openssl pkcs12 -export -cacerts -inkey private/ca.key.pem -in certs/ca.cer -out certs/ca.p12
    #pkcs12     PKCS#12编码格式证书命令。
    #-export    导出证书。
    #-cacerts   仅导出CA证书。
    #-inkey     输入密钥,这里为private/ca.key.pem。
    #-in        输入文件,这里为certs/ca.cer。
    #-out       输出文件,这里为certs/ca.p12。
    
    #keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456 如果你的密码设置的是123456,使用这个命令就能查看内容  
    
    
    
    echo 服务器证书转换 private/server.p12
    openssl   pkcs12   -export   -clcerts   -inkey   private/server.key.pem   -in  certs/server.cer -out certs/server.p12
    #pkcs12       PKCS#12编码格式证书命令。
    #-export      导出证书。
    #-clcerts     仅导出客户证书。
    #-inkey       输入密钥文件路径,这里为private/server.key.pem。
    #-in          输入文件路径,这里为certs/ca.cer。
    #-out         输出文件路径,这里为certs/server.p12。
    
    
    
    echo 客户证书转换 certs/client.p12
    openssl pkcs12 -export -inkey  private/client.key.pem -in certs/client.cer -out  certs/client.p12
    
    
    • 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
    2、下面我们使用nginx双向验证演示下。

    放开nginx配置文件nginx.conf中关于https的部分,并添加如下图证书相关配置。

    在这里插入图片描述

    重启nginx。这时,由于我们客户端没有对应证书,浏览器访问会报如下错误。

    在这里插入图片描述

    这时,在客户端安装ca.cer,client.p12两个证书,这时客户端就可以正常访问。

  • 相关阅读:
    聊一聊HTTPS双向认证的简单应用
    记一次edu站点并拿下的过程cnvd
    排序(二分法查找、冒泡排序、选择排序、插入排序以及快速排序)
    设计原则之【里式替换原则】
    关于react与vue的一些对比
    [PAT练级笔记] 69 Basic Level 1069 微博转发抽奖
    若依ruoyi系统报错:mybatis-plus报错,获取用户信息异常解决方案(借助ruoyi作为后台管理系统出现的bug)
    工业互联与MQTT
    毕业设计之基于Vue的数据可视化平台
    数据结构与算法介绍与学习路线
  • 原文地址:https://blog.csdn.net/wbo112/article/details/128178272