证书运维

pem、crt、cer、pfx、jks:证书格式到底有几种,怎么互转

证书的扩展名和它的实际格式常常对不上,很多所谓的「转换」只是改个名字。本文说明编码与容器这两个维度、各扩展名实际是什么、以及 pem/der/pfx/jks 之间的 openssl 与 keytool 转换命令,并给出转换后的验证方法。

从 CA 拿到的文件叫 fullchain.pem,Tomcat 要 .jks,IIS 要 .pfx,某个设备的后台只收 .cer —— 看起来是四种东西,实际上只有两个维度:编码容器

先把这两个维度分清楚,大部分「转换」就会变成「改个扩展名」或者「一条命令」。

两个维度

编码决定字节长什么样,只有两种:

编码内容怎么认
PEMBase64 文本,带 -----BEGIN ...----- 头尾用文本编辑器能打开、能看懂
DER同样的数据,二进制打开是乱码

容器决定一个文件里装了什么:

容器装了什么典型使用者
单独的证书文件一张或多张证书,没有私钥nginx、Apache
单独的私钥文件只有私钥nginx、Apache
PKCS#12证书 + 私钥 + 证书链,打包在一个文件里,通常有密码IIS、Windows、部分负载均衡
JKSJava 自己的密钥库格式老版本 Tomcat、Java 应用

扩展名其实不说明格式

这是最大的困惑来源:扩展名是约定,不是规范

扩展名实际上通常是注意
.pemPEM 编码,内容不定可能是证书、私钥,或者两者都有
.crt / .cer证书,PEM 或 DER 都有可能.cer 在 Windows 生态里更常见,但两者没有硬性区别
.key私钥,通常 PEM
.derDER 编码
.pfx / .p12PKCS#12两个扩展名完全等价
.jksJava 密钥库
fullchain.pem服务器证书 + 中间证书,PEMLet's Encrypt 的命名习惯

所以「.crt.pem」这个说法本身是空的 —— 得先看它里面是 PEM 还是 DER:

# 能读出内容 = 已经是 PEM,改个扩展名就行
openssl x509 -in cert.crt -noout -subject

# 上面报错,再试 DER
openssl x509 -in cert.crt -inform der -noout -subject

转换命令

PEM ↔ DER

openssl x509 -in cert.pem -outform der -out cert.der
openssl x509 -in cert.der -inform der -out cert.pem

PEM → PFX(给 IIS / Windows)

openssl pkcs12 -export \
  -inkey  privkey.pem \
  -in     cert.pem \
  -certfile chain.pem \
  -out    cert.pfx

-in 放服务器证书,-certfile 放中间证书。如果你手上只有 fullchain.pem(服务器证书和中间证书已经拼在一起),直接把它给 -in、省掉 -certfile 也可以。

导出时会要求设置密码。IIS 导入时要用同一个密码,不能留空 —— 部分 Windows 版本对空密码的 PKCS#12 会直接报「密码不正确」,而不是提示不支持。

PFX → PEM

# 证书部分(含链)
openssl pkcs12 -in cert.pfx -clcerts -nokeys -out cert.pem

# 私钥部分,-nodes 表示不给私钥再加密
openssl pkcs12 -in cert.pfx -nocerts -nodes -out privkey.pem

# 链上的其余证书
openssl pkcs12 -in cert.pfx -cacerts -nokeys -out chain.pem

导出的 PEM 文件头部会带一段 Bag Attributes 的说明文字。nginx 不介意,但某些较真的解析器会,删掉 -----BEGIN 之前的所有行即可。

PEM → JKS(给 Tomcat / Java)

没有直接的命令,标准做法是先转成 PKCS#12,再让 keytool 导入

openssl pkcs12 -export -inkey privkey.pem -in fullchain.pem \
  -name tomcat -out cert.p12

keytool -importkeystore \
  -srckeystore cert.p12 -srcstoretype pkcs12 \
  -destkeystore cert.jks -deststoretype JKS \
  -srcalias tomcat -destalias tomcat

-name / -alias 要一致,Java 应用的配置里引用的就是这个别名。

Tomcat 8.5 以上可以直接使用 PKCS#12,不必再转 JKS —— 在 server.xml 里把 keystoreType 写成 PKCS12 并指向 .p12 文件即可。JKS 是旧格式,能不转就不转。

转完之后验证

改扩展名、拆包、重新打包,每一步都有把证书和私钥配错的可能,而配错的后果是服务起不来或者握手失败 —— 到那时再回头找原因很费劲。两条命令就能确认:

# 一、证书和私钥是不是一对(两行输出必须一致)
openssl x509 -in cert.pem -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout     | openssl sha256

# 二、链是否完整、顺序是否正确
openssl verify -untrusted chain.pem cert.pem

第一条用 pkey 而不是 rsa,因为 ECC 私钥不吃 openssl rsapkey 对 RSA 和 ECC 都有效。

第二条返回 cert.pem: OK 才算过。

unable to get local issuer certificate 时,先确认 chain.pem 里是全部中间证书,而不只是一张。 当前的 Let's Encrypt 链在中间证书之上还带一张交叉签名的根证书,只提取「那张中间证书」会得到同样的报错 —— 而链本身是好的。把服务器发出的除第一张之外的证书全部拼进 chain.pem 再验一次。

确认链确实完整之后仍然报这个错,才是真的缺东西,见证书链不完整

另外,openssl verify 要在系统的根证书库里找最终的根。精简的容器镜像里常常没有装(Alpine 需要 apk add ca-certificates),这时它对任何证书都会报同一个错。

几个反复出现的坑

私钥带密码。 openssl rsa -in enc.key -out plain.key 可以去掉密码。nginx 也支持带密码的私钥,但那意味着每次重启都要人工输入 —— 对自动续期来说是致命的。

fullchain 的顺序。 服务器证书必须在最前,然后是中间证书,根证书不要放进去。顺序错了部分客户端会验证失败,而另一部分不会,于是表现成「只有某些人打不开」。

Windows 换行。 在 Windows 上编辑过的 PEM 文件可能带 \r\n,个别老解析器会因此失败。dos2unix cert.pemsed -i 's/\r$//' cert.pem 可以处理。

同名不同物。 很多面板把上传框标成「证书(crt)」和「密钥(key)」,但实际期望的是 fullchain 而不是单张证书。装完之后用证书检测确认一次链是完整的,比逐个面板去猜它要什么快得多。