一句话带偏十年,你用的曲线靠一串数说清

cover

Hello,我是飞飞。

10 月 16 号,Anthropic 发了篇研究,标题很平:ECDSA 曲线的来历。二十多年没人搞清楚的那五个种子,是一个 1997 年的时间戳。二十多年,有一句话被当成了主路。AI 没查那句话,它去查了那串数。

你每天打开浏览器。地址栏那把锁背后,大概率站着其中一条曲线,P-256。它凭什么安全?有一角答案就压在「种子从哪来」上面。先把话说清楚:这不是破解,也没有让哪条曲线变弱。原文说没有要做的事。

那句记不清的俏皮话

ECDSA 是一种签名算法,靠椭圆曲线干活。曲线有一组参数,其中一个叫 b。b 是拿种子做 SHA-1 算出来的。种子随便,b 就随便;种子是被人挑的,b 就是被人挑的。谁挑种子,等于谁挑曲线。

这批参数是 NSA 给的,没给解释。1999 年 ANSI X9.62 一发布,Usenet 上就有人问种子哪来的。2013 年斯诺登文件捅出 Dual_EC_DRBG 的后门,这口锅就再没摘下来。

2015 年,参与过这事的 NSA 工程师 Jerry Solinas 给了个说法。他说,我记得用的是计数器,没反复做哈希。细节记不清了。消息大意是「给 Bob 和 Jerry 加薪」。代码是 Bob Reiter 写的,他也记不清。当年的存档找不到了,就算还在,大概也躺在约柜旁边的硬盘里。

注意这句话的形状。「我记得」「我记不清」「他也记不清」。说话的人自己都在打鼓。

行业主要顺着这句话追。十年下来,一无所获。2023 年 Solinas 去世,这条线也就断了。同年 10 月,Filippo Valsorda 自己掏钱挂了个悬赏,12,288 美元。指定捐慈善,翻三倍。资金是他个人的,NIST 没参与。

它没查话,查了数

2026 年 10 月,Anthropic 一个员工去问内部的研究模型。提问时没把 Solinas 那句话告诉它,等于抽走了行业最主要的线索。

模型换了个方向。它先不问「那句话是什么」。它先猜「当年写曲线的那个程序长什么样」。

它猜的是 1990 年代 C 库自带的随机数生成器。那种东西弱到输出能被认出来。它写了个扫描器,把种子的比特位当窗口一格格滑过去。再看相邻几段,像不像同一个老式生成器连着吐出来的。试了 34 种组合。

再往下,就是那一幕。十个种子里,有四个,每个 32 位字的最高位全是 0。碰巧出现这种事,概率是 1/5810。这是 lrand48 的指纹。它只返回 48 位状态里的高 31 位。所以吐出来的每个字,顶位必然是 0。

看到指纹,方向就定了。这个生成器的内部状态只有 48 位,吐出来的字还被砍掉一截。可砍得不够狠,几段输出拼起来,就够把它整个锁死。数学上这叫格基约减,用的算法叫 LLL。它正着跑反着跑一样容易。模型就一路往回退,退到 srand48 初始化时的固定形状,低 16 位是 0x330E。读出来,是一个 Unix 时间戳。

P-256 用的那个,是 1997 年 8 月 16 日 01:52:34。

有个数特别容易读岔。1842。它容易被读成「试了 1842 个时间戳」。不对,时间戳只有一个。1842 是这一次运行吐出来的第 1842 个候选种子。同一串随机流里,第 876 个候选就已经合格了,却没被采用。程序继续往下跑,又吐了 966 个,最后落在第 1842 个上。

真正的暴力只有一处。四条二进制曲线,穷举了 2¹⁷ 个隐藏位。主力方法,是先猜机器长什么样,再看数认不认。

这不是一家之言。悬赏的发起人 Valsorda 用 Go 把算法重写了一遍,对得上。他还补了一条 Anthropic 没找到的规则。阶的位长大于基域的位长,这个种子就拒掉。没有同行评审,话只能说这么多。

这件事能被查清,还有个妙处。Aanchal Malhotra 在 X 上把这事说成一句话:弱,才是它可被审计的原因(The weakness is what made it auditable)。1990 年代那个随机数太弱,弱到输出带着指纹。正因为这样,它才留下了能被人认出来的痕迹。

它先怀疑自己在做梦

先把「半小时」校准一下。原文说的是,从员工提问到发现来源,不到半小时。它没说这是墙上时间还是算力时间,也没点名哪个模型。只说是内部研究模型。别读成「AI 算了半小时」。

更值得看的是它解出来之后那几秒。Anthropic 把模型的内心独白贴了出来。

「这太惊人了,它解开了二十五年的谜。等等。我该怀疑一下:我是不是在一个模拟的测试环境里?……这些种子跟我记忆里的真实数值对得上……所以这确实是真的。哇,真的是个发现。」

