印度证书颁发机构 eMudhra 的 8 张中间 CA 证书,私钥本应是 384 位的随机数,实际取值却是 4、56、66、115、155、156、230、237 这种个位数或两位数的整数。攻击者最多只需试 256 次就能还原私钥,并据此为任意域名签发足以骗过浏览器的 HTTPS 证书。Mozilla 在接到报告后 2 小时 53 分钟完成封堵,Chrome 当天跟进,苹果、微软随后补上——但这场事故真正的看点不在响应速度,而在一个更尴尬的事实:从 2025 年 4 月密钥生成,到 2026 年 10 月被发现,中间隔了整整 18 个月,没有任何一道检查拦下它。
一、事故本身:8 张证书,8 个小整数
先把事实摆清楚。
2026 年 10 月 17 日,安全研究员 Wayne 在 eMudhra 的公开证书数据里发现了一批异常的中间证书。这些证书的私钥使用的是 P-384 椭圆曲线,按 RSA 世界的说法,「384 位」意味着私钥是一个 384 位的随机整数,搜索空间约为 2³⁸⁴。而这 8 张证书的私钥取值分别是:
4、56、66、115、155、156、230、237
全部小于 256。
这不是”强度不够”,而是熵几乎为零。攻击者不需要破解 ECDLP,也不需要量子计算机,只需要写一个 for 循环,从 1 试到 256,逐个推导候选公钥,和证书里公开的公钥比对,命中即得私钥。
涉及的 8 张证书分两类:
| 用途 | 证书名称 | 数量 |
|---|---|---|
| TLS 服务器证书签发 | EM DV / OV / EV TLS CA – G3B、G3C | 6 张 |
| 代码签名 | EM OV CS CA – G3B、G3C | 2 张 |
全部挂在 emSign ECC Root CA – G3 之下,密钥生成时间是 2025 年 4 月的一次密钥生成仪式(key generation ceremony)。
二、我本地复现了一遍:1.2 毫秒
为了不让”毫秒级可破解”停留在形容词层面,我在本机用 Python 的 cryptography 库把这个过程跑了一遍。
第一步:直接用标量 4 构造 P-384 私钥并推导公钥。
第一步:调用椭圆曲线模块里的 derive_private_key 方法
第二步:传入标量 4 与 SECP384R1 曲线名(实测 0.016 秒)
第三步:从私钥对象取出 public_key 的公钥坐标
第二步:模拟攻击者——手里只有 CT 日志里那张证书的公开公钥,从 1 遍历到 256 找匹配。
实测结果:
暴力遍历命中: k = 4 | 用时 0.0012 秒 | 尝试次数 4
平均每个候选 0.29 ms
1.2 毫秒,试了 4 次就命中。
第三步:拿这把私钥给 www.example.com 签一张证书,再用这张中间证书去校验。
openssl verify -CAfile weak.crt forged.crt
forged.crt: OK
OK。这就是整件事的要害:一旦私钥在手,伪造证书在数学上是完全合法的,任何只做签名链校验的软件都会认。能被识破的唯一环节,是这张中间证书本身有没有被撤销。
三、18 个月:三道本该拦下它的检查,一道都没响
时间线摊开来看更刺眼:
| 时间 | 事件 |
|---|---|
| 2025-04 | 密钥生成仪式产出一批”弱密钥”,被误选用于签发中间证书 |
| 2025-04 至 2026-10 | 证书在 CT 日志中公开可查,公钥一直摆在那里 |
| 2026-10-17 20:06 UTC | Mozilla 收到研究员报告 |
| 2026-10-17 22:59 UTC | Mozilla 通过 OneCRL 完成 Firefox 阻断分发,耗时 2 小时 53 分 |
| 2026-10-17 当天 | Chrome 通过 CRLSet 推送,封锁 6 张 TLS 中间证书 |
| 2026-10-19 | 苹果加入阻断 |
| 2026-10-20 | 微软加入;Mozilla 发布 Firefox 158.0.1 兜底 |
响应端可以给满分。问题全出在前面 18 个月。
理论上至少存在三道检查该拦下它:
1. 生成脚本的自检。一行 if scalar.bit_length() < 384: raise 就够了。PCQuest 的评价很直接:这个错让密钥熵短缺了约 376 位。
2. HSM 的输出校验。硬件安全模块保证了密钥不落地、不外泄,但它保证不了密钥本身是好密钥——它只管把输入的数存起来、用它做运算,不管这个数是不是只有 4。
3. CA/B Forum 的合规审计。基线要求审计的是流程、文档、隔离措施,没有一条要求对已签发的中间证书做持续性的熵质量检测。
第三点是最需要被顶到台面上的。WebPKI 的整套信任模型,本质上是”相信 CA 会正确生成密钥”。这句话过去靠商业声誉兜着,现在看来,声誉兜不住一行写错的脚本。
四、eMudhra 的解释:脚本错误 + 标签撞名
按 eMudhra 在 Mozilla 事件记录里的初步说明,问题链条是这样的:
1. 密钥生成脚本有 bug,额外生成了一批弱密钥;
2. 这批弱密钥和正常密钥的标签(label)长得太像;
3. 签发请求进来时,选错了密钥,把弱密钥拿去签了中间证书;
4. 其中几张还被送去 Sectigo 做了交叉签名。
第 4 点是这次事故里最容易被忽略、后果最麻烦的一环。交叉签名的本意是平滑过渡——让新根通过老根的信任路径也能被认。但一旦这张中间证书出问题,受污染的信任路径就不止 eMudhra 自己那条了,Sectigo 那条也得一并切断。封堵范围从”一张证书”扩散成”两条链”。
eMudhra 的补充说明是:除一张外,其余受影响中间证书均未部署到生产环境;所有受影响证书均未签发终端实体证书;这一结论有内部分析和公开 CT 记录佐证。
这个说法大概率是真的——谷歌明确表示,Chrome 响应之前,CT 日志里没有出现挂在这 6 张 TLS 中间证书下的终端证书。但 Mozilla 的表态很克制,也很准确:
> “目前不清楚这些密钥是如何离开 eMudhra 的、何时离开的,也不清楚除研究员之外是否还有其他人持有。”
没有发现利用,不等于没有被利用。 这 18 个月里,任何从 CT 日志里读到这些公钥的人,都可以自己花 1 毫秒算出私钥。没有痕迹,只是因为伪造证书签发后不需要再回来打招呼。
五、真正麻烦的部分:浏览器修好了,你的服务器没有
这是我认为比事故本身更值得写的一段。
浏览器的封堵机制做得很漂亮:
- Chrome 的 CRLSet 封锁的是公钥哈希,不是证书指纹。这意味着同一把密钥签出的交叉签名证书会一并失效,而且不需要用户更新或重启浏览器;
- Firefox 的 OneCRL 同理,服务端推送即可生效。
但这两套机制只覆盖浏览器。而现实世界里,信任这些中间证书的东西远不止浏览器:
| 类别 | 典型代表 | 为什么不会自动修好 |
|---|---|---|
| 独立证书包 | Python certifi、Homebrew、Alpine/Debian/Ubuntu ca-certificates | 打包时把根和中间证书写死进镜像,不查询在线撤销状态 |
| 内置根库 | Go、Rust webpki-roots、Electron、Bun | 编译期嵌入信任列表,撤销信息拿不到 |
| 系统级工具 | macOS 系统自带的 curl 命令 | 其证书包不检查撤销信息 |
| 设备固件 | 路由器、IoT、老版本 Java keystore | 出厂后基本不更新信任库 |
Go 团队的反应可以作为参照——他们专门发了 1.27.3 和 1.26.10 两个版本,编号 CVE-2026-111943,因为用旧版 Go 编译出来的程序,二进制的信任列表里还躺着这些中间证书。这类程序不会因为你换了浏览器就变安全,必须重新编译。
一个具体的判断方法:如果你的服务端程序、爬虫、API 客户端是用 Go 编译的、或者用了自带的 CA bundle,那它现在可能仍然会信任一张攻击者能签发任意域名的中间证书。而这类程序往往跑在服务器上,处理的恰恰是没人盯着看的后台流量。
六、一张表自查:你的环境该动哪儿?
| 组件 | 已修复版本 | 要做什么 |
|---|---|---|
| Firefox | 158.0.1、ESR 更新 | 一般无需操作,OneCRL 已生效;配置受限者需装新版本 |
| Chrome / Edge | CRLSet 已推送 | 无需更新或重启 |
| macOS / iOS | 10-19 起系统更新 | 系统更新 |
| Windows | 10-20 起系统更新 | Windows Update |
| Go 程序 | 1.27.3 / 1.26.10 | 重新编译,不是换个运行时 |
| Python | certifi 2026.10.19、pip 26.2.2 | 升级证书包 |
| Rust | rustls-webpki 0.103.16、webpki-roots 1.0.10 | 升级依赖并重新构建 |
| Electron 应用 | 44.9.1 / 43.8.1 / 42.11.16 | 升级并重新打包分发 |
| Alpine Linux | ca-certificates 20261020-r0 | 重建镜像 |
| Ubuntu | ca-certificates 20260816ubuntu0.22/24/26 | apt upgrade |
| Debian / FreeBSD | 待发布 | 盯住公告 |
| Node.js / Bun | 待发布 | 盯住公告 |
Mozilla 的密码学工程师 Dennis Jackson 维护了一个事故跟踪页,里面提供了测试端点——访问一下就能知道你的客户端还在不在信任这些中间证书。这比逐条比对版本号可靠得多。
七、下一波:根也要拔了
中间证书被封堵只是止血。真正彻底的做法是把 emSign ECC Root CA – G3 这个根从信任库里移除——因为只要根还在,理论上还能从它下面签出新的中间证书。
- Mozilla 已宣布从 NSS 中移除该根;
- 谷歌计划在 Chrome Root Store 中移除,受影响设备的生效时间预计在 10 月 27 日左右。
根移除的影响范围比中间证书封堵大得多:凡是在这个根下面签发、还没换掉的证书,到时候会直接失效。
eMudhra 自己估计,大约有 3 万张合法证书需要迁移。这个数字意味着另一类风险——不是被冒充,而是到期没换、站点直接打不开。历史上每一次根证书移除,都会有一批疏于维护的站点在那一天集体变砖。
如果你或你的客户用过 emSign 签发的证书,现在就该去核对签发链,而不是等到 10 月 27 日看报错。
八、顺手给主机吧读者的一句话
回到本站最常见的场景——很多读者是拿 Let’s Encrypt 或宝塔面板一键签的 DV 证书,那基本不受这次事件影响。但有两类情况值得自查:
1. 在服务器上用 Go 写的程序、自建爬虫、或者用 certifi 的 Python 脚本:这类东西不会因为浏览器更新就安全,得手动升依赖、重新编译。
2. 自建 CA 的内部系统:如果你自己用 openssl 或脚本生成过 CA 密钥,去检查一下生成时有没有校验位数。这次的教训不是”某家印度 CA 不靠谱”,而是“自动生成的密钥,如果没有校验,就可能生成一个看似 384 位实则只有 8 位的数”。这一点对自建 PKI 同样成立。
顺带一提,本站当前使用的证书链是「TrustAsia TLS RSA Root CA → 宝塔 DV TLS RSA CA 2025」,中间证书为 4096 位 RSA、2030 年到期,与 emSign 体系无关,不受本次事件影响。
参考资料
- IT之家原文(2026-10-21,作者:问舟)
- Mozilla, Google and Apple block eight eMudhra certificates with weak keys — ATLABYTE
- eMudhra certificate flaw leaves some apps at risk despite browser fixes — PCQuest
- Browsers Block eMudhra After Eight CA Keys Exposed — 0dayNews
- 本地复现环境:Python 3.11 + cryptography 46.0.4 + OpenSSL 3.0.13,仅用于验证密钥空间,未对任何生产系统发起操作。
