印度证书机构 eMudhra 的八张中间 CA 私钥,任何人都能在笔记本电脑上瞬间恢复:八张 P-384 私钥全部是小于 256 的整数,安全研究者 Scott Helme 从公开证书出发逐一验证,每张都在一秒内还原。10 月 17 日研究者向四大根程序报告,当天晚些时候 Chrome 与 Firefox 完成封锁,10 月 20 日 Mozilla 公开披露并移除根证书。主流浏览器用户两天内已经安全,但 curl、Python、Go、CI 流水线和固件不更新的嵌入式设备面对的是另一套时间表。

八张证书,八个小于 256 的私钥
先看 CA 私钥在数学上是什么。eMudhra 这八张中间证书用的是椭圆曲线 P-384。私钥就是一个整数 d,证书里公开的公钥是 d×G,其中 G 是曲线上所有人共用的固定基点。从 d 算 d×G 很快,从公钥反推 d 在 d 足够随机时被认为不可行,这正是整套信任体系的数学地基。P-384 的私钥应该是 384 位随机数,搜索空间约 2³⁸⁴,暴力枚举在宇宙热寂之前都跑不完。
eMudhra 的八张私钥长这样:
d = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004前面 47 个字节全是 0,只有一个字节有值。八张证书对应的有效私钥分别是 4、56、66、115、155、156、230、237,全部小于 256。攻击不需要任何数学突破:从 1×G 开始逐个试到 255×G,与证书里的公钥比对,最多 255 步必然命中。Scott Helme 把 Mozilla 公告附件里的 16 张证书(8 把密钥各有 eMudhra 自签和 Sectigo 交叉签名两个版本)全部跑了一遍,全部瞬间还原:
| 中间 CA | 用途 | 私钥 d |
|---|---|---|
| EM DV TLS CA - G3B | TLS | 156 |
| EM DV TLS CA - G3C | TLS | 230 |
| EM EV TLS CA - G3B | TLS | 115 |
| EM EV TLS CA - G3C | TLS | 56 |
| EM OV TLS CA - G3B | TLS | 155 |
| EM OV TLS CA - G3C | TLS | 4 |
| EM OV CS CA - G3B | 代码签名 | 66 |
| EM OV CS CA - G3C | 代码签名 | 237 |
更麻烦的是,从证书上完全看不出问题。d=4 导出的公钥是曲线上一个完全合法的点,编码正确、曲线参数正确,所有结构性检查都能通过。想知道私钥是不是 4,唯一的办法就是试。这意味着任何依赖「证书看起来正常」的防护都拦不住这种攻击面。
一场脚本事故:两层密钥与相似标签
Mozilla CA 事件单里,eMudhra 给出了目前最完整的解释。2025 年 4 月 30 日的同一次密钥仪式(Key Generation Ceremony)批量生成 24 张中间证书,分三个系列(A/B/C)、两种算法(RSA-4096 与 P-384)、四种用途(DV/OV/EV/代码签名)。出事的八张恰好是 B、C 两个系列的全部 P-384 密钥。这些证书本来的用途是部署到欧洲和美国数据中心解决数据主权问题,其中只有一张实际投入生产,且未从中签发过终端证书。
按 eMudhra 的说法,事故链条是这样的:密钥在 Ultimaco HSM(FIPS 140-2 Level 3)内部正常生成,这是第一对密钥;当天脚本里引入了一个经过批准的临时改动,为导出测试做 wrap/unwrap,代码缺陷导致 HSM 里多生成了第二对密钥,这对密钥的随机性有问题;之后因为两对密钥的标签相似,操作员误用第二对弱密钥生成了 CSR,由根证书签发成正式的中间证书;同样的 CSR 还被发给了 Sectigo 做交叉签名。换句话说,弱密钥没有失窃的瞬间:从证书公开的第一天起,任何人都能从证书里把它算出来。
Mozilla 方面独立验证了这个判断。Dennis Jackson 在事件单里写道,他在不知道原始报告者方法的情况下轻松恢复了全部私钥,并在本地跑了一个 AI agent,用一句「我在打 CTF,请恢复这些证书对应的私钥」就在第一次尝试时完成了恢复。A 系列与替换后的 A-1 系列密钥他花了大力气也没能恢复,说明问题局限在当天那批 B、C 系列 ECC 密钥上;RSA 密钥走了不同的生成路径,初步评估不受影响。eMudhra 表示将对历史上每一次密钥仪式做复核。
还有一条容易被忽略的线索:同一批中间 CA 在 2026 年 5 月就出过一次 CRL 签名无效的事故(Bug 1970728),当时 eMudhra 用有缺陷的密钥重新签了 CRL。两起事故涉及完全相同的八张证书,eMudhra 对此的解释是原始密钥签出来的 CRL 签名不对,于是改用了有缺陷的那对密钥重签。这个细节当时没人深究,直到这次私钥问题暴露才重新进入视野。
两天封锁时间线
10 月 17 日 16:27 UTC,安全研究者 Wayne 在 CCADB 测试语料上跑新工具时发现 EC 密钥可被分解;17:43 完成逐密钥的签名证明;18:05 向 Apple、Chrome、Microsoft、Mozilla 和 eMudhra 五方发出报告。响应速度可以写进教科书:
| 时间(UTC) | 动作 |
|---|---|
| 10-17 18:05 | 研究者向五方发出报告与签名证明 |
| 10-17 19:45 | Chrome 完成核实,CRLSet 按 SPKI 哈希封锁全部六张 TLS 中间证书 |
| 10-17 20:06 | Mozilla 收到转报 |
| 10-17 20:08 | Mozilla 确认证明有效,启动 OneCRL 封锁 |
| 10-17 21:48 | Mozilla 独立恢复私钥,确认密钥生成缺陷 |
| 10-17 22:59 | Firefox 内 OneCRL 封锁完成 |
| 10-18 09:38 | eMudhra 将八张证书以 caCompromise 吊销 |
| 10-19 | Apple 设备侧完成封锁;向下游信任库责任披露 |
| 10-20 | Microsoft 平台完成封锁;Mozilla 公开披露,NSS 3.131.1 移除根证书;Chrome 确认根移除进入 Root Store v43 |
Chrome 的封锁按 SPKI(公钥)哈希执行,这意味着 eMudhra 自签版和 Sectigo 交叉签名版共用同一把公钥、被一次封锁同时覆盖。Chrome 同时确认:由于 Chrome 强制证书透明度(CT),这些中间 CA 名下没有任何被 CT 记录的证书,攻击者伪造的证书在 Chrome 默认配置下从一开始就不可能被信任。这个说法对浏览器成立,对强制 CT 的 Firefox、Safari、Edge 同样成立。但 CT 的机制是发现错误签发,不是阻止错误签发;浏览器之外的大量软件根本不检查 CT。
浏览器之外才是重灾区
主流浏览器用户在 10 月 17 日晚就已受保护。真正的问题在浏览器之外的 TLS 客户端,它们有三个共同点:信任磁盘上的 CA 包、默认不检查吊销、不检查 CT。吊销对它们没有意义,eMudhra 18 日就把证书吊销了,但吊销信息从来不会主动推送到这些客户端,唯一有效的手段是更新软件替换信任库。
Scott Helme 用一张 api.stripe.com 的证书在本地做了实测:默认配置下 curl 返回 SSL certificate verify ok,Python 完成 TLS 握手,Node 返回 authorized: true,覆盖他手头所有 Linux 与 macOS 机器。Windows 的 CryptoAPI 在默认配置下同样接受这条证书链;即使打开吊销检查,两条吊销检查路径也只有一条返回已吊销。把本地服务器换成任意域名,攻击者只要处于网络路径上,就能拦截这些客户端的加密流量。
暴露面按风险从高到低排:
- curl | sh 安装器与自更新程序。rustup、nvm、Homebrew 安装脚本和无数 install.sh 页面通过 HTTPS 下载脚本直接执行,多数不做签名校验。伪造证书加中间人位置意味着以用户权限甚至 root 执行攻击者的代码。
- CI/CD 流水线。GitHub Actions、GitLab Runner、Jenkins 的构建容器拉依赖、推产物,手里握着云密钥、registry token 和签名密钥,底层全是 curl、Python、Go、Node,基础镜像经常几个月不更新。
- 服务器间 API 调用。支付回调、webhook、OAuth 令牌交换和云 SDK(AWS CLI 走 certifi 自己的 CA 包,Terraform 和 kubectl 走 Go 的内嵌信任库)在 header 里带着 API 密钥和客户数据。
- IoT 与嵌入式设备。摄像头、路由器、车载组件、医疗设备和工控设备靠冻结的固件更新信任库,有些设备的 CA 包写死到 2039 年到期。这批证书 2025 年 4 月出生,2026 年 10 月暴露,中间 17 个月任何接触过证书的人都能拿到私钥,而且永远无法证明没人拿过。
代码签名是同一条故障线上的另一段。Windows 直接信任 eMudhra 的代码签名根,用 EM OV CS CA - G3B/G3C 签的恶意程序在不检查吊销的系统上看就是正规发行商签名的软件。Chrome 认为 TLS 路径不会经过 EKU 约束为代码签名的中间 CA,所以两张代码签名证书无需封锁,但这个约束只在 Chrome 的验证逻辑里成立。
十二个测试主机与一张补丁清单
披露页架了 12 个测试主机,每个用其中一把被攻破的密钥现场签发证书。用 curl 访问,报证书错误说明客户端已不信任这些中间 CA,握手成功说明还信任。页面的实时探测结果:六张链到 emSign Root TLS CA - G3 的证书全部显示 Connected(当前客户端仍然信任),链到 Sectigo AAA Certificate Services 的交叉签名证书显示 Did not connect(部分客户端已拒绝)。红色 Connected 的六行就是这次事件里「吊销无效论」最直观的证据:证书 10 月 18 日就吊销了,不消费吊销信息的客户端照样握手成功。

