美文网首页安全
终极武器——数字证书

终极武器——数字证书

作者: DreamsonMa | 来源:发表于2019-03-10 11:28 被阅读0次

    数字证书也称电子证书,由数字证书颁发认证机构(CA)签发才具备可认证性。数字证书采用了公钥基础设施(PKI),使用了相应的加密算法确保网络应用安全性:

    • 非对称加密算法用于对数据进行加密/解密操作,确保数据的机密性。
    • 数字签名算法用于数据进行签名/验证操作,确保数据的完整性和抗否性。
    • 消息摘要算法用于对数字证书本身做摘要处理,确保数字证书完整性。

    数字证书常用算法

    1、非对称加密算法:RSADSA(无法完成加密/解密实现,这样的数字证书不包括数据加密/解密功能)
    2、签名算法:SHA1withRSAsha1RSA
    3、消息摘要算法:SHA1

    数字证书文件编码格式

    1、CER(规范编码格式),是BER(基本编码格式)的一个变种,使用变长模式。
    2、DER(卓越编码格式),也是BER的一个变种,并使用定长模式。

    公钥基础设施(PKI)

    所有证书都符合PKI制定的X.509标准,如:PKCS(公钥加密标准)
    PKCS常用标准如下:

    公钥加密标准 描述信息 文件名后缀
    PKCS#7 密码消息语法标准 .p7b、.p7c、.spc
    PKCS#10 证书请求语法标准 .p10、.csr
    PKCS#12 个人信息交换语法标准 .p12、.pfx

    以上标准主要用于证书的申请和更新等操作。例如:PKCS#10文件用于证书签发申请,PKCS#12文件可作为JAVA中的密钥库或信任库直接而使用。

    数字证书模型

    1、数字证书颁发流程


    数字证书颁发流程

    2、数字证书服务请求与响应

    数字证书服务请求与响应

    数字证书管理

    KeyTool证书管理

    KeyTool是JAVA中的数字证书管理工具,用于数字证书的申请、导入、导出和撤销等操作。KeyTool与本地密钥库相关联,将私钥存于密钥库,公钥则以数字证书输出。

    1、构建自签名证书

    下面演示先生成一个自签名证书,然后导出数字证书,最后打印数字证书。使用keytool工具导出的证书,是一个自签名的X.509第三版类型根证书,并以Base64编码保存。但是,没有经过CA机构认证,几乎不具备任何法律效力。

    详细参数说明:

    生成本地数字证书

    D:\MyData\majx2>keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.crazyxing.com -keystore crazyxing.keystore -dname "CN=www.crazyxing.com,OU=crazyxing,O=crazyxing,L=GZ,ST=GD,C=CN"
    输入密钥库口令:
    再次输入新口令:
    输入 <www.crazyxing.com> 的密钥口令
            (如果和密钥库口令相同, 按回车):
    
    Warning:
    JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
    
    命令参数 命令说明
    -genkeypair 表示生成密钥
    -keyalg 指定密钥算法,这里制定RSA
    -keysize 指定密钥长度,默认1024,这里制定2048
    -sigalg 指定数字签名算法,这里指定SHA1withRSA算法
    -validity 指定证书有效期,这里指定为36000天
    -alias 指定别名,这里www.crazyxing.com
    -keystore 指定密钥库存储位置,这里是crazyxing.keystore
    -storepass 指定密码
    -dname 值得你个用户信息

    导出数字证书

    D:\MyData\majx2>keytool -exportcert -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.cer -rfc
    输入密钥库口令:
    存储在文件 <crazyxing.cer> 中的证书
    
    Warning:
    JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
    
    D:\MyData\majx2>keytool -printcert -file crazyxing.cer
    所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    发布者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    序列号: 1b2162b5
    有效期为 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
    证书指纹:
             MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
             SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
             SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
    签名算法名称: SHA1withRSA
    主体公共密钥算法: 2048 位 RSA 密钥
    版本: 3
    
    扩展:
    
    #1: ObjectId: 2.5.29.14 Criticality=false
    SubjectKeyIdentifier [
    KeyIdentifier [
    0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
    0010: 38 48 96 20                                        8H.
    ]
    ]
    
    命令参数 命令说明
    -exportcert 表示证书导出操作
    -alias 指定别名,这里为www.crazyxing.com
    -keystore 指定密钥库文件,这里crazyxing.keystore
    -file 指定导出文件路径,这里为crazyxing.cer
    -rfc 指定为Base64编码格式输出
    -storepass 指定密码

    迁移到行业标准格式 PKCS12

    D:\MyData\majx2>keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12
    输入源密钥库口令:
    已成功导入别名 www.crazyxing.com 的条目。
    已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消
    
    Warning:
    已将 "crazyxing.keystore" 迁移到 Non JKS/JCEKS。将 JKS 密钥库作为 "crazyxing.keystore.old" 进行了备份。
    
    D:\MyData\majx2>keytool -exportcert -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.p12 -rfc
    输入密钥库口令:
    存储在文件 <crazyxing.p12> 中的证书
    
    D:\MyData\majx2>keytool -printcert -file crazyxing.p12
    所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    发布者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    序列号: 1b2162b5
    有效期为 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
    证书指纹:
             MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
             SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
             SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
    签名算法名称: SHA1withRSA
    主体公共密钥算法: 2048 位 RSA 密钥
    版本: 3
    
    扩展:
    
    #1: ObjectId: 2.5.29.14 Criticality=false
    SubjectKeyIdentifier [
    KeyIdentifier [
    0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
    0010: 38 48 96 20                                        8H.
    ]
    ]
    

    2、构建CA签发证书

    获取CA机构认证的数字证书,需要将数字证书签发申请(CSR)导出,经由CA机构认证并颁发,同时将认证后的证书导入本地钥匙库和信任库。

    导出数字证书签发申请

    D:\MyData\majx2>keytool -certreq -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.pfx -v
    输入密钥库口令:
    存储在文件 <crazyxing.pfx> 中的认证请求
    将此提交给您的 CA
    
    命令参数 命令说明
    -certreq 表示数字证书申请操作
    -alias 指定别名,这里为www.crazyxing.com
    -keystore 指定密钥库文件,这里crazyxing.keystore
    -flie 指定导出文件路径,这里crazyxing.pfx(文件后缀,参考PKCS常用标准)
    -v 详细信息
    -storepass 指定密码

    导入数字证书

    D:\MyData\majx2>keytool -delete -alias www.crazyxing.com -keystore crazyxing.keystore
    输入密钥库口令:
    
    D:\MyData\majx2>keytool -importcert -trustcacerts -alias www.crazyxing.com -file crazyxing.p12 -keystore crazyxing.keystore
    输入密钥库口令:
    所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    发布者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    序列号: 1b2162b5
    有效期为 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
    证书指纹:
             MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
             SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
             SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
    签名算法名称: SHA1withRSA
    主体公共密钥算法: 2048 位 RSA 密钥
    版本: 3
    
    扩展:
    
    #1: ObjectId: 2.5.29.14 Criticality=false
    SubjectKeyIdentifier [
    KeyIdentifier [
    0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
    0010: 38 48 96 20                                        8H.
    ]
    ]
    
    是否信任此证书? [否]:  是
    证书已添加到密钥库中
    
    D:\MyData\majx2>keytool -list -alias www.crazyxing.com -keystore crazyxing.keystore
    输入密钥库口令:
    www.crazyxing.com, 2019-3-9, trustedCertEntry,
    证书指纹 (SHA1): 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
    
    命令参数 命令说明
    -importcert 表示导入数字证书
    -trustcacerts 表示将数字证书导入信任库
    -alias 指定别名 www.crazyxing.com
    -file 指定导入证书的文件路径,这里为crazyxing.p12
    -keystore 指定密钥库文件,这里为crazyxing.keystore
    -storepass 指定密码

    3、证书的使用

    先实现一个认证工具:

    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;
    
    public abstract class CertificateCoder {
    
        /**
         * 类型证书X509
         */
        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);
    
        }
    
    }
    

    下面是相应的单元测试:

    import org.apache.commons.codec.binary.Hex;
    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class CertificateCoderTest {
    
        private String password = "123456";
        
        private String alias = "www.crazyxing.com";
        
        private String certificatePath = "D:/MyData/majx2/crazyxing.cer";
        
        private String keyStorePath = "D:/MyData/majx2/crazyxing.keystore.old";
    
        /**
         * 公钥加密——私钥解密
         * 
         * @throws Exception
         */
        @Test
        public void test1() throws Exception {
    
            System.err.println("公钥加密——私钥解密");
            String inputStr = "Ceritifcate";
            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 = "sign";
            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);
    
        }
    
    }
    

    OpenSSL证书管理

    下面示例生成双向认证所需的全部证书。

    安装OpenSSL工具

    1、首先下载OpenSSL
    http://slproweb.com/download/Win64OpenSSL-1_1_1b.exe

    2、给OpenSSL设置环境变量


    设置环境变量 设置Path

    3、修改ca证书路径
    编辑%OpenSSL_HOME%\bin\openssl.cfg配置文件,设置ca路径


    修改配置文件

    4、构建CA目录结构

    D:\MyData\majx2>mkdir demoCA
    
    D:\MyData\majx2>cd demoCA
    # 构建已发行证书存放目录
    D:\MyData\majx2\demoCA>mkdir certs
    # 构建新证书存放目录
    D:\MyData\majx2\demoCA>mkdir newcerts
    # 构建私钥存放目录
    D:\MyData\majx2\demoCA>mkdir private
    # 构建证书吊销列表存放目录
    D:\MyData\majx2\demoCA>mkdir cr1
    # 构建索引文件
    D:\MyData\majx2\demoCA>echo 0>index.txt
    # 构建序列号文件
    D:\MyData\majx2\demoCA>echo 01>serial
    

    构建根证书

    1、构建随机数

    D:\MyData\majx2\demoCA>openssl rand -out private/.rand 1000
    
    命令参数 命令说明
    -rand 随机数命令
    -out 输出文件路径,这里将随机数文件输出到private目录下

    2、构建根证书
    openssl通常使用PEM(隐私增强邮件)编码格式保存私钥

    D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/ca.key.pem 2048
    Generating RSA private key, 2048 bit long modulus (2 primes)
    ...............................................+++++
    ...................+++++
    e is 65537 (0x010001)
    Enter pass phrase for private/ca.key.pem:
    Verifying - Enter pass phrase for private/ca.key.pem:
    
    命令参数 命令说明
    genrsa 产生RSA密钥命令
    -aes256 使用AES算法(256位密钥)对产生的私钥加密。可选算法包括DES/DESede/IDEA/AES
    -out 输出路径,这里是private/ca.key.pem

    3、生成根证书签发申请

    D:\MyData\majx2\demoCA>openssl req -new -key private/ca.key.pem -out private/ca.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=*.crazyxing.com"
    Enter pass phrase for private/ca.key.pem:
    
    命令参数 命令说明
    req 生成证书签发申请命令
    -new 表示新请求
    -key 密钥,这里pirvate/ca.key.pem文件
    -out 输出路径,这里private/ca.csr文件
    -subj 指定用户信息,这里使用泛域名"*.crazyxing.com"作为用户名

    4、签发根证书
    得到根证书申请文件后,可以发送给CA机构签发,也可以自行签发根证书

    D:\MyData\majx2\demoCA>openssl x509 -req -days 10000 -sha1 -extensions v3_ca -signkey private/ca.key.pem -in private/ca.csr -out certs/ca.cer
    Signature ok
    subject=C = CN, ST = GD, L = GZ, O = crazyxing, OU = crazyxing, CN = *.crazyxing.com
    Getting Private key
    Enter pass phrase for private/ca.key.pem:
    
    命令参数 命令说明
    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

    5、根证书转换
    OpenSSL产生的数字证书不能在Java语言中直接使用,需要将其转化成PKCS#12编码格式

    D:\MyData\majx2\demoCA>openssl pkcs12 -export -cacerts -inkey private/ca.key.pem -in certs/ca.cer -out certs/ca.p12
    Enter pass phrase for private/ca.key.pem:
    Enter Export Password:
    Verifying - Enter Export Password:
    
    D:\MyData\majx2\demoCA>keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456
    密钥库类型: PKCS12
    密钥库提供方: SunJSSE
    
    您的密钥库包含 1 个条目
    
    别名: 1
    创建日期: 2019-3-10
    条目类型: PrivateKeyEntry
    证书链长度: 1
    证书[1]:
    所有者: CN=*.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    发布者: CN=*.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
    序列号: 6fad9a9cb9fb61a60c976403ee5fe42228c956fa
    有效期为 Sun Mar 10 09:29:11 CST 2019 至 Thu Jul 26 09:29:11 CST 2046
    证书指纹:
             MD5:  4D:FA:6B:F3:A9:91:16:37:34:C1:7E:E3:66:1B:3C:A3
             SHA1: F8:52:09:30:17:02:07:CF:82:45:4C:92:66:7A:85:73:C6:BE:40:4D
             SHA256: 03:5B:03:ED:C8:15:9F:B5:76:3F:F6:F9:43:EB:7D:4A:ED:B5:6F:88:73:0D:C2:7C:3C:CB:08:6A:04:05:56:F4
    签名算法名称: SHA1withRSA
    主体公共密钥算法: 2048 位 RSA 密钥
    版本: 1
    
    
    *******************************************
    *******************************************
    
    命令参数 命令说明
    pkcs12 PKCS#12编码格式证书命令
    -export 表示导出证书
    -cacerts 表示仅导出CA证书
    -inkey 表示输入密钥,这里为private/ca.key.pem
    -in 表示输入文件,这里为certs/ca.cer
    -out 表示输出文件,这里为certs/ca.p12

    构建服务器证书

    1、构建服务器私钥

    D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/server.key.pem 2048
    Generating RSA private key, 2048 bit long modulus (2 primes)
    .......................................+++++
    .................................................................................................................................................+++++
    e is 65537 (0x010001)
    Enter pass phrase for private/server.key.pem:
    Verifying - Enter pass phrase for private/server.key.pem:
    
    命令参数 命令说明
    genrsa 产生RSA密钥命令
    -aes256 使用AES算法(256位密钥)对产生的私钥加密。可选算法包括DES/DESede/IDEA/AES
    -out 输出路径,这里是private/server.key.pem

    2、生成服务器证书签发申请

    D:\MyData\majx2\demoCA>openssl req -new -key private/server.key.pem -out private/server.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=www.crazyxing.com"
    Enter pass phrase for private/server.key.pem:
    
    命令参数 命令说明
    req 生成证书签发申请命令
    -new 表示新请求
    -key 密钥,这里pirvate/server.key.pem文件
    -out 输出路径,这里private/server.csr文件
    -subj 指定用户信息,这里使用泛域名"www.crazyxing.com"作为用户名

    3、签发服务器证书

    D:\MyData\majx2\demoCA>openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/ca.key.pem -CAserial ca.sr1 -CAcreateserial -in private/server.csr -out certs/server.cer
    Signature ok
    subject=C = CN, ST = GD, L = GZ, O = crazyxing, OU = crazyxing, CN = www.crazyxing.com
    Getting CA Private Key
    Enter pass phrase for private/ca.key.pem:
    
    命令参数 命令说明
    x509 签发X.509格式证书命令
    -req 表示证书输入请求
    -days 表示有效天数,这里10000天
    -sha1 表示证书摘要算法,这里为SHA1算法
    -extensions 表示按OpenSSL配置文件v3_ca项添加扩展
    -CA 表示CA证书,这里certs/ca.cer
    -CAkey 表示CA证书密钥,这里private/ca.key.pem
    -CAserial 表示CA证书序列号文件,这里ca.sr1
    -CAcreateserial 表示创建CA证书序列号
    -in 表示输入文件,这里private/ca.csr
    -out 表示输出文件,这里certs/ca.cer

    4、服务器证书转换

    D:\MyData\majx2\demoCA>openssl pkcs12 -export -clcerts -inkey private/server.key.pem -in certs/server.cer -out certs/server.p12
    Enter pass phrase for private/server.key.pem:
    Enter Export Password:
    Verifying - Enter Export Password:
    
    命令参数 命令说明
    pkcs12 PKCS#12编码格式证书命令
    -export 表示导出证书
    -clcerts 表示仅导出客户证书
    -inkey 表示输入密钥,这里为private/server.key.pem
    -in 表示输入文件,这里为certs/server.cer
    -out 表示输出文件,这里为certs/server.p12

    构建客户端证书

    1、产生客户密钥

    D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/client.key.pem 2048
    Generating RSA private key, 2048 bit long modulus (2 primes)
    ................................................................................................+++++
    .................+++++
    e is 65537 (0x010001)
    Enter pass phrase for private/client.key.pem:
    Verifying - Enter pass phrase for private/client.key.pem:
    
    命令参数 命令说明
    genrsa 产生RSA密钥命令
    -aes256 使用AES算法(256位密钥)对产生的私钥加密。可选算法包括DES/DESede/IDEA/AES
    -out 输出路径,这里是private/server.key.pem

    2、生成客户证书签发申请

    D:\MyData\majx2\demoCA>openssl req -new -key private/client.key.pem -out private/client.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=crazyxing"
    Enter pass phrase for private/client.key.pem:
    
    命令参数 命令说明
    req 生成证书签发申请命令
    -new 表示新请求
    -key 密钥,这里pirvate/client.key.pem文件
    -out 输出路径,这里private/client.csr文件
    -subj 指定用户信息,这里使用"crazyxing"作为用户名

    3、签发客户证书

    D:\MyData\majx2\demoCA>openssl ca -days 3650 -in private/client.csr -out certs/client.cer -cert certs/ca.cer -keyfile private/ca.key.pem
    Using configuration from C:\Program Files\Common Files\SSL/openssl.cnf
    Enter pass phrase for private/ca.key.pem:
    Check that the request matches the signature
    Signature ok
    Certificate Details:
            Serial Number: 1 (0x1)
            Validity
                Not Before: Mar 10 02:44:01 2019 GMT
                Not After : Mar  7 02:44:01 2029 GMT
            Subject:
                countryName               = CN
                stateOrProvinceName       = GD
                organizationName          = crazyxing
                organizationalUnitName    = crazyxing
                commonName                = crazyxing
            X509v3 extensions:
                X509v3 Basic Constraints:
                    CA:FALSE
                Netscape Comment:
                    OpenSSL Generated Certificate
                X509v3 Subject Key Identifier:
                    AB:6F:ED:7E:D7:15:C2:6B:8C:F4:C7:5E:20:5A:15:A0:5C:DF:ED:8E
                X509v3 Authority Key Identifier:
                    DirName:/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=*.crazyxing.com
                    serial:6F:AD:9A:9C:B9:FB:61:A6:0C:97:64:03:EE:5F:E4:22:28:C9:56:FA
    
    Certificate is to be certified until Mar  7 02:44:01 2029 GMT (3650 days)
    Sign the certificate? [y/n]:y
    
    
    1 out of 1 certificate requests certified, commit? [y/n]y
    Write out database with 1 new entries
    Data Base Updated
    
    命令参数 命令说明
    ca 签发证书命令
    -days 表示有效天数,这里3650天
    -in 表示输入文件,这里private/client.csr
    -out 表示输出文件,这里certs/client.cer
    -cert 表示证书文件,这里为certs/ca.cer
    -keyfile 表示根证书密钥文件,这里为private/ca.key.pem

    4、客户证书转换

    D:\MyData\majx2\demoCA>openssl pkcs12 -export -inkey private/client.key.pem -in certs/client.cer -out certs/client.p12
    Enter pass phrase for private/client.key.pem:
    Enter Export Password:
    Verifying - Enter Export Password:
    
    命令参数 命令说明
    pkcs12 PKCS#12编码格式证书命令
    -export 表示导出证书
    -clcerts 表示仅导出客户证书
    -inkey 表示输入密钥,这里为private/client.key.pem
    -in 表示输入文件,这里为certs/client.cer
    -out 表示输出文件,这里为certs/client.p12

    证书使用

    先给出一个证书工具类

    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;
    
    public abstract class CertificateCoder {
    
        /**
         * 证书类型X509
         */
        public static final String CERT_TYPE = "X.509";
    
        /**
         * 密钥库类型PCKS12
         */
        private static final String STORE_TYPE = "PKCS12";
    
        /**
         * 由KeyStore获得私钥
         * 
         * @param keyStorePath
         *            密钥库路径
         * @param alias
         *            别名
         * @param password
         *            密码
         * @return PrivateKey 私钥
         * @throws Exception
         */
        public 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
         */
        public static PublicKey getPublicKeyByCertificate(String certificatePath)
                throws Exception {
    
            // 获得证书
            Certificate certificate = getCertificate(certificatePath);
    
            // 获得公钥
            return certificate.getPublicKey();
    
        }
    
        /**
         * 获得Certificate
         * 
         * @param certificatePath
         *            证书路径
         * @return Certificate 证书
         * @throws Exception
         */
        private static X509Certificate getCertificate(String certificatePath)
                throws Exception {
    
            // 实例化证书工厂
            CertificateFactory certificateFactory = CertificateFactory
                    .getInstance(CERT_TYPE);
    
            // 取得证书文件流
            FileInputStream in = new FileInputStream(certificatePath);
    
            // 生成证书
            Certificate certificate = certificateFactory.generateCertificate(in);
    
            // 关闭证书文件流
            in.close();
    
            return (X509Certificate) certificate;
        }
    
        /**
         * 获得KeyStore
         * 
         * @param keyStorePath
         *            密钥库路径
         * @param password
         *            密码
         * @return KeyStore 密钥库
         * @throws Exception
         */
        private static KeyStore getKeyStore(String keyStorePath, String password)
                throws Exception {
    
            // 实例化密钥库
            KeyStore ks = KeyStore.getInstance(STORE_TYPE);
    
            // 获得密钥库文件流
            FileInputStream in = new FileInputStream(keyStorePath);
            // 加载密钥库
            ks.load(in, password.toCharArray());
    
            // 关闭密钥库文件流
            in.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, String certificatePath) throws Exception {
    
            // 获得证书
            X509Certificate x509Certificate = getCertificate(certificatePath);
    
            // 构建签名,由证书指定签名算法
            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);
    
        }
    
    }
    

    下面是相应的单元测试

    import org.apache.commons.codec.binary.Hex;
    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class CertificateCoderTest {
    
        private String password = "123456";
    
        private String alias = "1";
    
        private String certificatePath = "D:/MyData/majx2/demoCA/certs/ca.cer";
    
        private String keyStorePath = "D:/MyData/majx2/demoCA/certs/ca.p12";
    
        /**
         * 公钥加密——私钥解密
         * 
         * @throws Exception
         */
        @Test
        public void test1() {
    
            try {
                System.err.println("公钥加密——私钥解密");
                String inputStr = "Ceritifcate";
                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);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                fail(e.getMessage());
            }
    
        }
    
        /**
         * 私钥加密——公钥解密
         * 
         * @throws Exception
         */
        @Test
        public void test2() {
    
            System.err.println("私钥加密——公钥解密");
    
            String inputStr = "sign";
            byte[] data = inputStr.getBytes();
    
            try {
                // 私钥加密
                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);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                fail(e.getMessage());
            }
        }
    
        /**
         * 签名验证
         * 
         * @throws Exception
         */
        @Test
        public void testSign() {
    
            try {
                String inputStr = "签名";
                byte[] data = inputStr.getBytes();
                System.err.println("私钥签名——公钥验证");
    
                // 产生签名
                byte[] sign = CertificateCoder.sign(data, keyStorePath, alias,
                        password,certificatePath);
                System.err.println("签名:\n" + Hex.encodeHexString(sign));
    
                // 验证签名
                boolean status = CertificateCoder.verify(data, sign,
                        certificatePath);
                System.err.println("状态:\n" + status);
    
                // 校验
                assertTrue(status);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                fail(e.getMessage());
            }
    
        }
    
    }
    

    相关文章

      网友评论

        本文标题:终极武器——数字证书

        本文链接:https://www.haomeiwen.com/subject/pbyfpqtx.html