技术

理解PKI(二):从 ASN.1 到 DER,数字证书为什么是一串字节

从 ASN.1、DER、TLV 和 Base64 四个概念入手,解释 X.509 数字证书如何从抽象结构变成可传输、可签名、可验证的字节序列。

发布
阅读约 7 分钟
理解PKI(二):从 ASN.1 到 DER,数字证书为什么是一串字节
章节索引页面导航1 / 13
一、ASN.1:只描述结构,不负责保存数据1 / 13

上一篇文章讲清了 PKI 的整体框架:CA 把身份与公钥绑定在数字证书中,用户再沿着信任链验证这层绑定关系。

但当我们真正拿到一张证书时,看到的往往不是“持有者、公钥、有效期”这些整齐的字段,而是一段这样的文本:

-----BEGIN CERTIFICATE-----
MIIC...
-----END CERTIFICATE-----
text

或者干脆是一份编辑器无法正常打开的二进制文件。

这些内容与证书字段之间是什么关系?为什么同一张证书既可以保存成 .der,也可以保存成 .pem?Base64 是不是一种加密?要回答这些问题,需要把数字证书拆成四层:

ASN.1:定义数据结构

DER:把结构编码成唯一的二进制字节

Base64:把二进制转换成可打印文本

PEM:为 Base64 文本加上类型边界
text

这四层分别解决“数据是什么”“如何变成字节”“如何放进文本环境”和“这段文本是什么类型”的问题。

上图展示的是数字证书最常见的处理方式。RFC 7468 允许文本编码包装 BER 数据,但强烈推荐使用 DER;实际证书工具通常采用 DER。


一、ASN.1:只描述结构,不负责保存数据

ASN.1(Abstract Syntax Notation One,抽象语法记法)是一套与编程语言无关的数据描述语言。

它的作用有些像接口定义或数据结构声明:只规定一个对象由哪些字段组成、字段是什么类型、哪些字段可以省略,不关心 Java、Go 或 C 具体用什么类来实现。

ASN.1 将常用类型分为两类:

类型常见成员在证书中的用途
基本类型INTEGERBIT STRINGOCTET STRINGOBJECT IDENTIFIER、各种字符串和时间类型序列号、公钥、签名值、算法标识、名称和有效期
结构类型SEQUENCESETSEQUENCE OFSET OFCHOICE把多个字段组合成证书、名称、扩展项等复杂结构

例如,X.509 证书最外层可以简化为:

Certificate ::= SEQUENCE {
  tbsCertificate       TBSCertificate,
  signatureAlgorithm   AlgorithmIdentifier,
  signatureValue       BIT STRING
}
text

这段定义表达了三个事实:

  1. 一张证书是一个有序结构 SEQUENCE
  2. tbsCertificate 是待签名的证书正文;
  3. 正文之外还保存了签名算法和 CA 生成的签名值。

这里的 TBSTo Be Signed 的缩写。CA 真正签名的对象不是我们在证书查看器中看到的格式化文本,而是 tbsCertificate 编码后得到的那段确定字节。

ASN.1 是证书的“结构图纸”,不是证书文件本身。只有经过编码规则处理后,它才会变成可以保存和传输的字节。


二、BER 与 DER:同一份结构如何变成字节

ASN.1 只定义结构,还需要一套规则把结构转换成二进制。BER、DER、CER 和 PER 都是 ASN.1 的编码规则,其中 PKI 最常见的是 DER

BER 为什么还不够

BER(Basic Encoding Rules)允许同一个值存在多种合法编码方式。例如,某些长度可以使用不同的表示方法,结构既可以提前写明长度,也可以用结束标记表示内容结束。

普通数据交换只要双方都能解码,多种表示方式未必是问题;数字签名却要求更加严格。

签名计算面对的是字节序列。假设两个编码都表达同一个证书结构,但最终字节不同,那么它们计算出的摘要和签名也会不同。接收方重新编码时只要选择了另一种合法形式,验签就会失败。

