终极武器——数字证书

数字证书也称电子证书,由数字证书颁发认证机构(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());
        }

    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容