一、数字证书
数字证书是一段经CA签名的、包含拥有者身份信息和公开密钥的数据体。数字证书和一对公、私钥相对应。公钥以明文形式放到数字证书中,私钥为拥有者秘密掌握。CA确保数字证书中信息的真实性,可以作为终端实体的身份证明。在电子商务和网络信息交流中,数字证书常用来解决相互间的信任问题。可以说,数字证书类似于现实生活中公安部门发放的居民身份证。
国际标准X.509定义了规范的数字证书格式,它是PKI技术体系中应用最广泛、最基础的一个国际标准。许多与PKI相关的协议标准(如PKIX、S/MIME、SSL、TLS、IPsec等)都是在X.509基础上发展起来的。
二、国密数字证书的基本数据结构
以下是国密SM2证书的ASN.1结构定义,与国际上RSA证书采用的X509格式差别不大。
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate, -- 证书主体
signatureAlgorithm AlgorithmIdentifier, -- 证书签名算法标识
signatureValue BIT STRING --证书签名值
}
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1, -- 证书版本号
serialNumber CertificateSerialNumber, -- 证书序列号(对同一CA所颁发的证书,序列号唯一标识证书)
signature AlgorithmIdentifier, --证书签名算法标识
issuer Name, --证书发行者名称
validity Validity, --证书有效期
subject Name, --证书主体名称
subjectPublicKeyInfo SubjectPublicKeyInfo,--证书公钥
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,-- 证书发行者ID(可选),只在v2、v3中支持
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,-- 证书主体ID(可选),只在v2、v3中支持
extensions [3] EXPLICIT Extensions OPTIONAL -- 证书扩展段(可选),只在v3中支持
}
Version ::= INTEGER { v1(0), v2(1), v3(2) }
CertificateSerialNumber ::= INTEGER
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
Name ::= CHOICE { RDNSequence }
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType,
value AttributeValue
}
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY DEFINED BY AttributeType
Validity ::= SEQUENCE {
notBefore Time, -- 证书有效期起始时间
notAfter Time -- 证书有效期终止时间
}
Time ::= CHOICE {
utcTime UTCTime, -- 世界时间类型
generalTime GeneralizedTime -- 通用时间类型
}
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier, -- 公钥算法
subjectPublicKey BIT STRING -- 公钥值
}
UniqueIdentifier ::= BIT STRING
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
Extension ::= SEQUENCE {
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING
}
三、生成国密SM2数字证书
下面采用开源BC库生成国密SM2的数字证书,生成PFX格式和cert格式的证书。其中cert证书只包含公钥证书,PFX证书既包含公钥也包含私钥。
Maven依赖:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.62</version>
</dependency>
演示代码:
@Slf4j
public class SM2CertTest extends SM2Test {
private X500Name createX500Name(String country, String province, String locality, String organization,
String commonName) {
X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
// 通用名称
builder.addRDN(BCStyle.CN, commonName);
// 组织
builder.addRDN(BCStyle.O, organization);
// 地区
builder.addRDN(BCStyle.L, locality);
// 省份或州
builder.addRDN(BCStyle.ST, province);
// 国家代码
builder.addRDN(BCStyle.C, country);
return builder.build();
}
private void addExtensions(X509v3CertificateBuilder certBuilder) throws Exception {
certBuilder
// 设置密钥用法
.addExtension(Extension.keyUsage, false, new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.nonRepudiation))
// 设置扩展密钥用法:客户端身份认证、安全电子邮件
.addExtension(Extension.extendedKeyUsage, false, this.getExtendedKeyUsage())
// 基础约束,标识是否是CA证书,这里false标识为实体证书
.addExtension(Extension.basicConstraints, false, new BasicConstraints(false))
// SSL客户端身份认证
.addExtension(MiscObjectIdentifiers.netscapeCertType, false, new NetscapeCertType(NetscapeCertType.sslClient))
// 使用者备用名称
.addExtension(Extension.subjectAlternativeName, false, this.getSubjectAlternativeName());
}
private DERSequence getExtendedKeyUsage() {
ASN1EncodableVector v = new ASN1EncodableVector();
// 客户端身份认证
v.add(KeyPurposeId.id_kp_clientAuth);
// 安全电子邮件
v.add(KeyPurposeId.id_kp_emailProtection);
return new DERSequence(v);
}
private DERSequence getSubjectAlternativeName() {
// 作为SSL证书时的域名绑定
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new GeneralName(GeneralName.dNSName, "baidu.com"));
v.add(new GeneralName(GeneralName.dNSName, "www.baidu.com"));
v.add(new GeneralName(GeneralName.dNSName, "*.baidu.com"));
return new DERSequence(v);
}
private X509Certificate createCert(X500Name issuer, X500Name subject, PrivateKey privateKey, PublicKey publicKey)
throws Exception {
SerialNumber sn = new SerialNumber(new BigInteger(DateUtil.format(new Date(), "yyyyMMddHHmmss")
+ DateUtil.currentSeconds() + System.currentTimeMillis()));
// 构造X.509 第3版的证书构建者
X509v3CertificateBuilder tbsBuilder = new JcaX509v3CertificateBuilder(
// 颁发者信息
issuer
// 证书序列号
, sn.getNumber()
// 证书生效日期
, new Date(DateUtil.offsetMonth(new Date(), -1).getTime())
// 证书失效日期
, new Date(DateUtil.offsetMonth(new Date(), 240).getTime())
// 使用者信息(PS:由于是自签证书,所以颁发者和使用者DN都相同)
, subject
// 证书公钥
, publicKey);
// 设置证书扩展
this.addExtensions(tbsBuilder);
// 证书签名
ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2").setProvider("BC").build(privateKey);
// 将证书构造参数装换为X.509证书对象
X509Certificate certificate = new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(tbsBuilder.build(signer));
return certificate;
}
private void savePfx(X509Certificate certificate, PrivateKey privateKey, String filename, String password)
throws Exception {
// 生成pfx文件
String pfxPath = "C:/" + filename + ".pfx";
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
log.info("KeyStore类型:{}", keyStore.getType());
keyStore.load(null, null);
String alias = IdUtil.fastSimpleUUID();
keyStore.setKeyEntry(alias, privateKey, password.toCharArray(), new X509Certificate[] { certificate });
FileOutputStream fos = new FileOutputStream(new File(pfxPath));
keyStore.store(fos, password.toCharArray());
fos.flush();
}
private void saveCer(X509Certificate certificate, String filename) throws Exception {
// 生成cer文件
String cerPath = "C:/" + filename + ".cer";
log.info("证书数据: {}", HexUtil.encodeHexStr(certificate.getEncoded()));
FileUtil.writeBytes(certificate.getEncoded(), cerPath);
}
@Test
public void testCreateCert() throws Exception {
// 产生CA数字证书和PFX
KeyPair caKeyPair = getSM2KeyPair();
X500Name caX500Name = createX500Name("CN", "CS", "CD", "HD", "CA_TEST");
X509Certificate caCertificate = createCert(caX500Name, caX500Name, caKeyPair.getPrivate(),
caKeyPair.getPublic());
String filename = IdUtil.fastSimpleUUID() + "_sm2-ca";
saveCer(caCertificate, filename);
savePfx(caCertificate, caKeyPair.getPrivate(), filename, "111111");
}
}
执行结果如下:
14:37:45.282 [main] INFO org.wcls.cryptotest.SM2Test - 私钥:308193020100301306072a8648ce3d020106082a811ccf5501822d04793077020101042059c9fab82e7f454beca0927fa1018b12ea262c2845b1f4e6b48aba49d64ba836a00a06082a811ccf5501822da14403420004ce4fe174c8851d1aacead11d972b3e26010e1016c4216dc24e8ac8c5922800de270f4ecd3eed379f73e48ba514ccc54d55b0f5d5e112c168737aa3e51fd38280
14:37:45.315 [main] INFO org.wcls.cryptotest.SM2Test - 公钥:3059301306072a8648ce3d020106082a811ccf5501822d03420004ce4fe174c8851d1aacead11d972b3e26010e1016c4216dc24e8ac8c5922800de270f4ecd3eed379f73e48ba514ccc54d55b0f5d5e112c168737aa3e51fd38280
14:37:45.707 [main] INFO org.wcls.cryptotest.SM2CertTest - 证书数据: 308201d53082017ca003020102021001850b34206c3df9f47850b3bc5c6859300a06082a811ccf5501837530463110300e06035504030c0743415f54455354310b3009060355040a0c024844310b300906035504070c024344310b300906035504080c024353310b300906035504061302434e301e170d3230303230373036333734355a170d3430303330373036333734355a30463110300e06035504030c0743415f54455354310b3009060355040a0c024844310b300906035504070c024344310b300906035504080c024353310b300906035504061302434e3059301306072a8648ce3d020106082a811ccf5501822d03420004ce4fe174c8851d1aacead11d972b3e26010e1016c4216dc24e8ac8c5922800de270f4ecd3eed379f73e48ba514ccc54d55b0f5d5e112c168737aa3e51fd38280a34c304a300b0603551d0f0404030206c0301d0603551d250416301406082b0601050507030206082b0601050507030430090603551d1304023000301106096086480186f8420101040403020780300a06082a811ccf55018375034700304402203c46862a16f97ebae38981946e894589f32a107ddde9fd05bf2e33dc4690b499022038475a179ea6d104d4a48953f8ad7592ee48afd3d5254a30ed851704ee5f1b45
14:37:45.808 [main] INFO org.wcls.cryptotest.SM2CertTest - KeyStore类型:PKCS12
参考资料
全部评论