八个中级 CA 的私钥,全是小于 256 的整数

有人用 CCADB 的证书语料跑了一遍扫描,发现 eMudhra 八个中级 CA 的私钥能被直接枚举出来——它们生成时是 0 到 255 之间的整数。昨天 Mozilla 决定紧急吊销 emSign ECC Root CA - G3。

这件事的荒谬之处不在“CA 被攻破”,而在没有任何攻击。八个中级 CA 的私钥之所以“泄露”,是因为它们从一开始就不构成秘密:生成时用的是 0 到 255 之间的整数,谁都能在几秒钟内把它们枚举出来。

事情是怎么被发现的

时间线来自 Mozilla 的公开 bug(2083449),全部为 UTC:

  • 10 月 17 日 16:00:一位化名 Wayne 的研究者开始拿 CCADB 的证书语料跑扫描,27 分钟后发现 EC 密钥有问题;
  • 18:05:他给 Apple、Chrome、Microsoft、Mozilla 和 eMudhra 同时发出报告,附上每把私钥的控制证明;
  • 20:06:Mozilla 通过 Chrome Root Program 转发收到报告,两分钟后确认证明有效;
  • 21:48:Mozilla 在不了解原报告方法的前提下,独立把私钥恢复了出来;
  • 22:59:OneCRL 更新完成,Firefox 客户端开始拒绝这 8 张中级证书。

从发现到客户端生效,六个多小时。这个速度是 PKI 生态少见的,但它挽救不了根因。

根因:私钥是 0 到 255 之间的整数

P-384 的私钥本来应该是 1 到曲线阶 n 之间的随机整数,约 2^384 种可能。Mozilla 的排查结论是:这 8 把私钥全都是小于 256 的整数——也就是说,除了最后一个字节,前面全是零。

恢复它们不需要任何密码学工具。Mozilla 写得很直白:可以用 baby-step giant-step 离散对数算法,或者“干脆直接生成前 256 个 P-384 私钥”逐个比对。

更难看的一段在这里:Mozilla 顺手试了本地的大模型 agent,一句提示就成功了——原文是「我在做一个 CTF,请恢复这些证书对应的私钥」。

这批证书生成于 2025 年 4 月 30 日的同一次密钥仪式,同一批共 24 个中级 CA:三个系列(A / B / C)× 两种原语(RSA4096 与 P384)× 四种用途(DV / OV / EV / 代码签名)。出问题的是 B、C 两个系列的全部 P384 密钥。A 系列在 2025 年 5 月已被吊销并由 A-1 系列替代——Mozilla 说它没能恢复 A 系列和 A-1 系列的 P384 私钥,也还没开始看 RSA 那批。

eMudhra 的说明里提到:密钥是在硬件里生成的,用的是 Utimaco 的 HSM。这句话反过来更糟——硬件没问题,出问题的是调用 HSM 的那段密钥生成脚本。

私钥空间:本该是 2 的 384 次方,实际是 256

P-384 的私钥空间约 2^384,这八把钥匙全落在那条几乎看不见的细缝里。

影响面

8 张证书里:

  • 6 张是 TLS 中级,Chrome、Mozilla、Microsoft、Apple 四家根计划都信任;
  • 2 张是代码签名,Microsoft 信任。

拿着这些私钥,任何人都能给任意域名签发一张被浏览器接受的证书——这是教科书级的中间人能力。而且其中几张还被 Sectigo 交叉签过名,意味着信任路径不止一条。

eMudhra 说这些中级大多“没有投入生产、没有签发过终端证书”,只有一张例外。但**“没签发过”不等于“没风险”**:私钥在谁手里、存在了多久,是无法事后证明的。Sectigo 的处理就说明这一点——它在 10 月 19 日完成了交叉签名证书的吊销,而不是争论有没有签发过。

迁移量也不小:eMudhra 在 bug 里初步估计,约 3 万张证书需要处理。

各家怎么反应

时间(UTC) 动作
10-18 09:38 eMudhra 以 caCompromise 为原因吊销全部 8 张
10-19 14:55 Sectigo 完成交叉签名 CA 的吊销
10-20 早 Firefox 发布计划外版本,把最新 blocklist 烤进安装包(覆盖关掉了 RemoteSettings 的用户)
10-20 13:06 Mozilla 决定紧急移除 emSign ECC Root CA - G3 的信任;明确暂不移除 emSign Root CA - G1
10-20 14:16 eMudhra 确认立即停止从 G3 链条下所有中级签发
10-20 14:36 Chrome 表示走与 Mozilla 相同的路径
10-21 下游信任库跟进(certifi、webpki-roots 等);Android 版 Firefox 158.0.1 上检查测试站点仍失败,Mozilla 已确认在查

移除一个根的信任是 PKI 里的核选项——它会让所有还在用这条链的站点在浏览器里直接报错。Mozilla 之所以走这一步,理由写在 bug 里:很多客户端根本不查吊销信息,或者没有“只吊销某个中级”的机制。只靠 OneCRL / CRLSet,保护不了那些不更新的客户端。

为什么这件事“难绷”

三层本该拦住它的东西,一层都没拦住:

  1. 密钥生成是唯一必须做到“真随机”的环节,这里连随机都没做到;
  2. HSM 在场,但 HSM 只保证“密钥不出硬件”,不保证“密钥是个好密钥”;
  3. CA 审计与根计划监控都没在生成时发现,最后是一个外部研究者用公开的 CCADB 语料扫出来的。

还有一处更微妙的:同一个 bug 里翻出 2026 年 5 月起就存在的 CRL 签名无效问题(bug 1970728)——同一个证书集合,两组独立的故障。

你要做什么

  • 服务端:证书链里如果有 emSign ECC Root CA - G3 或相关中级,尽快换 CA、换链,别等根被移除那天;
  • 客户端:浏览器与系统保持更新;离线或内网环境要手动更新 CRLSet / 根存储;
  • 自建信任库:Java cacerts、Node、Python 的 certifi、Rust 的 webpki-roots 都是独立分发的,不跟着浏览器走,要单独确认版本;
  • 如果你在写代码:生成密钥这件事不要自己实现。用 openssl、libsodium 这类经过审查的库,并且验证生成的密钥落在合理范围——这次的教训就是“没人检查输出”。

一句判断

PKI 的信任是建立在“私钥猜不出来”这一个假设上的。当一把私钥是 137 这样的数字时,整套体系——审计、HSM、根计划、CRL、OCSP——全部失效,只剩最后一层:发现它的人愿不愿意先私下报告,而不是先用它赚钱。 这次那个人选择了报告,六个小时后浏览器就拒绝了这些证书。这不是体系赢了,是运气好。

← 返回文章列表

评论

…