证书

证书格式详解:PEM、DER、PFX、P7B、JKS 的区别

作者: · 最后核实

证书文件在两个维度上互不相同:编码方式(二进制 DER 还是 Base64 文本 PEM),以及容器(单张证书、只装证书的 PKCS#7 包、带私钥并用密码保护的 PKCS#12 包,或者 Java 密钥库)。Nginx、Apache 和大多数 Linux 软件要 PEM;Windows 和 IIS 要 PKCS#12(.pfx);Java 能用 PKCS#12,老系统还在用 JKS。扩展名只是参考,转换之前先看清文件内容。

编码和容器是两回事

证书本身永远是 RFC 5280 定义的同一个 X.509 结构,各种格式只是改变它的书写方式和随行内容。实际工作中会碰到下面五种:

格式 编码 能装什么 含私钥? 常见扩展名 规范
DER 二进制 ASN.1 一张证书、一把私钥或一个 CRL 只能单独成文件 .der、.cer、.crt ITU-T X.690、RFC 5280
PEM 带首尾标记行的 Base64 文本 任意多张证书、私钥、CSR、CRL 可以,单独一段 .pem、.crt、.cer、.key RFC 7468
PKCS#7 DER 或 PEM 证书和 CRL 不能 .p7b、.p7c RFC 2315
PKCS#12 二进制 私钥 + 证书 + 证书链,用密码加密 能 .pfx、.p12 RFC 7292
JKS 二进制,Java 专有 密钥库里的私钥和证书 能 .jks、.keystore Java 文档

用分层的方式看更清楚:

X.509 证书(ASN.1 结构,RFC 5280)
 |
 +-- 写成二进制 DER ................................ .der / .cer
      |
      +-- Base64 + "-----BEGIN CERTIFICATE-----" ..... PEM(.pem / .crt)
      +-- 几张证书装进一个信封 ...................... PKCS#7(.p7b)
      +-- 证书 + 私钥,用密码保护 .................... PKCS#12(.pfx / .p12)

PEM:大多数服务器用的文本格式

PEM 就是把 DER 做 Base64 编码、每 64 个字符换行,再夹在标明内容的 BEGIN 行和 END 行之间。RFC 7468 把沿用了几十年的标签正式标准化:

标签 内容
CERTIFICATE X.509 证书
PRIVATE KEY 未加密的 PKCS#8 私钥(任何算法)
ENCRYPTED PRIVATE KEY 带密码的 PKCS#8 私钥
RSA PRIVATE KEY / EC PRIVATE KEY 较老的按算法区分的私钥格式(PKCS#1、SEC 1)
CERTIFICATE REQUEST CSR 证书签名请求
X509 CRL 证书吊销列表
PKCS7 PEM 形式的 PKCS#7 包

从 OpenSSL 3.0 起,openssl genrsa 默认输出 -----BEGIN PRIVATE KEY-----(PKCS#8),不再是老的 RSA PRIVATE KEY,Nginx 和 Apache 两种都认。

PEM 是文本,所以可以直接拼接。fullchain.pem 就是网站证书后面接上中间证书;HAProxy 则要求把证书、中间证书和私钥放进同一个文件。PEM 常见的问题有:Windows 换行符、编辑器自动加的 BOM、复制粘贴时两段之间丢了换行、-----END 行被截断。在 Windows 记事本里编辑过再上传到 Linux 的证书尤其容易出这些问题。块外面的多余文字,比如 openssl pkcs12 导出时写的 Bag Attributes,基于 OpenSSL 的服务器会直接忽略。

DER:二进制形式

DER 是 ASN.1 结构的标准二进制编码。DER 证书几乎总是以字节 30 82 开头,表示一个长度占两字节的 ASN.1 SEQUENCE。Windows 导出证书时同时提供“DER 编码二进制 X.509 (.CER)”和“Base64 编码 X.509 (.CER)”两种选项,所以两个都叫 .cer 的文件可能是不同格式。Java 的 keytool -exportcert 默认也输出 DER,加 -rfc 才是 PEM。

# DER 转 PEM,再转回来
openssl x509 -inform der -in certificate.cer -out certificate.pem
openssl x509 -in certificate.pem -outform der -out certificate.der

PKCS#7(.p7b):只有证书,没有私钥

PKCS#7 包把证书(也可以带 CRL)装进一个不带签名、不带私钥的 signed-data 信封。一些 CA 用这种格式交付中间证书链,Windows 和 Java 可以一步导入。因为 .p7b 里永远没有私钥,单靠它无法配置服务器。

# 把 P7B 里的所有证书导出为 PEM
openssl pkcs7 -in chain.p7b -print_certs -out chain.pem
# 用 PEM 证书链生成 P7B
openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b

PKCS#12(.pfx/.p12):私钥和证书链装进一个加密文件

PKCS#12 用来把私钥连同证书和证书链一起搬运。它把每一项放进一个“袋子”(bag),用由密码派生的密钥加密私钥(通常也加密证书),再用 MAC 保护整个文件。Windows、IIS、Azure 以及许多 Java 和 macOS 工具都能直接导入。PFX 这个名字来自微软更早的 Personal Information Exchange 格式,PKCS#12 正是在它的基础上发展来的。

OpenSSL 3.0 改了默认算法

OpenSSL 3.0 改变了 openssl pkcs12 -export 保护新文件的方式。老版本用 RC2-40 或 3DES 加 SHA-1,OpenSSL 3.0 把这些算法挪进了 legacy 提供程序。在 OpenSSL 3.x 下默认结果如下:

openssl pkcs12 -export -inkey example.com.key -in example.com.crt \
  -certfile intermediate.pem -out example.com.pfx
openssl pkcs12 -info -in example.com.pfx -noout
MAC: sha256, Iteration 2048
MAC length: 32, salt length: 16
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256

这会在两个方向上引出问题:

  • 老程序读不了新文件。 较老的 Windows Server、较早的 Java 8 更新版本和老版本 macOS 钥匙串,可能拒绝 AES-256 的 PKCS#12 文件,而且往往报一个误导人的“密码错误”。用 -legacy 生成即可:私钥用 3DES、证书用 RC2、MAC 用 SHA-1。
  • OpenSSL 3 读不了老文件。 打开多年前由 Windows 或 OpenSSL 1.x 导出的 PFX 时,会报 unsupported 并提到 RC2-40-CBC。在命令里加 -legacy 加载 legacy 提供程序就行。
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...
Algorithm (RC2-40-CBC : 0), Properties ()

另外,-nodes 从 OpenSSL 3.0 起已废弃,“导出的私钥不加密”的新写法是 -noenc。两个方向的现成命令见 PKCS#12 命令手册。

JKS 与 Java 密钥库

JKS 是 Java 专有的密钥库格式,完整性保护较弱,现代 Java 默认已经不再生成它。从 JDK 9(JEP 229)起,默认密钥库类型是 PKCS12,也就是说用 OpenSSL 生成的 .p12 文件可以直接当 Java 密钥库用。转换已有的 JKS:

keytool -importkeystore -srckeystore server.jks -srcstoretype jks \
  -destkeystore server.p12 -deststoretype pkcs12
keytool -list -v -keystore server.p12

每次打开 JKS 文件,keytool 都会提示建议迁移到 PKCS12。只有老版本的 Servlet 容器和部分设备还要求 JKS,其他情况优先用 PKCS12。国内不少老项目(如老版本 Tomcat、部分国产中间件)配置里仍写着 .jks,迁移前先确认软件版本是否支持 PKCS12。

私钥的格式

私钥也有自己的格式,私钥格式不对是服务器起不来的常见原因。日常会见到三种 PEM:

首行 结构 说明
BEGIN PRIVATE KEY PKCS#8,未加密 OpenSSL 3.x 对所有密钥类型的默认输出
BEGIN ENCRYPTED PRIVATE KEY PKCS#8,带密码 服务器启动时需要密码
BEGIN RSA PRIVATE KEY / BEGIN EC PRIVATE KEY PKCS#1 / SEC 1 老版本 OpenSSL 的输出,仍被广泛支持

openssl pkey 可以在它们之间转换,密钥本身不变:

# 任意支持的私钥转成未加密的 PKCS#8
openssl pkey -in old.key -out new.key
# 查看算法和长度
openssl pkey -in new.key -noout -text | head -1

Nginx 只有通过 ssl_password_file 提供密码才能读取加密私钥,所以大多数部署让私钥保持不加密,靠文件权限保护(chmod 600、属主 root)。PKCS#12 文件也一样:密码只在传输途中起保护作用,拿到文件和密码的人就拿到了私钥。密码和文件要通过不同渠道发送,也不要把私钥或 PFX 上传到会在服务器端处理的网站。

如何识别来路不明的文件

先看开头的字节,再让 OpenSSL 按你猜测的格式解析,别信扩展名。

看到的内容 格式 验证命令
-----BEGIN CERTIFICATE----- PEM 证书 openssl x509 -in f -noout -text
-----BEGIN PKCS7----- PEM 形式的 PKCS#7 openssl pkcs7 -in f -print_certs
-----BEGIN PRIVATE KEY----- PEM PKCS#8 私钥 openssl pkey -in f -noout -text
-----BEGIN CERTIFICATE REQUEST----- PEM CSR openssl req -in f -noout -text
二进制,以 30 82 开头 DER 证书、PKCS#12 或 DER 形式的 PKCS#7 依次试 x509 -inform der、pkcs12 -info、pkcs7 -inform der

Linux 和 macOS 的 file 命令能认出其中几种:

file leaf.der site.pfx chain.p7b leaf.pem
leaf.der:  Certificate, Version=3
site.pfx:  data
chain.p7b: ASCII text
leaf.pem:  PEM certificate

PKCS#12 文件只会显示为 data,所以二进制文件如果不是 DER 证书,下一步就试 openssl pkcs12 -info -in 文件 -noout。公开证书也可以直接粘贴到 证书解码工具 里自动识别。

格式之间的转换

大多数转换只要一条 OpenSSL 命令,私钥不必离开你的电脑。速查表:

从 到 命令
DER PEM openssl x509 -inform der -in cert.der -out cert.pem
PEM DER openssl x509 -in cert.pem -outform der -out cert.der
P7B PEM openssl pkcs7 -in chain.p7b -print_certs -out chain.pem
PEM P7B openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b
PEM + 私钥 PFX openssl pkcs12 -export -inkey key.pem -in fullchain.pem -out site.pfx
PFX PEM openssl pkcs12 -in site.pfx -out all.pem -noenc
JKS PKCS12 keytool -importkeystore -srckeystore a.jks -destkeystore a.p12 -deststoretype pkcs12

带输出示例和边界情况的版本见 格式转换命令手册,常见转换也可以用浏览器里的 证书格式转换工具。转换之后,用 私钥匹配检查 确认私钥和证书仍然配对;不配对时 Nginx 报的就是 key values mismatch。

各种服务器要什么格式

按加载证书的软件来选格式:

平台 证书 私钥 证书链
Nginx PEM(ssl_certificate) PEM(ssl_certificate_key) 放在证书文件里,紧跟网站证书
Apache 2.4.8+ PEM(SSLCertificateFile) PEM(SSLCertificateKeyFile) 放在证书文件里;SSLCertificateChainFile 已废弃
HAProxy 一个 PEM 文件 同一文件 同一文件
IIS / Windows 导入 PKCS#12(.pfx) 在 PFX 里 在 PFX 里
Tomcat PKCS#12 或 JKS 密钥库;较新版本也能直接用 PEM 在密钥库或 PEM 里 在密钥库或 PEM 里
Node.js PEM 字符串(cert、key)或 PFX(pfx) PEM 或 PFX 追加在 cert 里
宝塔面板 粘贴 PEM(“证书(PEM格式)”框) 粘贴 PEM(“密钥(KEY)”框) 粘在证书框里,紧跟网站证书
云负载均衡、CDN 一般分别粘贴 PEM 证书、证书链和私钥 PEM 单独字段或附在证书后

阿里云、腾讯云等平台的证书下载页会按服务器类型给出不同的压缩包:Nginx 包是 PEM 证书(含证书链)加私钥文件,Apache 包把证书和证书链分成两个文件,IIS 包是 .pfx 加一个存密码的文本文件,Tomcat 包是 .pfx 或 .jks 加密码文件。它们是同一张证书装在不同的容器里。不论下载哪一种,证书链都必须完整,原因见 证书链详解。

常见问题

.crt 和 .pem 是一回事吗?

多数时候是,但不一定。两个扩展名都常用来存 PEM 文本,可也有 CA 和 Windows 工具把二进制 DER 证书存成 .crt 或 .cer。用文本编辑器打开:开头是 -----BEGIN CERTIFICATE----- 就是 PEM,一堆乱码就多半是 DER。

密码明明没错,Windows 或 Java 导入 .pfx 时却提示密码错误?

这个文件多半是 OpenSSL 3.x 生成的,默认用 AES-256 和 PBKDF2 加密。一些老版本的导入程序读不了这种算法,就笼统地报“密码错误”。用 openssl pkcs12 -export -legacy ... 重新生成即可(私钥用 3DES、证书用 RC2、MAC 用 SHA-1)。

.p7b 文件里有私钥吗?

没有。PKCS#7 只装证书和 CRL。要迁移私钥,请用 PKCS#12(.pfx/.p12)或单独的 PEM 私钥文件。

从阿里云、腾讯云下载证书时该选哪种服务器类型?

按你实际用的软件选:Nginx 选 Nginx(PEM 证书加私钥),Apache 选 Apache,IIS 选 IIS(.pfx 加密码文件),Tomcat 选 Tomcat(.pfx 或 .jks 加密码文件)。宝塔面板选 Nginx 或“其他”下载的 PEM 文件,把证书和私钥内容分别粘贴进去即可。

资料来源

  1. RFC 7468:PKIX、PKCS、CMS 结构的文本编码(PEM)
  2. RFC 7292:PKCS #12 v1.1
  3. RFC 2315:PKCS #7 v1.5
  4. RFC 5280:X.509 证书与 CRL 规范
  5. OpenSSL 3.0 手册:openssl-pkcs12
  6. OpenSSL 3.0 迁移指南
  7. OpenJDK JEP 229:默认创建 PKCS12 密钥库