证书
证书格式详解: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 文件,把证书和私钥内容分别粘贴进去即可。