跳到主要内容
ecdsa.com

PEM vs DER:同一张证书的文本形式与二进制形式

DER 是定义证书和密钥所用的二进制 ASN.1 编码。PEM 就是同样的 DER,经过 base64 编码后放在 BEGIN 行与 END 行之间。转换不会改变其中的任何内容。

在 PEM 和 DER 之间选择,并不是在两种不同的数据之间选择:证书、密钥、CSR 或 CRL 始终定义为 DER 编码的 ASN.1,PEM 则是包在这些原样字节外面的一层文本封装。真正要弄清的是:某个工具、协议或平台需要哪种表示形式,以及如何识别和转换每一种形式。

概览

DERPEM
是什么ASN.1 的可分辨编码规则(Distinguished Encoding Rules,ITU-T X.690):二进制的标签-长度-值结构RFC 7468 文本格式:夹在标签行之间的 DER 的 base64 编码
开头证书和密钥以字节 0x30(SEQUENCE)开头-----BEGIN CERTIFICATE-----、-----BEGIN PRIVATE KEY----- 等
大小(P-256 证书,实测)389 字节583 字节(约为 4/3 倍,另加换行和标签)
每个文件中的对象数一个任意数量的块(证书链、密钥加证书)
自描述没有标签,类型要靠上下文判断标签写明结构类型
常见扩展名.der、.cer、.crt(有歧义).pem、.crt、.cer、.key、.csr(有歧义)
指纹对这些字节计算的 SHA-256值相同:指纹是对其中的 DER 计算的
可安全放入电子邮件、YAML、环境变量否是

两者分别是什么

DER 是 ASN.1 数据的规范二进制编码:每个值都由标签、长度和内容组成,而且每个值只有唯一一种有效编码。签名之所以可行,靠的正是这种规范性:证书的签名覆盖其 tbsCertificate 的 DER 字节,验证方不需要重新编码任何内容。

PEM(名称源于1993年的隐私增强邮件标准 Privacy-Enhanced Mail,RFC 7468 规定了它在 PKIX 对象上的用法)用标准 base64 包裹这些字节,按惯例每行 64 个字符,放在写明结构类型的 BEGIN 行和 END 行之间:CERTIFICATE、X509 CRL、CERTIFICATE REQUEST、PUBLIC KEY、PRIVATE KEY、ENCRYPTED PRIVATE KEY,以及 OpenSSL 的旧格式 RSA PRIVATE KEY 和 EC PRIVATE KEY。块之前、块之间和块之后的文本都会被忽略,带注释的证书包和 openssl x509 -text 的输出正是靠这一点才能正常使用。

转换与检查

下面的脚本用 OpenSSL 3.6.5 运行,创建一张临时证书,并表明两种编码承载的是同一个对象。

OpenSSL:同一张证书的 PEM 与 DER 形式
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl req -new -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout key.pem \
  -subj "/CN=example.com" -days 30 -out cert.pem 2>/dev/null
openssl x509 -in cert.pem -outform DER -out cert.der
printf 'cert.pem %s bytes, cert.der %s bytes\n' "$(wc -c < cert.pem | tr -d ' ')" "$(wc -c < cert.der | tr -d ' ')"
head -1 cert.pem
openssl x509 -in cert.der -inform DER -noout -fingerprint -sha256 | cut -c1-40
openssl x509 -in cert.pem -noout -fingerprint -sha256 | cut -c1-40
cd / && rm -rf "$dir"
输出
cert.pem 583 bytes, cert.der 389 bytes
-----BEGIN CERTIFICATE-----
sha256 Fingerprint=FE:8B:DC:19:30:EE:A8:
sha256 Fingerprint=FE:8B:DC:19:30:EE:A8:

同样的做法也适用于密钥(openssl pkey -in key.pem -outform DER)、CSR(openssl req -outform DER)和 CRL(openssl crl -outform DER)。要判断一个未知文件是哪种格式:如果它以 -----BEGIN 开头,就是 PEM;如果 xxd file | head -1 在开头附近显示 3082,那几乎可以肯定是 DER。