DER 的核心价值是“唯一”

DER(Distinguished Encoding Rules)是 BER 的受限子集。它通过更严格的规则,要求同一个 ASN.1 值只能得到一种规范编码,例如:

因此,DER 真正解决的不是“能不能编码”,而是“能不能得到唯一字节”。

对密码系统而言,唯一编码非常重要:结构相同、编码就相同;编码相同、摘要和签名才有稳定的验证基础。


三、TLV:DER 字节流的阅读方法

DER 编码通常可以按 TLV 来理解:

Tag | Length | Value
类型 | 长度   | 内容
text

常见的 Tag 如下:

十六进制ASN.1 类型常见用途
02INTEGER证书序列号、RSA 大整数
03BIT STRING公钥、证书签名值
04OCTET STRING扩展项包装、二进制数据
06OBJECT IDENTIFIER算法、属性和扩展项标识
0CUTF8StringUTF-8 字符串
13PrintableString可打印字符名称
17UTCTimeRFC 5280 要求证书有效期在 2049 年及以前使用该类型
18GeneralizedTimeRFC 5280 要求证书有效期从 2050 年起使用该类型
30SEQUENCE / SEQUENCE OF有序复合结构
31SET / SET OF集合结构

一个 OID 的编码示例

对象标识符 OID 用一串数字唯一标识算法、名称属性或证书扩展项。例如 1.2.840.113549 是 RSA/PKCS 命名空间的一部分。

它经过 DER 编码后是:

06 06 2A 86 48 86 F7 0D
│  │  └───────────────┘
│  │       Value
│  └─ Length:6 字节
└──── Tag:OBJECT IDENTIFIER
text

第一段 06 表示这是 OID,第二段 06 表示内容长度为 6 字节,后面的字节才是 OID 各节点压缩后的值。

为什么正整数前面有时会多一个 00

DER 中 INTEGER 使用最高位表示正负。如果一个正整数编码后的首字节最高位恰好为 1,就必须在前面补一个 00,避免它被解释为负数。

十进制 128 的十六进制值是 80,完整 TLV 不是 02 01 80,而是:

02 02 00 80
text

这也是解析证书序列号或 RSA 大整数时,经常会看到前导零的原因。它不是数据被随意补齐,而是在保持数值符号正确。


四、X.500 Name:证书名称为何是层层嵌套的结构

证书签发者 issuer 和持有者 subject 都使用 X.500 的 Name 类型。它不是一段简单字符串,而是多层结构:

Name
└── RDNSequence                 SEQUENCE OF
    └── RelativeDistinguishedName  SET OF
        └── AttributeTypeAndValue  SEQUENCE
            ├── type               OBJECT IDENTIFIER
            └── value              按 type 决定具体类型
text

我们平时看到的名称:

C=CN, O=Example, CN=Test User 1
text

实际表达的是多个“属性类型 + 属性值”。其中 COCN 只是便于人阅读的简称,编码中使用的是 OID:

简称含义OID
C国家或地区2.5.4.6
O组织2.5.4.10
OU组织单元2.5.4.11
CN通用名称2.5.4.3

理解这层结构后,就能解释一些开发中容易误判的问题:


五、Base64 与 PEM:只是包装,不是加密

DER 得到的是二进制数据。二进制适合程序处理,却不方便放进邮件、配置文件或只接受文本的传输协议,于是有了 Base64

Base64 把每 3 个字节转换成 4 个可打印字符;末尾不足 3 个字节时使用 = 填充。因此编码后的体积通常会增加约三分之一。

Base64 只改变表示形式:

通常所说的 PEM,更准确地讲是 RFC 7468 定义的 PKIX 文本编码:它把 ASN.1 编码结果转换为 Base64,并在外层增加类型标签。

-----BEGIN CERTIFICATE-----
Base64 编码后的证书数据(通常为 DER)
-----END CERTIFICATE-----
text

不同标签代表不同对象:

