数字证书、公私钥小记

发布于2016年2月23日 -

术语

  • X.509

    在密码学中,X.509是由ITU-T为了公开密钥基础建设(PKI)与授权管理基础建设(PMI)提出的产业标准。X.509标准,规范了公开密钥认证、证书吊销列表、授权证书、证书路径验证算法等。[1]

  • PKCS(Public Key Cryptography Standards)

    PKCS是由RSA公司制定的一组关于公钥加密的标准,和存储相关的主要包括:

    PKCS1:定义了RSA的数理基础、公私钥格式,以及加解密、签/验章的流程 PKCS8:定义了私钥消息的表示 PKCS12:定义了包含私钥与公钥证书的文件格式,其中私钥采密码保护

公私钥及证书的生成

以下操作会用到opensslkeytool两个工具。注意,如果不加声明,证书和密钥的存储都是PEM格式。

生成私钥

openssl genrsa -out ca.key 2048

openssl生成的私钥是按PKCS#1编码的,这种格式包括了密钥的所有信息(如n、e、d、p、q等)[2],所以genrsa只生成私钥。

如果需要PKCS#8编码的私钥,还需要额外的转换:

openssl pkcs8 -topk8 -inform PEM in ca.key -outform PEM -nocrypt -out ca.pkcs8.key

如果需要公钥,需要使用如下的命令:

从私钥得到公钥

openssl rsa -in ca.key -pubout -outform PEM -out ca.pub

生成的公钥是按PKCS#8编码的。大多数地方都采用这种格式表示公钥。

生成根证书

openssl req -x509 -new -nodes -key ca.key -days 3000 -out ca.crt

生成的证书是按X.509编码的,有效期是3000天。

签发证书

有了根证书,就可以对证书签名了。为此,你需要先生成一个私钥,例如叫做host.key,然后通过私钥创建一个签名请求(CSR):

openssl req -new -key host.key -out host.csr

openssl会问你一些问题,务必注意CN这个选项,它应当是放置该证书的主机名(域名或者IP地址),如果开启了证书校验,它必须跟客户端建立连接时填写的host一致。

有了CSR,就可以用根证书和它的密钥来签发证书了:

openssl x509 -req -in host.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 1000 -out host.crt

证书依旧是按X.509编码的,有效期是1000天。

证书格式的转换

以上生成的公私钥和证书都是PEM格式的,但很多时候不同场景中还需要用到其他格式的证书:

p12/pfx

p12/pfx是按照PKCS#12编码的对象,它通常由X.509证书和对应的私钥组成。生成p12格式的方法如下:

openssl pkcs12 -export -in ca.crt -inkey ca.key -out ca.p12

由于文件中保存了私钥,因此执行该命令,openssl会要求用户输入密码,用于保护私钥。

jks

在Java中,免不了使用JKS格式,JKS是Java标准的密钥和证书保存格式,严格来说,Java的KeyStore存储的是多个密钥和证书。要生成它,需要用到keytool工具:

p12文件转换为jks

keytool -importkeystore -srckeystore cert.p12 -srcstoretype pkcs12 -destkeystore cert.jks

分别输入目标jks文件的密码和源p12文件的密码,即可生成。

crt文件转换为jks

keytool -import -file ca.crt -keystore ca.jks

不同环境/工具对密钥的需求

Java

在Java中,如果要使用java.security包中和RSA相关的算法,你需要PKCS#8编码的公私钥,分别使用PKCS8EncodedKeySpecX509EncodedKeySpec加载Base64解码后的数据即可。

如果要使用javax.net.ssl下的KeyStore或者TrustStore,最好使用jks格式,可以省去很多麻烦。

C#

在.Net中,System.Security.Cryptography下的X509Certificate2X509Certificate已经过时了)使用p12格式。

一些问题

.Net 证书验证

默认情况下,.Net会对证书做很严格的检验,包括但不限于证书链、吊销情况,SSL/TLS中还会检验主机名。但对于自签名证书,吊销情况是无法通过检查的,因此需要手动把吊销检验关闭:

var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.Build(yourCert);

Java 证书验证

Java会自己维护一个证书链(位于JAVA_HOME/lib/security/cacerts),因此把证书加到系统的cacerts里似乎是无效的。对于 SSLContext,必须要自己把证书加到 TrustStore里。需要指出的是,如果使用JSSE,Java是不会验证主机名的,必须自己处理,这里有讨论。

参考资料