NIST P-256 等椭圆曲线有一组公开了二十多年、却一直说不清来源的“种子”。Anthropic 在 2026 年 10 月 16 日公布了一条能逐字节复现它们的生成路径:这些种子不是某句暗语的 SHA-1,而是 1990 年代 C 语言库的 lrand48 输出,初始状态来自 Unix 时间戳。
听起来像是“全球通用的密码曲线用了弱随机数”,但结论恰好不是“ECDSA 被破解”。这次找回的是公开曲线参数的来历,不是任何人的私钥,也不是每次签名必须保密的随机数。简单、可复现的生成过程减少了参数被秘密挑选的空间,因此让蓄意后门的猜测变得更不可信;它没有给出伪造签名的新办法。
先分清三种经常都被叫作“随机数”的东西
椭圆曲线签名里至少有三类容易混在一起的数:
| 对象 | 什么时候产生 | 是否保密 | 出问题会怎样 |
|---|---|---|---|
| 曲线种子 | 标准制定时一次性产生 | 否,写在标准里 | 来源不透明会引发参数被刻意挑选的疑虑 |
| 用户私钥 | 每个用户或设备生成 | 是 | 泄露后可以冒充持有人签名 |
每次签名的随机数 k | 每次签名时产生 | 是 | 重复或可预测时可能直接泄露私钥 |
可以把标准曲线想成一座公开体育场:曲线种子只涉及“这座场地最初怎么选定尺寸”,所有人都看得到;私钥是运动员自己的更衣柜钥匙;签名随机数则是每场比赛临时使用、用完即弃的战术口令。查明场地设计图来自哪台旧电脑,不等于拿到了任何人的钥匙。
NIST 在 2000 年发布的 FIPS 186-2 中列出了 P-192、P-224、P-256、P-384、P-521 等曲线。标准给出一个 160 位种子,再由它导出曲线系数,但没有解释种子本身从哪里来。参数由 NSA 提交,生成记录又没有公开,密码学界因此长期追问:它们究竟是随手产生的,还是在大量候选中挑出了具有某种隐藏性质的一组?
为什么一个老式随机数发生器会留下指纹
lrand48 不是密码学安全随机数发生器,而是一个 48 位线性同余发生器。它的内部状态每次按固定公式前进一步:
x(n+1) = (0x5DEECE66D × x(n) + 0xB) mod 2^48调用 srand48(t) 时,32 位种子 t 会被放在高位,低 16 位固定为 0x330E:
x(0) = (t << 16) | 0x330E这很像一台只有 48 位里程表、每转一下都遵循固定齿轮比的机器。只要看到足够多的连续读数,就能补回被遮住的数字;知道当前读数以后,也能把齿轮反向转回出厂位置。
Anthropic 的报告称,Claude 先扫描了 34 种旧式随机数发生器及输出切片组合。部分二进制曲线种子的 32 位字最高位全部为零,这是 lrand48 只返回内部状态高 31 位留下的特征;报告给出的偶然出现概率是 1/5810。对这些种子,可以枚举首个输出没有暴露的 17 位,再检查后续输出是否连续。
质数曲线保留的位更少,不能直接枚举。报告描述的办法是把“截断线性同余发生器的状态恢复”转成格问题,用 LLL 格基约简恢复 48 位内部状态。恢复状态后再倒着运行发生器,直到低 16 位重新出现 0x330E,高 32 位便可以按 Unix 时间戳解释。
最终,十个质数曲线种子的初始化时间落在 1997 年 5 月至 1998 年 4 月。P-256 对应 1997-08-16 01:52:34 UTC,从该状态开始,每个候选消耗 20 次输出;第 1842 个候选正好是标准中的:
C49D360886E704936A6678E1139D26B7819F7E90这不是“看起来相似”,而是 160 位完全一致。
一个可以直接运行的 P-256 复现
下面是从官方公开代码缩减出的最小版本,只依赖 Python 标准库:
from datetime import datetime, timezone
def unix(when: str) -> int:
dt = datetime.fromisoformat(when).replace(tzinfo=timezone.utc)
return int(dt.timestamp())
def candidate(timestamp: int, number: int) -> str:
state = (timestamp << 16) | 0x330E
output = ""
for index in range(20 * number):
state = (0x5DEECE66D * state + 0xB) % 2**48
if index >= 20 * (number - 1):
output += f"{state >> 40:02X}"
return output
expected = "C49D360886E704936A6678E1139D26B7819F7E90"
timestamp = unix("1997-08-16 01:52:34")
assert candidate(timestamp, 1842) == expected
assert candidate(timestamp + 1, 1842) != expected
print(candidate(timestamp, 1842))2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
实际运行输出为:
C49D360886E704936A6678E1139D26B7819F7E90第二条断言是一项简单的负向校验:只把初始化时间向后移动一秒,结果就变为 85C759A46CDB290320FB02DC641E81545F611CEC,不会误打误撞得到标准种子。
我还运行了原文给出的完整种子生成逻辑:十个质数曲线种子和四个二进制曲线种子全部逐字节匹配,共 14 项通过。随后运行基点复现代码,使用 FreeLIP/早期 NTL 的随机数发生器,六条曲线的公开基点横坐标也全部匹配。这里验证的是 Anthropic 公布的生成配方确实能重放标准参数,并没有独立复现 Claude 最初如何从未知输出恢复状态、选择算法和找到时间戳。
它为何降低后门疑虑,却不能给出绝对证明
过去最令人不安的不是种子公开与否,而是无法解释“为什么偏偏选了这组数”。如果制定者可以先寻找具有秘密弱点的曲线,再为它包装一段看似随机的来源,公开种子就不能真正证明参数是自然产生的。
现在找到的时间戳、固定 PRNG 和候选序号把选择过程压缩成了一条很具体的路径。报告认为,这使“先挑弱曲线、再伪装生成过程”更不现实。不过它仍留下三个边界:
- 候选跳过规则不完整。 P-256 在第 1842 个候选被采用,但同一次运行的第 876 个候选也满足报告检查的基本条件;P-224 和 ANSI 示例曲线还跳过了其他可用候选。报告没有找回当年的完整筛选标准。
- 并非所有参数都已解释。 B-163 的种子仍没有匹配;P-224、P-256、P-384、P-521 的基点如何选定也没有答案。
- 可复现来源不等于数学安全证明。 这项发现说明参数的来源更朴素,却不能代替对曲线阶、扭曲线、离散对数难度和实现侧信道的密码分析。
因此,最准确的结论不是“后门疑云彻底消失”,而是“一个重要的可疑空间被显著缩小,同时仍保留可以继续调查的选择步骤”。
开发者需要换证书或迁移算法吗
不需要因为这项发现紧急更换使用 P-256、P-384 或 P-521 的证书与密钥。Anthropic 原文也明确表示,这不是密码学攻击、曲线弱点或新的迁移通知。
现实中的算法选择仍应跟随当前标准和系统威胁模型。NIST 现行 FIPS 186-5 与 SP 800-186 继续规定 ECDSA 和推荐曲线;二进制曲线已经被标为弃用,当前实现指南更推荐质数曲线 P-224、P-256、P-384、P-521。这个迁移边界来自标准本身,并不是 10 月 16 日发现的新风险。
这次发现真正值得开发者借鉴的是研究方法:面对一串“看起来随机”的常量,不要只猜暗语和哈希函数;先问当年的工程师会使用什么库、数据被怎样截断、状态机留下了哪些结构性痕迹。Claude 的贡献也不只是快速枚举答案,而是提出可被程序否证的假设,再用 160 位精确匹配把故事闭合。
原始资料
- Anthropic:Claude reveals ECDSA curve origins
- NIST FIPS 186-2:Digital Signature Standard
- NIST FIPS 186-5:Digital Signature Standard
- NIST SP 800-186:Elliptic Curve Domain Parameters
- Koblitz 与 Menezes:A Riddle Wrapped in an Enigma
- Filippo Valsorda:NIST Elliptic Curves Seeds Bounty
- The Open Group:
lrand48接口说明
💬 评论