CERTIFICATE          数字证书
CERTIFICATE REQUEST  证书签名请求
PRIVATE KEY          PKCS#8 私钥
PUBLIC KEY           公钥
text

在最常见的 DER 场景中,DER 决定二进制内容,Base64 负责文本化,PEM 负责标明边界和对象类型。RFC 7468 也允许包装 BER,但强烈推荐 DER。表示形式的转换不会改变证书字段、签名或信任关系。

文件扩展名只能作为提示,不能作为可靠判断依据。.cer 既可能是 DER 二进制,也可能是 PEM 文本;最直接的办法是查看开头是否存在 -----BEGIN,或交给证书工具识别。


六、从文件到字段:实际如何查看证书

OpenSSL 可以分别查看 PEM 和 DER 证书:

# 查看 PEM 证书字段
openssl x509 -in certificate.pem -text -noout

# 查看 DER 证书字段
openssl x509 -inform DER -in certificate.der -text -noout
bash

如果希望观察 TLV 的嵌套结构,可以使用:

openssl asn1parse -in certificate.pem -inform PEM -i
bash

这些命令只负责解析和展示证书,不会自动证明证书可信。需要验证证书链时,应显式提供信任锚和中间证书:

openssl verify \
  -CAfile trust-anchor.pem \
  -untrusted intermediate.pem \
  certificate.pem
bash

这条 verify 命令用于构建并验证证书链,但默认不会执行 OCSP 查询;如果要检查 CRL,还需提供 CRL 数据并显式启用 -crl_check-crl_check_all。因此命令返回成功也不能单独证明证书当前未被吊销。

asn1parse 输出中的偏移、头部长度和内容长度对应 DER 结构的嵌套关系与 TLV 边界;verify 在链验证失败时则会显示出错证书的深度。遇到证书解析或验证失败时,可以按以下顺序排查:

  1. 文件究竟是 PEM 还是 DER;
  2. PEM 的头尾标签是否匹配,Base64 是否完整;
  3. DER 的 Length 是否与 Value 长度一致;
  4. ASN.1 类型与协议定义是否一致;
  5. 证书签名算法、正文签名算法是否一致;
  6. 解析成功后,再检查有效期、用途、吊销状态和信任链。

“能解析”只说明编码结构合法,不代表证书可信;“签名值正确”也只说明内容未被签发后修改,还需要验证签发者是否受信任、证书是否在有效期内、是否已被吊销、用途是否允许当前操作。


七、把四层关系重新串起来

现在再看一张 X.509 证书,它的形成过程就很清楚了:

1. X.509 使用 ASN.1 定义证书字段和嵌套结构
2. CA 按 DER 规则把 tbsCertificate 编成唯一字节
3. CA 对这段字节计算摘要并生成签名
4. tbsCertificate、签名算法和签名值共同组成 Certificate
5. 常见实现将整张 Certificate 保存为 DER
6. 需要文本传输时,对证书数据进行 Base64 编码并添加 PEM 类型边界;标准允许 BER,但强烈推荐 DER
text

这里最值得记住的不是某个 Tag 的具体数值,而是每一层的职责:

ASN.1 管结构,DER 管唯一字节,Base64 管文本表示,PEM 管对象边界。

分清这四层之后,PEM、DER、.cer、Base64 就不再是互相混杂的文件名词,而是一条从抽象证书结构到实际文件的编码流水线。

下一篇可以沿着这条流水线继续向里走:拆开 tbsCertificate,逐项理解版本号、序列号、签发者、持有者、公钥信息,以及 X.509 v3 扩展项如何限制一张证书“能做什么”。


参考资料


系列文章

笔记整理自《PKI_CA与数字证书技术大全》第二部分“PKI 技术基础”,并结合第三部分的 X.509 证书格式重新组织。


正在加载留言…


下一篇
Mac 下用 Git 提交自动生成 Obsidian 工作日报

搜索文章...

⌘K / Ctrl K

使用上下方向键选择结果,按 Enter 打开,按 Esc 关闭

搜索结果