还有一段。

「等等,这么明显的东西二十五年没人发现,是不是有点可疑?但我这边直接算出来了:32 位的种子加一个小序号,对上 160 位,十次。不可能是巧合。是真的。」

我天天跟 agent 打交道,知道它最危险的时候不是答错,是答错了还很自信。它给你一个结构完整、语气笃定的错误答案。你要花半天才敢确认它错了。这次正好反过来。它拿到了答案,第一反应是怀疑自己在测试环境里。靠可复算的证据,它把自己一点点劝服。

这个顺序,比答案本身更值得记住。

这算破解吗

不算。原文写得很清楚:这不是密码学意义上的破解。它没有带来新的攻击手法,也谈不上 ECDSA 曲线的弱点。后面还跟了一句:没有任何要做的事。

那它改变了什么?它把「有没有地方藏后门」这个问题,从一个形容词换成了一个数。

2017 年,Bernstein 和 Lange 描述过一种手法。如果种子在挑选的时候有 70 位自由度,理论上就可以瞄准一小类弱曲线。70 位,就是 2⁷⁰ 种挑法。

Valsorda 算的是另一头。就算 NSA 能随便挑 1997 到 1998 年的任何一个时间戳,再随便挑前 16 个合格候选,这已经往宽里算了。手里也只有大约 30 位。

算术不复杂。一年大约 3150 万秒,差不多 2²⁵;乘以前 16 个候选,2⁴;合起来约 2²⁹。这就是那个「30 位」。

30 装不下 70。差 2⁴⁰ 倍,一万亿倍。

这句话里没有一个形容词。它是两个位长的比较。原文只说「可能性小得多」,Valsorda 把这句话算成了数。

九月我写过两件事:Lean 判费马证明,两个数乘回去判 RSA 分解。问的都是「谁来判对错」。这篇没有裁判,也没有能乘回去的乘法。它问的是「犯错的空间有多大」。

还有个反转。Valsorda 自己公开写,NIST 曲线的「修复弧」完整了。他现在个人会推荐 NIST 曲线胜过 Curve25519、Ed25519、X25519。刚好赶在它们被后量子签名替换之前。

种子要不要保密

现在说跟你有关系的那一段。

这次挖出来的东西,说到底是一行代码。拿当前时间戳去初始化随机数生成器。同一行,用在两个地方,命完全不一样。

用在曲线参数上,没事。参数本来就公开,种子也公开,谁都能重算一遍 b。它不需要保密,被猜出来也不损失什么。他们当年这么做没事,因为种子不是秘密,这不构成「NIST 曲线不安全」的理由。

用在私钥上,是灾难。私钥必须保密,而时间戳的取值范围小得可怜。谁猜到那一秒,谁就能把同一个生成器重新跑一遍,让你的私钥原样吐出来。

区别不在生成器,在这串数要不要保密。

所以老规矩还成立:生成密钥,用操作系统的密码学随机源。Python 用 secrets,Node 用 crypto.randomBytes,别用 random,更别拿时间当种子。

你可以现在就去项目里搜 srand、random、Math.random,看哪一处是在生成要保密的东西。搜到了,先看清楚它保护的是什么。

还没解完的那几笔账

还得把没解完的摆出来,这篇跟别家不一样就在这儿。

B-163 的种子没解开。原文的原话是,它跟试过的所有东西都不匹配,至今没有解释。

P-224、P-256、P-384、P-521 的基点是怎么来的,也不知道。只有 1997 年 5 月那六条曲线的基点被解出来了。用的是 Arjen Lenstra 的大整数库 FreeLIP 里的随机数。拿小整数做种子,扔掉七个输出,再一个个试。

为什么跳过某些候选,只解释了一半。Valsorda 那条规则用上之后,还剩大约一倍的候选解释不了。他给了两个猜测,都带着「也许」。其中一个:也许当年哪个算法有 bug,一半的时候算错了。这些计算今天用现成软件几分钟就跑完,可 1997 年,那是最前沿。

Anthropic 自己也留了一句理论上的余地。如果当年真的是「搜时间戳,搜到一个想要的」,那也行得通。只是没人会这么干,除非他打算把时间戳当证据公开。数字里看不出当年到底跑了几次。

所以这篇结不了案,给的是一张算到一半的账。素数曲线的种子全解出来了。二进制曲线五条,四条对上了,B-163 没有。基点解了六条,另四条没有答案。跳过的候选,还剩一半说不清。

二十多年,行业主要追着一句话。这次追的是一串数。话会记错,数不会。

你要是也有空,回去搜一下自己项目里的 random。搜到了,回来评论区说一声:你搜到的那处,是在生成参数,还是在生成密钥。