pem、crt、cer、pfx、jks:证书格式到底有几种,怎么互转
证书的扩展名和它的实际格式常常对不上,很多所谓的「转换」只是改个名字。本文说明编码与容器这两个维度、各扩展名实际是什么、以及 pem/der/pfx/jks 之间的 openssl 与 keytool 转换命令,并给出转换后的验证方法。
从 CA 拿到的文件叫 fullchain.pem,Tomcat 要 .jks,IIS 要 .pfx,某个设备的后台只收 .cer —— 看起来是四种东西,实际上只有两个维度:编码和容器。
先把这两个维度分清楚,大部分「转换」就会变成「改个扩展名」或者「一条命令」。
两个维度
编码决定字节长什么样,只有两种:
| 编码 | 内容 | 怎么认 |
|---|---|---|
| PEM | Base64 文本,带 -----BEGIN ...----- 头尾 | 用文本编辑器能打开、能看懂 |
| DER | 同样的数据,二进制 | 打开是乱码 |
容器决定一个文件里装了什么:
| 容器 | 装了什么 | 典型使用者 |
|---|---|---|
| 单独的证书文件 | 一张或多张证书,没有私钥 | nginx、Apache |
| 单独的私钥文件 | 只有私钥 | nginx、Apache |
| PKCS#12 | 证书 + 私钥 + 证书链,打包在一个文件里,通常有密码 | IIS、Windows、部分负载均衡 |
| JKS | Java 自己的密钥库格式 | 老版本 Tomcat、Java 应用 |
扩展名其实不说明格式
这是最大的困惑来源:扩展名是约定,不是规范。
| 扩展名 | 实际上通常是 | 注意 |
|---|---|---|
.pem | PEM 编码,内容不定 | 可能是证书、私钥,或者两者都有 |
.crt / .cer | 证书,PEM 或 DER 都有可能 | .cer 在 Windows 生态里更常见,但两者没有硬性区别 |
.key | 私钥,通常 PEM | |
.der | DER 编码 | |
.pfx / .p12 | PKCS#12 | 两个扩展名完全等价 |
.jks | Java 密钥库 | |
fullchain.pem | 服务器证书 + 中间证书,PEM | Let'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.pemPEM → 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 rsa。pkey 对 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.pem 或 sed -i 's/\r$//' cert.pem 可以处理。
同名不同物。 很多面板把上传框标成「证书(crt)」和「密钥(key)」,但实际期望的是 fullchain 而不是单张证书。装完之后用证书检测确认一次链是完整的,比逐个面板去猜它要什么快得多。