术语
-
X.509
在密码学中,X.509是由ITU-T为了公开密钥基础建设(PKI)与授权管理基础建设(PMI)提出的产业标准。X.509标准,规范了公开密钥认证、证书吊销列表、授权证书、证书路径验证算法等。[1]
-
PKCS(Public Key Cryptography Standards)
PKCS是由RSA公司制定的一组关于公钥加密的标准,和存储相关的主要包括:
PKCS1:定义了RSA的数理基础、公私钥格式,以及加解密、签/验章的流程 PKCS8:定义了私钥消息的表示 PKCS12:定义了包含私钥与公钥证书的文件格式,其中私钥采密码保护
公私钥及证书的生成
以下操作会用到openssl
和keytool
两个工具。注意,如果不加声明,证书和密钥的存储都是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编码的公私钥,分别使用PKCS8EncodedKeySpec
和X509EncodedKeySpec
加载Base64解码后的数据即可。
如果要使用javax.net.ssl
下的KeyStore
或者TrustStore
,最好使用jks
格式,可以省去很多麻烦。
C#
在.Net中,System.Security.Cryptography
下的X509Certificate2
(X509Certificate
已经过时了)使用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是不会验证主机名的,必须自己处理,这里有讨论。