一、 基础
BouncyCastle,是一个用于Java平台的轻量级开源密码包。目前除了支持Java外,也支持C#。授权遵循MIT协议。
BouncyCastle几乎完成了当前主流的所有加解密算法、证书生成签发等内容。作为JCE(Java Cryptography Extension)的提供者(Provider),BouncyCastle对如消息摘要处理、非对称加密密钥对生成、证书签名处理等都提供了良好的支持。
二、 关键词
ECDSA,椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟。具体算法不在此描述。
secp256r1,可以简单理解为一种ECC,或者就简单理解为一种曲线。
非对称加密算法,可以理解为通信的双方,各自有一套密钥对。即公钥和私钥。私钥自己保存,公钥可公开。在双方通信时,一方先使用对方的公钥对数据加密,另一方在收到数据后,用自己的私钥解密数据。
数字证书,包含公钥、主体信息、扩展部分等诸多内容,最后使用签名算法,用私钥对这些信息进行签名。在双方通信过程中,通过数字证书的传递,可以理解为识别对方主体信息、并交换了公钥,及对其中的签名进行认证(判断可信度)。
CA,即电子商务认证中心,可以理解为一个权威的第三方机构。假设一种情景,如果两人交互,在彼此不认识的情况下,又无法识别对方的证件真伪时,一般是很难互相取得信任的。但是,如果存在双方都信任的第三个人(中间人),这个事情就好办多了。CA就类似于这个中间人。CA会对双方的证书进行签名,这样在一方与另一方通信时,只要携带被共同信任的CA签名过的证书,就可以认为这方可以信任。
CSR,即证书签名请求,包含自身的公钥、主体信息及与公钥对应的私钥自签名。一方可向CA机构提出证书签名请求,从而获取经CA签名过的数字证书。
三、 自签名步骤
一个自签名的步骤可按照如下顺序进行:
(1) 生成自签名根CA证书(含证书及私钥)
(2) 生成待签名主体的密钥对(含公钥及私钥)
(3) 生成待签名主体的证书签名请求(CSR)
(4) 解析CSR,并根据自签名根CA证书和其对应的私钥,对待签名主体进行签名
四、 生成自签名根CA证书
这个步骤可以使用openssl进行,如在安装有openssl的电脑上,用命令行工具执行以下脚本:
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys\oemRootCA.key -aes128 -passout file:passphrase.txt
openssl req -new -x509 -days 36500 -sha256 -key privateKeys\oemRootCA.key -set_serial 05 -passin file:passphrase.txt -config configs\oemRootCACert.cnf -extensions ext -out certs\oemRootCA.pem
第一句使用ecparam,设置ec(用椭圆曲线算法生成密钥)时使用的曲线为secp256r1,接下来,使用ec命令,生成私钥。并使用128位AES对这个私钥的内容加密。
第二句提出证书签名请求,并使用SHA256算法进行签名,生成证书为X.509格式的。
经过这命令后,可生成根CA证书及私钥文件。
五、 生成待签名主体的密钥对
有了根CA证书(虽然是自签名的,但也没关系),我们就可以在Java中使用BouncyCastle库,生成密钥对了。
在使用BouncyCastle前,首先需要将BouncyCastle加入到Security的提供者中:
Security.addProvider(new BouncyCastleProvider());
这样,以后在使用BouncyCastle时,提供者就可以使用名称“BC”获取。比如:
provider = Security.getProvider("BC");
默认情况下,虚拟机会使用Java自带的默认提供者进行签名等相关操作,但是自带的提供者库是不支持很多新的算法和曲线的。
之后,可如下生成密钥对:
// 获取椭圆曲线签名算法ECDSA生成器
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", provider);
// 设置椭圆曲线参数
ECGenParameterSpec ecSpec = new ECGenParameterSpec(ecName);
// 生成密钥对
KeyPair pair = keyGen.initialize(ecSpec);
六、 生成证书签名请求
在生成证书签名请求之前,还需要准备几个内容:主体信息、扩展信息等。
(1) 主体信息
主体信息(Subject),一般包含了ASN1定义的一些内容,如:CN,commonName代表公用名;O,organization代表组织;OU,organizationUnit代表组织单位,等等。
在Java中,我们使用X500Name构建这个信息:
X500NameBuilder builder = new X500NameBuilder(X500Name.getDefaultStyle());
builder.addRDN(BCStyle.C, country);
builder.addRDN(BCStyle.O, organization);
builder.addRDN(BCStyle.OU, organizationalUnit);
builder.addRDN(BCStyle.CN, commonName);
builder.addRDN(BCStyle.DC, domainComponent);
builder.build();
(2) 扩展信息
扩展信息(Extensions),一般包含了ASN1定义的一些证书次要内容,如:basicConstraints、keyUsage等。
这里,我们使用一个列表,构建Extension的集合:
List<Extension> extensions = new ArrayList<Extension>();
BasicConstraints basicConstraints = new BasicConstraints(isCA);
Extension extension = new Extension(Extension.basicConstraints, true, new DEROctetString(basicConstraints));
extensions.add(extension);
KeyUsage keys = new KeyUsage(keyUsage);
extension = new Extension(Extension.keyUsage, keyUsageCritical, new DEROctetString(keys));
extensions.add(extension);
(3) 构建证书签名请求
证书签名请求实际上是使用待签名主体的私钥,对其主体信息及公钥信息进行自签名的过程:
PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(subject, publicKeyInfo);
if (extensions != null && extensions.size() > 0)
{
for (Extension extension : extensions)
{
if (extension == null) {
continue;
}
builder.addAttribute(extension.getExtnId(), extension.getParsedValue());
}
}
ContentSigner signer = null;
PKCS10CertificationRequest csr = null;
try {
//获取签名信息
signer = getContentSigner(privateKey, signatureAlgorithm);
//构建证书签名请求
csr = builder.build(signer);
}catch(IOException|OperatorCreationException e)
{
//获取签名信息失败
e.printStackTrace();
}
在签名前,我们需要准备好主体信息,及公钥信息。并采用指定的签名算法,对内容进行签名。签名后得到的就是证书签名请求文件了。
证书签名请求文件,包含了待签名主体的信息、待签名主体的公钥及用与公钥对应的私钥进行的签名。
七、 签发证书
这一步,我们使用先前生成的根CA证书及其对应私钥,对证书请求文件进行签名。
首先,我们需要通过CA证书,得到CA的主体信息,这个信息以后会关联到最后签发后的证书中的父级主题中。
X500Principal principal = certIssuer.getSubjectX500Principal();
X500Name parentSubjectDn = X500Name.getInstance(principal.getEncoded());
这里的certIssuer就是X509格式证书,即X509Certificate。
接下来,我们就可以签发证书了:
//初始化证书的序列号
BigInteger serial = BigInteger.probablePrime(32, new Random());
//日期转化
Date notBeforeDate = Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant());
Date notAfterDate = Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant());
//证书签名构建器
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuerDn, serial, notBeforeDate, notAfterDate, csr.getSubject(), csr.getSubjectPublicKeyInfo());
if (extensions != null && extensions.size() > 0)
{
for (Extension extension : extensions) {
if (extension == null) {
continue;
}
builder.addExtension(extension.getExtnId(), extension.isCritical(), extension.getParsedValue());
}
JcaX509ExtensionUtils sdf = new JcaX509ExtensionUtils();
SubjectKeyIdentifier subjectKeyIdentifier = sdf.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo());
builder.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
}
//获取签名(签发证书时,使用CA的私钥来签名)
ContentSigner signer = getContentSigner(issuerPrivateKey, signatureAlgorithm);
X509CertificateHolder holder = builder.build(signer);
// BC加密算法
X509Certificate cert = new JcaX509CertificateConverter().setProvider(provider).getCertificate(holder);
//验证用于签名证书的私钥是否与传入的公钥是一对
cert.verify(issuerPublicKey);
这个步骤,我们构建了证书的序列号、证书的有效期(notBefore及notAfter)。并根据证书签名请求文件,构建了X509证书构建器。
和先前一样,我们提供了待签名的主体信息。
以上这些作为待签名的内容,最后使用CA根证书的私钥及指定的签名算法SHA256,生成签名后的证书。这个证书就是颁发给待签名主体的证书了。
八、 工具包
最后的最后,为了方便使用,我编写了BouncyCastleUtility工具包,封装了此过程中的零碎操作。有需要的小伙伴可以前往下载。地址Java平台BouncyCastleUtility工具包-Java文档类资源-CSDN下载