Claude破解NIST椭圆曲线种子生成逻辑

Anthropic团队用Claude破解NIST椭圆曲线种子生成逻辑。该谜团困扰学界二十余年,研究证实种子源自rand48伪随机数生成器,排除了故意植入后门的可能,现有配置无需更改。

困扰加密学界数十年的 NIST 椭圆曲线标准起源之谜终于被解开。Anthropic 团队利用其 AI 模型 Claude,成功追溯了 ECDSA(椭圆曲线数字签名算法)中关键参数种子的生成过程。

种子源自rand48伪随机数生成器

这一发现为长期存在的猜测画上了句号。对于普通用户和开发者而言,无需采取任何额外行动或更改现有配置。大多数研究人员此前已信任这些曲线,此次揭秘进一步证实了参数的安全性,排除了故意植入后门的可能性。

ECDSA 与种子争议背景

ECDSA 是目前应用最广泛的基于椭圆曲线的加密标准之一。2000年,美国国家标准与技术研究院(NIST)在 FIPS 186-2 数字签名标准中正式采纳了该算法,并定义了一系列不同安全级别的标准曲线。规范中公布了一个名为 .seed 的参数,其哈希值用于确定曲线系数 b。然而,这些种子由国家安全局(NSA)提供,且未附带任何解释或生成逻辑说明,这引发了长达二十多年的质疑。

例如,NIST P-256 曲线的种子值为 SEED=C49D3608 86E70493 6A6678E1 139D26B7 819F7E90,但其来源一直成谜。尼尔·科布利茨(Neal Koblitz)和阿尔弗雷德·梅内兹(Alfred Menezes)曾撰文总结这一争议,并设立了寻找种子来源的奖励机制。

此前流传最广的说法指向 NSA 长期员工杰里·索利纳斯(Jerry Solinas)。他在 2015 年曾表示,虽然记得使用过某种计数器而非多个哈希,但具体细节已模糊不清。相关代码可能由 Bob Reiter 编写,他也无法回忆细节。由于档案缺失,真相一度被认为永远沉没在历史中。遗憾的是,索利纳斯已于 2023 年去世,当时其他同事也未对此作出回应。

Claude 破解种子生成逻辑

尽管 Anthropic 内部多名员工曾尝试追踪种子来源未果,但在 2026 年 10 月,一名好奇的员工向内部研究模型 Claude 提出了这一问题。不到半小时,Claude 便揭示了 NIST P-192、P-224、P-256、P-384 和 P-521 等曲线种子的生成方式。

研究发现,这些种子并非来自复杂的哈希算法,而是源自 C 语言库中的 rand48 伪随机数生成器(PRNG)。该算法通过 Unix 时间戳初始化,经过一定次数的迭代后输出候选种子。例如,每 20 代产生一个 20 字节的候选值。以 P-256 为例,标准化种子出现在第 1842 次迭代中。

技术原理:如何识别 PRNG

Claude 并未直接搜索谣言中的线索,而是预测了生成软件的底层逻辑。它假设软件使用了旧式 PRNG,这类生成器的输出特征可被识别,从而反推其初始化状态。

Claude 编写了一个扫描程序,对每个已发布的种子进行逐位滑动分析,测试相邻数据块是否符合 1990 年代老式生成器的连续输出特征。在覆盖 34 种生成器组合的扫描中,Claude 注意到部分种子的最高位呈现全零模式。写成五个 32 位字时,十个种子中有四个的前 20 个高位均为 0。这种概率极低的巧合(1/5810)是线性同余生成器 rand48 的典型特征,因为它仅返回 48 位状态中的前 31 位。

为了恢复完整状态,Claude 提取第一个字,遍历 17 个隐藏位的 2^17 种可能性,并验证后续四个字是否匹配。这一方法成功复原了四种主要种子的状态。

对于比特截断更严重的种子,Claude 采用了格基归约算法(Lenstra-Lenstra-Lovász, LLL)来恢复 PRNG 的内部状态。所有五个 NIST 素数域曲线种子以及五个 ANSI X9.62 标准种子均显示出相同的模式:lrand48() >> 23。每个恢复的 48 位状态都能完美再生出种子的全部 160 位,证明这绝非偶然。

时间戳还原与结论

线性同余生成器支持双向运行。Claude 将恢复的状态向后回溯,直到达到 srand48 的初始化形式(低 16 位等于 0x330E)。虽然这种状态每 65536 步出现一次,但结合上下文分析,结果显示:

  • 十个素数域曲线种子的初始化时间对应 Unix 时间戳,日期分布在 1997 年 5 月至 1998 年 4 月之间。
  • 四个二进制域曲线的初始化为 1984 年 2 月的时间戳,若考虑时间减半因素,则映射至 1998 年 4 月 17 日。

这意味着种子是通过一种极其简单的方式生成的:使用常见的伪随机数生成器,并以当时的系统时间为种子。这一发现极大地增强了人们对 NIST 曲线参数安全性的信心,表明选择过程没有刻意保留后门或弱点。

基点 G 的来源探索

除了曲线系数,ECDSA 公钥计算还依赖固定的基点 G。Claude 在分析过程中也试图追溯基点的生成逻辑。

对于 1997 年 5 月生成的六个曲线(包括 P-192 及 ANSI X9.62 示例曲线 prime192v2/v3, prime239v1-v3),其基点来源于 Arjen Lenstra 开发的长整数算术包 FreeLIP 中的随机数生成器。维克多·舒普(Victor Shoup)的 NTL 库早期版本也包含相同代码。

