数字证书基础:X.509 证书的里里外外

聊 TLS,绕不开数字证书。说白了,证书就是网络世界里的身份证。你访问一个网站,怎么确定它真的是那个网站,而不是中间人冒充的?靠的就是这张证书。

我刚开始接触 HTTPS 时,总觉得证书就是个加密文件。后来排查过几次证书问题,才真正理解它的结构有多重要。今天咱们就把 X.509 证书拆开看看。

什么是 X.509 证书?

X.509 是国际电信联盟(ITU-T)定义的一个标准。它规定了公钥证书的格式。你平时在浏览器里看到的 SSL/TLS 证书,绝大多数都是 X.509 证书。

你可以把它理解成一个数字信封。里面装着持有人的公钥,还有一堆元数据。谁发的、发给谁的、什么时候过期、怎么验证——全写在这张证书里。

一句话总结:X.509 证书 = 公钥 + 身份信息 + 颁发者签名。三者缺一不可。

证书的组成结构

一张 X.509 v3 证书,包含以下核心字段。我习惯把它们分成三组来看:

字段组 字段名 说明
身份信息 Subject(主体) 证书持有者,比如域名 example.com
Issuer(颁发者) 签发这张证书的 CA 名称
Validity(有效期) notBefore 和 notAfter,起止时间
密码学材料 Subject Public Key Info 公钥算法 + 公钥值(RSA/ECC)
Signature(签名) CA 用私钥对证书内容的签名值
扩展 X.509 v3 Extensions SAN、Key Usage、CA 标志等

这里有个坑,我提醒一下:签名不是证书持有者自己签的,是 CA 签的。你想想看,如果自己签自己,那跟伪造有什么区别?CA 的签名,才是信任的锚点。

公钥、签名、颁发者、有效期——逐个拆解

公钥

这是证书的核心资产。TLS 握手时,客户端用证书里的公钥加密一个随机数,服务器用私钥解密。只有持有对应私钥的人,才能完成握手。

公钥算法现在主流是 RSA 2048 位或 ECDSA P-256。我个人更倾向 ECDSA,密钥短、性能好。但很多老系统还在用 RSA,兼容性更好。

签名

CA 把证书里的所有字段(除了签名本身)做一次哈希,然后用 CA 的私钥加密这个哈希值,得到签名。验证时,用 CA 的公钥解密签名,跟重新计算的哈希对比。一致就说明证书没被篡改。

我曾经遇到过一个案例:证书的 Subject 被改了一个字母,但签名没变。客户端验证签名时直接报错。嗯,这就是签名的作用——防篡改。

颁发者

Issuer 字段告诉你是谁签发了这张证书。浏览器拿到证书后,会去本地信任存储里找 Issuer 对应的根证书。找不到?那就报“证书不受信任”。

这里有个常见误区:Issuer 不是域名,是 CA 的名称。比如 Let's Encrypt 的 Issuer 是 "R3"。你可以在证书详情里看到。

有效期

每张证书都有生效时间和失效时间。浏览器在验证时,会检查当前时间是否在有效期内。过期证书直接拒绝。

我记得有一次线上告警,排查了半天,发现是证书过期了。从那以后,我都在证书到期前 30 天设置自动续期提醒。别等到过期再处理,那太被动了。

注意:有效期不是越长越好。业界现在推荐 90 天以内的短寿命证书。原因很简单:万一私钥泄露,短有效期能缩小损失窗口。Let's Encrypt 就是 90 天,自动续期。

证书编码格式:DER 与 PEM

证书在磁盘上怎么存?两种主流格式:DER 和 PEM。我经常看到有人搞混,其实区别很简单。

特性 DER PEM
编码方式 二进制(ASN.1 DER) Base64 文本
可读性 不可读,乱码 可读,有头尾标记
常见后缀 .der .crt .cer .pem .crt .cer .key
典型用途 Windows、Java 密钥库 OpenSSL、Nginx、Apache

PEM 格式长这样:

-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIU...
(中间是一长串 Base64 编码)
-----END CERTIFICATE-----

DER 格式就是上面那段 Base64 解码后的原始二进制。你可以在 OpenSSL 里互相转换:

# DER 转 PEM
openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM

# PEM 转 DER
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER

我个人习惯用 PEM。原因很简单:文本格式,可以 cat 查看,可以复制粘贴,不容易损坏。DER 是二进制,传输出错很难排查。

小技巧:用 openssl x509 -in cert.pem -text -noout 可以查看 PEM 证书的完整内容。包括公钥、签名算法、有效期、扩展字段等。排查问题时,这个命令我几乎天天用。

X.509 证书结构图

下面这张图,把证书的核心结构画出来了。你可以对照着看,每个字段在证书里的位置和关系。

X.509 证书结构图 X.509 v3 证书结构 版本号 (Version) v1 / v2 / v3(主流是 v3) 序列号 (Serial Number) CA 分配的唯一整数标识 签名算法 (Signature Algorithm) 如 sha256WithRSAEncryption 颁发者 (Issuer) 签发此证书的 CA 名称 有效期 (Validity) notBefore → notAfter 主体 (Subject) 证书持有者,如 CN=example.com 主体公钥信息 (Subject Public Key Info) 算法 + 公钥值 扩展 (Extensions) [v3 可选] SAN、Key Usage、CA:TRUE 等 CA 签名值:对以上所有字段的签名

你看,从版本号到扩展,每一段都是明文。唯独最后的签名值,是 CA 用私钥加密的。验证时,浏览器会重新计算前面所有字段的哈希,跟签名值解密后的哈希对比。对得上,证书就是真的。

避坑指南:我踩过的几个坑

  • 证书链不完整:我曾经只配置了服务器证书,没配中间证书。结果 Android 手机访问正常,iOS 报错。原因是 iOS 不自动下载中间证书。解决方案:把服务器证书和中间证书合并成一个 .pem 文件。
  • SAN 字段缺失:老证书可能只写了 Common Name,没写 Subject Alternative Name。Chrome 58 以后强制要求 SAN。我遇到过客户拿着一张只有 CN 的证书来找我,说 Chrome 报“不安全”。没办法,只能重新签发。
  • 编码混淆:有一次我把 DER 格式的证书直接传给了 Nginx,Nginx 报错说“bad PEM”。检查才发现,Nginx 只认 PEM 格式。用 OpenSSL 转一下就好了。

重要提醒:永远不要在生产环境手动修改证书文件。改一个字节,签名就失效了。证书的完整性靠签名保证,不是靠文件权限。如果你需要更新证书,找 CA 重新签发。

好了,X.509 证书的基础就聊到这里。你理解了证书的结构,后面讲证书链验证时,就能明白每一步在做什么。记住:公钥是核心,签名是保障,有效期是约束,编码格式是工具。把这四点吃透,证书这块就稳了。