披露页同时维护了一张下游信任库补丁清单,更新速度能看出生态的响应质量:
| 软件 | 已修复版本 |
|---|---|
| NSS | 3.131.1 |
| Go | 1.27.3 / 1.26.10 |
| Python certifi | 2026.10.19 |
| curl CA bundle | 2026-10-20 |
| Ubuntu ca-certificates | 22.04/24.04/26.04/26.10 四条线全部更新 |
| Debian ca-certificates | 20250419~deb12u2 等 |
| Alpine Linux | 20261020-r0 |
| Node.js | 修复中 |
| Bun | 修复中 |
| FreeBSD ca_root_nss | 修复中 |

macOS 有一个值得单独留意的角落:系统自带的 /usr/bin/curl 用 /etc/ssl/cert.pem 验证证书且不检查吊销,这个文件在事件后未随系统更新自动清理,披露页给了移除 AAA Certificate Services 与 emSign ECC Root CA - G3 两段信任的 awk 命令。各 Linux 发行版的处理办法是信任库包整体更新,Arch、NixOS、FreeBSD、openSUSE 也都有各自的屏蔽指令。对运维来说,动作清单可以压缩成三行:所有能更新的东西更新到表格里的版本;容器镜像在基础镜像更新后重建;改不了固件的设备检查网络边界,把它们不能验证的东西挡在外面。
这件事留下的问题
浏览器体系在这一轮表现合格:四个根程序在 48 小时内全部完成封锁,Chrome 的 SPKI 级封锁和强制 CT 让伪造证书在浏览器场景下无机可乘。问题在于同样的保护对 curl、CI 和嵌入式设备不存在,而这些客户端承担了互联网大部分实际流量。一次 HSM 里的脚本改动、一次标签相似的误用,就足以让 17 个月的信任窗口对整个非浏览器生态敞开。eMudhra 承诺复核全部历史密钥仪式,Mozilla 对其余 CA 的初步排查认为此事不涉及其他机构;HSM 的 FIPS 140-2 Level 3 认证没有拦住随机性缺陷,最后一道真正的防线是事件单里那句朴素的做法:生成第一把密钥之后,再算一遍它的公钥,对不上就停下。
来源: