结论先说:crt 不是一种格式,只是扩展名;PEM 是 Base64 文本编码,PFX(PKCS#12)是带密码的二进制容器,里面可以同时装证书、私钥和证书链。三种东西互相转换,用 openssl 的三条命令就够了:pkcs12 负责 PEM 与 PFX 互转,x509 负责 PEM 与 DER 互转,pkcs7 负责 P7B 与 PEM 互转。
pem、pfx、crt 到底有什么区别?
结论:判断一个证书文件的真实格式不依赖扩展名,要看内容——-----BEGIN 开头就是 PEM,二进制乱码就是 DER / PFX / P7B。
PEM 是 Base64 编码的 ASCII 文本,头部带 -----BEGIN CERTIFICATE-----、-----BEGIN PRIVATE KEY----- 这类标记,Linux 上的 Nginx、Apache 直接使用它。常见的 .crt、.cer、.key、.pem 后缀,绝大多数内容都是 PEM。PFX 又叫 P12,是 PKCS#12 标准,二进制、需要密码、一个文件打包完整证书和私钥,Windows / IIS / Tomcat 主要使用它。P7B 是 PKCS#7,只有证书链、没有私钥,Windows 导出证书时很常见。DER 是纯二进制,.cer 在老式设备上经常是 DER 编码。
怎么把 crt(PEM)+ key 转成 pfx?
结论:一条 openssl pkcs12 -export 命令即可完成,必须用 -certfile 把中间证书一起打包进去,否则导出的 pfx 在 IIS 上会提示证书链不完整。
命令如下:
openssl pkcs12 -export \
-inkey server.key \
-in server.crt \
-certfile chain.crt \
-name "gj0.com" \
-out server.pfx
执行后会提示输入导出密码(Export Password),这个密码在导入端还要用一次。想改成非交互式,加 -passout pass:你的密码。-name 给证书起别名,多证书场景下便于区分。
pfx 怎么拆回 pem / crt / key?
结论:加 -nodes 让私钥不加密输出,再用 -nocerts、-clcerts、-cacerts 三个开关分别把私钥、站点证书、证书链抽取到独立文件,这是还原 Nginx 配置最稳的做法。
openssl pkcs12 -in server.pfx -nodes -out all.pem
# 或者分开抽
openssl pkcs12 -in server.pfx -nocerts -nodes -out server.key
openssl pkcs12 -in server.pfx -clcerts -nokeys -out server.crt
openssl pkcs12 -in server.pfx -cacerts -nokeys -out chain.crt
抽完记得按「站点证书 → 中间证书」的顺序拼全链:cat server.crt chain.crt > fullchain.crt。顺序反了、或者把根证书也塞进去,部分安卓机和 curl 会校验失败。私钥文件权限收到 chmod 600 server.key,属主给运行 Nginx 的账号。
pem 和 der、p7b 怎么互转?
结论:PEM 与 DER 之间用 x509 -inform/-outform 切换,PEM 与 P7B 之间用 crl2pkcs7 / pkcs7,转换过程不改变证书本身,SHA-256 指纹必然一致。
# PEM -> DER
openssl x509 -in cert.crt -outform der -out cert.der
# DER -> PEM
openssl x509 -in cert.der -inform der -out cert.pem
# PEM(含链)-> P7B
openssl crl2pkcs7 -nocrl -certfile fullchain.crt -out cert.p7b -outform DER
# P7B -> PEM
openssl pkcs7 -in cert.p7b -print_certs -out cert.pem
如果只做改后缀的事——比如 server.pem 直接重命名成 server.crt——内容不变,Nginx 照样读得动,因为这两个扩展名指向同一种 PEM 编码。
转换后怎么验证证书和私钥是否匹配?
结论:比对证书与私钥的公钥指纹是唯一可靠的匹配校验,RSA 证书比 modulus,ECC 证书比公钥。两边输出一致才算配对成功。
# RSA 证书
openssl x509 -noout -modulus -in server.crt | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
# 通用做法(RSA / ECC 都适用)
openssl x509 -noout -pubkey -in server.crt | openssl md5
openssl pkey -pubout -in server.key | openssl md5
顺带用 openssl x509 -in server.crt -noout -subject -dates -fingerprint -sha256 核一遍域名和到期时间。中文单位名在 OpenSSL 3.x 下会显示成转义串,加 -nameopt utf8,sep_multiline 就正常了。
最后提醒两个容易出错的情况:一是 PEM 文件换行必须是 LF,从 Windows 记事本另存过的证书常带 CRLF,Nginx 会报 PEM_read_bio_X509_AUX() failed,用 dos2unix server.crt 处理一下;二是用 -export 生成 pfx 时密码不能为空(部分 OpenSSL 版本允许空密码但 PHP、Java 端读取会失败),统一设成 8 位以上字符最省事。走完一遍转换,证书的 SHA-256 指纹和转换前完全一致,就说明整个流程没有损坏任何东西。