前沿研究 · 动手算一遍
Claude 半小时解开 NIST 椭圆曲线种子之谜
多问问团队 · 更新于
这件事是什么
2000 年,美国国家标准与技术研究院(NIST)公布了一组标准椭圆曲线。每条曲线带一个 160 位的「种子」,没有人说明种子是怎么来的。
2026 年 10 月,Anthropic 内部的一个研究模型用不到半小时找到了来源:种子来自 C 语言库里的老式随机数生成器 lrand48,起点是 1997 年的时钟。
这不是攻破。用这些曲线的人什么都不用做。它让「曲线里藏了后门」的说法更站不住。
先认识几个词
- 椭圆曲线签名
- 一种数字签名方法。它用一条公开的曲线和一个只有你知道的数,算出一串别人能核对、但不能伪造的数字。ECDSA 就是这种方法。
- 种子
- 一串 160 位(20 字节)的数。按标准流程,种子做 SHA-1 哈希,结果决定曲线方程里的系数 b。种子定了,曲线就定了。
- 伪随机数生成器
- 一个按固定公式一步一步算数的程序。它算出的数看起来很乱。但只要起点(初始值)一样,算出来的整串数就完全一样。
- lrand48
- C 语言库自带的一个伪随机数生成器。它有一个 48 位的内部状态。每调用一次,状态按公式更新一次,然后返回状态的高 31 位。
- 格问题与 LLL
- 从被截断的输出倒推内部状态的成熟数学工具。LLL 是解这类问题的经典算法。
为什么密码学家纠结了 26 年
种子由美国国家安全局(NSA)提供。标准里没有说明种子怎么来。
所以有一个疑问一直悬着:设计者有没有试了很多种子,挑出一条带隐藏性质的曲线?2015 年,Koblitz 和 Menezes 在论文《A Riddle Wrapped in an Enigma》里讨论过这个疑问。Bernstein 和 Lange 提出过「海选弱曲线」的一种可能技术。
2015 年,NSA 密码学家 Jerry Solinas 回忆:种子可能是一句玩笑话(比如 "Give Bob and Jerry a raise")加一个计数器,再做哈希。他 2023 年去世,当年的档案没有找到。
密码学家 Filippo Valsorda 悬赏 12,288 美元,找种子的来源。如果奖金捐给公益组织,金额翻三倍,是 36,864 美元。
Anthropic 的几名员工先沿着「玩笑话」这条线索找过,没有找到。后来证明,这条线索是记错了。
Claude 怎么找到的
先看答案。程序先用 srand48(时间戳) 设定起点,再反复调用 lrand48。每次调用只留输出的最高 8 位(lrand48() >> 23),当作 1 个字节。每 20 个字节是 1 个候选种子。程序按 ANSI X9.62 的流程检查候选能不能生成合格的曲线。不合格,就取下一个候选。P-256 用的是第 1842 个候选。
下面的控件就按这条规则在你的浏览器里计算。按「下一步」看一次调用;按「跳到第 1842 个候选」,看它和 P-256 公布的种子逐位对照。
lrand48 · 48 位线性同余生成器 · 在你的浏览器里计算
状态 ← (0x5DEECE66D × 状态 + 0xB) mod 2⁴⁸
素数曲线的规则:每次只留最高 8 位,20 个字节一个候选。
0x33F507E2330E
- 留下的 8 位
- >>23 丢掉的 23 位
- 从不返回的 17 位
低 16 位 = 0x330E:srand48 初始化的格式
高 32 位 = 871696354 = 1997-08-16 01:52:34 UTC
还没调用。按「下一步」。
按 5 个 32 位字看
P-256 公布的种子: C49D3608 86E70493 6A6678E1 139D26B7 819F7E90
- 换一个角度不再猜口令,改猜 1997 年写曲线生成程序的人会用什么随机数。最可能的是 C 库自带的弱生成器。这类生成器的输出有可以识别的统计特征。
- 写扫描程序对每个公开的种子逐位滑动,检查相邻的数据块像不像某个 90 年代生成器的连续输出。一共试了 34 种「生成器 × 输出截取方式」的组合。
- 发现特征把种子写成 5 个 32 位字。10 个种子里有 4 个,这 4 个种子的全部 20 个字,最高位都是 0。随机出现的概率约为 1/5810。这正是 lrand48 的特征:它只返回 48 位内部状态的高 31 位,放进 32 位字里,最高位一定是 0。控件里切到「全部 31 位」可以看到这个特征。
- 穷举对这 4 个种子,取第一个 32 位字。被丢掉的隐藏位有 17 个,一共 2^17 种可能。逐一试过,检查后面 4 个字能不能接着对上。4 个种子全部对上了。
- 用格的方法素数曲线的种子留下的位更少:输出右移 23 位,只剩 8 位。从截断的线性同余输出恢复内部状态,是一个经典的格问题,可以用 LLL 算法解。不知道留下的是哪几位,就把每一种可能的位窗口都算一遍。
- 倒着走线性同余生成器可以往前算,也可以往回算。从恢复出的状态一步一步往回走,直到遇到 srand48 初始化的格式:低 16 位等于 0x330E。这种格式平均每 65,536 步就会碰巧出现一次,所以单独看它不算证据。控件里的「倒推回起点」就是这一步。
- 决定性的证据10 个素数曲线种子往回走到的初始值,全部能读成 Unix 时间戳。它们集中在 1997 年 5 月到 1998 年 4 月,和 NIST 当年做这项工作的时间一致。每个恢复出的 48 位内部状态,都能一位不差地重算出全部 160 位种子。
- 怀疑自己在思考记录里,Claude 先怀疑自己是不是在一个测试环境里。它又问:这么明显的线索,为什么 25 年没人发现?最后它这样说服自己:一个 32 位的初始值加一个很小的序号,能精确对上 160 位的种子,而且 10 个都对上,不可能是巧合。
- 顺带的发现P-192 和 5 条 ANSI 曲线的基点 G,来自大整数库 FreeLIP 的随机数生成器。初始值是 20000、20420、20507、20509、20512、20517。程序丢掉前 7 个输出,再取 26 位的候选 x 坐标,直到它落在曲线上。在这之前,没有任何一条 NIST 曲线的基点来源被解释过。
十一个月,十几条曲线
每个点是一次运行的起点时间(UTC),旁边的数是这次运行用到的候选序号。点一条素数曲线,控件会从那个时间戳出发,跳到那个序号。二元域曲线(B 开头)的时钟值按原样读是 1984 年 2 月,乘 2 以后是 1998 年 4 月 17 日,在 P-224 之后两周。所以程序可能把时钟除以了 2。
- 1
1997-05-15 15:49:03 UTC
- 2
1997-05-18 14:45:34 UTC
- 3
1997-08-08 01:12:04 UTC
- 4
1997-08-16 01:52:34 UTC
- 5
1997-08-30 14:50:08 UTC
- 6
1998-04-03 12:52:07 UTC
- 7
1998-04-17 (时钟值 ×2 之后)
B-233 #155B-283 #32B-409 #60B-571 #73
这意味着什么
- 不是攻破。Anthropic 的原话是:"not a cryptographic break, new attack, or weakness in the ECDSA curves"。用户不需要做任何事。ECDSA 和 P-256 照常安全。
- 后门的说法更站不住。种子来自一个简单、可以重算的过程:时间戳加弱随机数,不是人手指定的。Anthropic 说这让后门「更不可信」。Valsorda 说得更重:它能 "completely rule out the technique described by Bernstein and Lange"。整个过程只有大约 30 位的自由度,不够挑出一小类弱曲线。
- 比特币与此无关。比特币用的曲线是 secp256k1,不是 NIST 曲线。
- 时间戳加 lrand48 本身是很弱的随机来源。但在这里,它只用来生成公开的参数,不保护任何私钥。
还没解释的
- 跳过的候选。P-256 那次运行里,第 876 个候选也能生成合格的曲线,程序却用了第 1842 个。P-224 那次跳过了 3 个,两次 ANSI 运行跳过了 7 个。Valsorda 统计,合格的种子大约是公布数量的两倍。他找到一条隐含规则:阶 n 的位长不能超过域 p 的位长。第二条规则仍未找到。可能的解释是检查过程本身也消耗了随机数,或者程序有 bug。Anthropic 认为,跳过的数量太少,不足以支持「刻意挑了弱曲线」。
- B-163 的种子从哪来,仍然不清楚。
- P-224、P-256、P-384、P-521 和二元域曲线的基点 G 从哪来,仍然不清楚。
想自己动手
- Anthropic 公开了复现代码,放在公共领域(CC0)。链接在下面 Anthropic 的原文里。
- Valsorda 用 Go 语言独立复现了一遍,代码也公开了。链接在下面他的博客里。
- 本页控件只把 P-256 的结果和公布的种子逐位对照。其余曲线按同一规则计算,对照请用上面两份公开代码。
- 这笔 36,864 美元的悬赏,捐给了 Internet Security Research Group,也就是 Let's Encrypt 背后的非营利组织。
来源
- Anthropic,ECDSA 曲线种子的来源(2026-10-16)
- Filippo Valsorda,The NIST Elliptic Curves Seeds Bounty Has Been Claimed(2026-10-16)