数字签名与证书:X.509 证书体系

数字签名和证书,是平台工程里最容易被忽视,但又最要命的基础设施。说白了,它就是系统的“身份证”加上“签名笔”。

我记得刚接手一个内部组件仓库时,发现所有开发者上传的包都没有签名。结果某天供应链攻击,一个恶意包被偷偷替换,下游三个服务全部中招。从那以后,我对签名和证书的敬畏心就拉满了。

X.509 证书体系

X.509 是公钥证书的国际标准格式。你看到的 HTTPS 证书、代码签名证书、客户端认证证书,底层都是它。

一个标准的 X.509 v3 证书包含以下核心字段:

字段 说明 个人踩坑点
版本号 (Version) 目前主流是 v3,支持扩展项 别用 v1/v2,扩展项都用不了
序列号 (Serial Number) CA 签发的唯一编号 我曾经遇到过序列号重复的诡异问题,后来发现是内部 CA 没做原子递增
签名算法 (Signature Algorithm) 如 sha256WithRSAEncryption SHA-1 已经在很多浏览器里被标记为不安全了
签发者 (Issuer) CA 机构的 DN 注意 DN 的层级顺序,区分根 CA 和中间 CA
有效期 (Validity) notBefore / notAfter 证书过期后的连锁故障,我经历过——整个 CI 全部挂掉
主体 (Subject) 证书持有者的 DN SAN(主题备用名称)比 CN 更重要,现在是标准做法
公钥 (Public Key) RSA/ECC 的公钥信息 ECC 的密钥短很多,性能也更好,我个人的新项目首选 ECDSA
扩展项 (Extensions) 如 Key Usage、CA、Subject Alt Name 这地方坑最多,用错了签名验证直接失败

核心原则:证书链的信任是传递的。你只信任根 CA,根 CA 签发中间 CA,中间 CA 再签发终端证书。这个链条一旦断裂,整个信任体系就崩了。

数字签名流程

我们聊聊签名到底怎么工作的。很多人觉得签名就是“加密”,其实不是。它是“哈希 + 私钥加密”的组合拳。

签名过程(你签)

  1. 对原始数据做哈希运算,得到摘要(比如 SHA-256)。
  2. 用你的私钥对这个摘要加密,得到数字签名。
  3. 把原始数据、签名、你的证书一起发给对方。

验签过程(对方验)

  1. 用你的证书里的公钥解密签名,得到摘要A。
  2. 对原始数据自己做哈希,得到摘要B。
  3. 比较A和B。相等则签名有效,数据未被篡改。

你想想看,如果数据被篡改了,哈希值肯定对不上。如果私钥泄露了,那签名就完全不可信了。所以我常说:私钥就是命根子。

# 用 OpenSSL 做签名和验签的典型命令
# 1. 生成私钥(RSA 2048位)
openssl genrsa -out private.pem 2048

# 2. 生成公钥
openssl rsa -in private.pem -pubout -out public.pem

# 3. 签名文件 data.txt
openssl dgst -sha256 -sign private.pem -out data.sig data.txt

# 4. 验签
openssl dgst -sha256 -verify public.pem -signature data.sig data.txt

提示:实际生产环境不要用 RSA 2048 了。我个人推荐 ECDSA 搭配 P-256 曲线,同等安全强度下,性能和密钥长度都更优。验签速度能快好几倍,尤其在 IoT 或移动端场景里非常明显。

代码签名实践

代码签名,就是把数字签名技术用在软件制品上。为什么需要代码签名?

  • 完整性:确保代码在传输或存储过程中没被篡改。
  • 来源可信:接收方能确认代码确实来自声称的发布者。
  • 平台要求:Windows、macOS、Android、iOS 都要求应用必须签名才能安装或运行。

我曾经遇到过一个场景:内部团队在 CI 里直接用开发人员本地的私钥签名。结果一个开发者的机器被入侵,攻击者拿到了私钥,开始在仓库里投毒。后来我们强制迁移到硬件安全模块(HSM)或云 KMS 管理私钥,才彻底解决了这个问题。

代码签名的标准流程

  1. 准备证书:从受信任的 CA 申请代码签名证书。个人或开发阶段可以用自签名证书,但生产环境必须用 CA 签发的。
  2. 保护私钥:私钥存放到 HSM、KMS 或安全密码箱里。绝不在 CI 的明文环境变量里放私钥。
  3. 计算哈希:对要签名的文件(如 .jar、.dll、.exe)计算哈希。
  4. 签名:用私钥对哈希签名,生成签名文件或嵌入到文件中(如 Authenticode 签名嵌入 PE 文件里)。
  5. 分发:将签名后的制品分发给用户或部署到服务器。

下面是一个典型的代码签名在 CI 中的集成示意:

数字签名与验签流程(代码签名场景) 签名方(发布者CI) 原始代码制品 SHA-256 哈希 → 摘要A 私钥加密摘要A → 签名 制品+签名+证书 验证方(用户/平台) 对代码做 SHA-256 → 摘要B 用证书公钥解密签名 → 摘要A 比对摘要A == 摘要B ? 签名验证结果 ✅ 摘要匹配 → 签名有效:代码未被篡改,发布者身份可信,可以安装或运行。 ❌ 摘要不匹配 → 签名无效:代码可能被篡改,或签名证书不匹配,拒绝执行。 ⚠️ 证书过期/吊销/不可信 → 即使签名匹配,信任链断裂,仍应拒绝。

警告:代码签名的私钥一旦泄露,攻击者可以用它签发任何恶意代码。你的整个软件供应链信誉会在几分钟内崩塌。我建议:

  • 使用硬件安全模块(HSM)或云 KMS(如 AWS KMS、Azure Key Vault)存储私钥。
  • 签名的操作在 CI 中通过 KMS API 完成,私钥永远不离开安全边界。
  • 私钥的访问权限严格管控,并开启审计日志。

另外,证书的有效期也要注意。很多团队签完代码后就不管了,等到证书过期才发现所有新版本都无法通过验证。更糟的是,如果证书被吊销(比如私钥泄露),之前用这个证书签的所有代码都会立刻变成“不受信任”的。

所以我的建议是:

  • 证书有效期不要太长。个人习惯:代码签名证书一年一换。
  • 建立证书到期前的自动告警机制。
  • 如果旧证书被吊销,尽快用新证书重新签名所有可执行文件。

提示:对于内部平台,可以搭建私有 CA 来签发内部代码签名证书。这样做的好处是:你可以完全控制信任链,不需要依赖外部 CA。但坏处是:你得自己管好根 CA 的私钥——一旦丢了,所有内部颁发的证书都得作废。