典型错误

  • OpenSSL 报出 no start line / Expecting: TRUSTED CERTIFICATE:工具需要的是 PEM,拿到的却是 DER,或者是标签不同的 PEM 块。加上 -inform DER,或者检查标签。
  • header too long / wrong tag:工具需要的是 DER,拿到的却是 PEM 文本,或者是被截断的文件。
  • 粘贴到网页表单或 YAML 中的 PEM 块里出现 Windows 换行符或多余的空格;严格的解析器会拒绝这样的块。应重新编码,而不是手工修改。
  • 在只需要一张证书的地方提供了 PEM 证书包:许多工具只读取第一个块,所以顺序很重要(服务器应把叶子证书放在最前面)。
  • 用了 Base64url 而不是 base64:PEM 使用带 +、/ 和填充的标准字母表;JOSE 和 WebAuthn 使用 base64url。从 JWK 的 x5c 成员中复制出来的证书是标准 base64 编码的 DER,给它加上 PEM 的首尾标签行,就成了有效的 PEM 文件。

ASN.1 查看器接受两种编码并显示其结构,PEM 证书包拆分工具可以拆分包含多张证书的文件。

证书包与证书链

DER 只能容纳一个对象,因此凡是需要在一个文件中放多个对象的场合,要么把 PEM 块拼接起来,要么使用容器格式。服务器的 fullchain.pem 是叶子证书后面跟着中间证书;/etc/ssl/certs/ca-certificates.crt 这样的 CA 证书包则是把数百张根证书放在一个 PEM 文件里。基于 DER 的替代方案是 PKCS #7 证书包(.p7b,即不含内容、只含证书的 CMS SignedData,在 Windows 上很常见),以及私钥要随证书链一起传递时使用的 PKCS #12(.pfx)。把 .p7b 转换为 PEM 的命令是 openssl pkcs7 -print_certs -in bundle.p7b,它会把每张证书分别写成一个 PEM 块。

顺序和完整性比编码更重要:服务器必须先发送叶子证书,再发送通往客户端所信任的根证书所需的每一张中间证书;缺了一张中间证书,不会自行获取它的客户端就会出错,与文件格式无关。

如何选择

  • Web 服务器、反向代理、ACME 客户端:PEM(fullchain.pem、privkey.pem)。
  • Java(keytool -importcert、CertificateFactory):两者皆可,但 DER 是规范的输入格式;密钥库用 PKCS #12 存放密钥及其证书链。
  • Windows 证书导入:DER 格式的 .cer 或 PEM 编码的 .cer;密钥加证书则放进 .pfx。
  • 嵌入源代码、环境变量、Kubernetes Secret:PEM 文本(如果某个字段要求,也可以用 base64 编码的 DER)。
  • 网络传输协议和二进制容器:始终是 DER;TLS、CMS、OCSP 和 X.509 扩展从不承载 PEM。
  • 归档与审计:需要逐字节精确保存时用 DER;需要人工查看文件时用 PEM。

迁移说明

从密码学意义上讲,这里没有什么需要迁移的;切换编码只是部署层面的细节。

  1. 统一文件命名:PEM 用 .pem,DER 用 .der,在自己的工具链中避免使用有歧义的 .crt 和 .cer。
  2. 在部署时转换,在仓库或机密存储中只保留一份权威来源(通常是 PEM)。
  3. 转换后检查指纹,使用 openssl x509 -noout -fingerprint -sha256;两者必须一致。
  4. 私钥要保持加密:除非指定加密算法,否则 openssl pkey -outform DER 写出的是未加密的密钥,而工具在处理 DER 密钥时给出的警告往往更少。
  5. 在配置中写明所需格式(ssl_certificate 需要 PEM;Java 信任库需要 DER 或 PKCS #12),因为大多数格式错误都源于猜测。

参考资料

全部对比