数字证书也称电子证书,由数字证书颁发认证机构(CA)签发才具备可认证性。数字证书采用了公钥基础设施(PKI),使用了相应的加密算法确保网络应用安全性:
- 非对称加密算法用于对数据进行加密/解密操作,确保数据的机密性。
- 数字签名算法用于数据进行签名/验证操作,确保数据的完整性和抗否性。
- 消息摘要算法用于对数字证书本身做摘要处理,确保数字证书完整性。
数字证书常用算法
1、非对称加密算法:RSA
、DSA
(无法完成加密/解密实现,这样的数字证书不包括数据加密/解密功能)
2、签名算法:SHA1withRSA
(sha1RSA
)
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设置环境变量
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());
}
}
}