生成配方如下:

  1. 用小整数播种生成器。
  2. 抛弃前七个输出。
  3. 从 26 位输出中提取候选 x 坐标。
  4. 重复步骤 3,直到 x 位于曲线上。

Claude 确认了以下种子值:P-192 为 20000;prime239v1 至 v3 分别为 20420、20507 和 20509;prime192v2 和 v3 则为 20512 和 20517。目前尚不清楚 P-224、P-256、P-384、P-521 以及 NIST 二进制曲线的基点是如何生成的,它们不符合上述配方。

遗留问题与奖金去向

尽管取得了重大突破,仍有部分细节待解。例如,第五个二进制种子 B-163 尚未被解释。此外,关于候选曲线的筛选标准也不明确:在 P-256 的运行中,除最终采用的第 1842 号候选外,第 876 号候选也能生成符合要求的曲线。由于跳过的候选数量较少,弱化曲线被选中的可能性极低,但这仍是一个开放性问题。

根据此前设立的奖励机制,找到这些种子来源所获得的 36,864 美元奖金将捐赠给互联网安全研究小组(ISRG)。Anthropic 团队也在致谢中特别提到了已故的杰里·索利纳斯,以及组织 NIST 椭圆曲线种子奖励活动的菲利波·瓦尔索达(Filippo Valsorda)。

复现代码

以下是用于复现种子生成的 Python 代码片段:

from datetime import datetime, timezone

def unix(when):
    return int(datetime.fromisoformat(when).replace(tzinfo=timezone.utc).timestamp())

def candidate(t, n, per, shift, width):
    x, out = (t << 16) | 0x330E, ""                      # the state srand48(t) sets
    for i in range(per * n):
        x = (0x5DEECE66D * x + 0xB) % 2**48              # one step of lrand48
        if i >= per * (n - 1):
            out += "%0*X" % (width, x >> shift)
    return out

PRIME = {  # srand48(time); 20 bytes per candidate: lrand48() >> 23
    "prime239v1": ("1997-05-15 15:49:03", 2185),
    "prime239v2": ("1997-05-15 15:49:03", 2320),
    "prime239v3": ("1997-05-15 15:49:03", 3048),
    "P-192":      ("1997-05-18 14:45:34", 139),
    "prime192v3": ("1997-05-18 14:45:34", 5228),
    "prime192v2": ("1997-05-18 14:45:34", 6013),
    "P-521":      ("1997-08-08 01:12:04", 1136),
    "P-256":      ("1997-08-16 01:52:34", 1842),
    "P-384":      ("1997-08-30 14:50:08", 2688),
    "P-224":      ("1998-04-03 12:52:07", 7026),
}
BINARY = {  # srand48(time / 2), if that reading is right; 5 words per candidate: lrand48()
    "B-233": ("1998-04-17 15:24:18", 155),
    "B-283": ("1998-04-17 15:27:44", 32),
    "B-409": ("1998-04-17 15:30:18", 60),
    "B-571": ("1998-04-17 15:32:08", 73),
}
for name, (when, n) in PRIME.items():
    print(f"{name:10} {when} {n:5} {candidate(unix(when), n, 20, 40, 2)}")
for name, (when, n) in BINARY.items():
    print(f"{name:10} {when} {n:5} {candidate(unix(when) // 2, n, 5, 17, 8)}")

代码输出结果验证了各曲线种子的正确性:

prime239v1 1997-05-15 15:49:03  2185 E43BB460F0B80CC0C0B075798E948060F8321B7D
prime239v2 1997-05-15 15:49:03  2320 E8B4011604095303CA3B8099982BE09FCB9AE616
prime239v3 1997-05-15 15:49:03  3048 7D7374168FFE3471B60A857686A19475D3BFA2FF
P-192      1997-05-18 14:45:34   139 3045AE6FC8422F64ED579528D38120EAE12196D5
prime192v3 1997-05-18 14:45:34  5228 C469684435DEB378C4B65CA9591E2A5763059A2E
prime192v2 1997-05-18 14:45:34  6013 31A92EE2029FD10D901B113E990710F0D21AC6B6
P-521      1997-08-08 01:12:04  1136 D09E8800291CB85396CC6717393284AAA0DA64BA
P-256      1997-08-16 01:52:34  1842 C49D360886E704936A6678E1139D26B7819F7E90
P-384      1997-08-30 14:50:08  2688 A335926AA319A27A1D00896A6773A4827ACDAC73
P-224      1998-04-03 12:52:07  7026 BD71344799D5C7FCDC45B59FA3B9AB8F6A948BC5
B-233      1998-04-17 15:24:18   155 74D59FF07F6B413D0EA14B344B20A2DB049B50C3
B-283      1998-04-17 15:27:44    32 77E2B07370EB0F832A6DD5B62DFC88CD06BB84BE
B-409      1998-04-17 15:30:18    60 4099B5A457F9D69F79213D094C4BCD4D4262210B
B-571      1998-04-17 15:32:08    73 2AA058F73A0E33AB486B0F610410C53A7F132310

此外,针对基点 G 的复现代码也展示了特定曲线的生成逻辑:

M, A = 2**107 - 1, pow(3, 121, 2**107 - 1)       # zrandom: state = state * 3^121 mod (2^107 - 1)
P192 = 2**192 - 2**64 - 1
P239 = 2**239 - 2**143 - 2**95 + 2**47 - 1

CURVES = [  # name, LIP seed, p, b, published Gx   (a = -3 throughout)
    ("prime192v1", 20000, P192,
     0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1,
     0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012),
    ("prime239v1", 20420, P239,
     0x6b01

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读