ai newsSa., 17. Okt.
EilmeldungClaude Traced the NIST Curve Seeds to a 1997 Clock and lrand48. Nothing to Rotate on P-256

Claude Traced the NIST Curve Seeds to a 1997 Clock and lrand48. Nothing to Rotate on P-256

Book: System Design Pocket Guide: Interviews The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding

dev.toSamstag 03:02 · 17.10.6 Min. Lesezeit
Bild: dev.to

The seeds behind P-256, the curve under every ES256 signature on a JWT, were not the hash of a secret NSA sentence. According to Anthropic's write-up from October 16, they came out of the C library's lrand48 random-number generator, "seeded with a Unix timestamp", on dates between May 1997 and April 1998.

Anthropic says an internal research model worked that out "in under half an hour." The cryptographer who ran a three-year-old bounty on the question, Filippo Valsorda, wrote the same day that the bounty had been claimed, and that he "reimplemented the algorithm in Go to verify the claim."

Before you go looking for something to patch: Anthropic's own post says "There is no action to be taken or things to do differently." What the story does give you is a settled argument from the late 1990s, and a good reason to check what mints your session tokens.

When NIST standardised ECDSA in FIPS 186-2 in 2000, the curves P-192 through P-521 were each derived from a seed. Anthropic's post says those seeds "were provided by the NSA with no explanation or justification." The best-known account was that they were a hash of a humorous passphrase, and nobody could find the passphrase.

That gap fed suspicion. If a curve's maker can pick its seed freely, they can, in theory, keep trying seeds until a curve has a property only they know about. Anthropic notes people have long wondered whether the seeds "may have been chosen to have deliberate backdoors in them." Nobody had evidence of one, as BleepingComputer pointed out in 2023, but nobody could rule it out either.

So in 2023 Valsorda and a group of cryptographers put up $12,288 for the original sentences, tripled to $36,864 if the winner gave it to charity.

The answer was not a sentence at all. Per Anthropic, "Claude guessed the software used a pseudorandom number generator (PRNG) that shipped with 1990s C libraries," then recovered that generator's internal state from the published seeds, which the post calls "a textbook lattice problem."

Anthropic names lrand48; Valsorda's post says drand48. Both belong to the same POSIX family, which generates numbers "using a linear congruential algorithm and 48-bit integer arithmetic." Same engine, different output format.

The seeds came from a clock and a 48-bit generator, not a hidden sentence.

Anthropic published two Python scripts that reproduce the seeds and selected base points, dedicated to the public domain under CC0. The team claimed the bounty and chose to donate it to the Internet Security Research Group, the non-profit behind Let's Encrypt, which tripled it to $36,864, according to Valsorda.

Valsorda's post makes the argument in numbers. A 2017 idea from Bernstein and Lange needed roughly 70 bits of freedom in choosing seeds to aim at a class of weak curves. With timestamps and a generator, Valsorda writes that even if the NSA could pick any timestamp in 1997–98 and any of the first 16 prime-order candidates, "they would have had barely 30 bits of freedom." He says the findings "completely rule out the technique described by Bernstein and Lange."

Anthropic's phrasing is more careful: the findings make a backdoor "much less plausible." And the post is explicit about scope: "This is not a cryptographic break, new attack, or weakness in the ECDSA curves."

Valsorda goes further. He says this "nicely completes the 'rehabilitation arc' of NIST elliptic curve cryptography" and, given modern deterministic signing and verified implementations, calls the NIST curves ones "which I would now personally recommend over Curve25519/Ed25519/X25519."

For P-256 itself: nothing. Don't rotate keys, don't swap curves because of this. Anthropic says so in as many words.

The useful lesson is the generator. A 1997 program seeded a non-cryptographic PRNG with the clock, and decades later its output could be traced back to the second it ran. POSIX says the drand48 family "should be avoided whenever non-trivial requirements (including safety) have to be fulfilled." Python's random docs and MDN's Math.random() page carry the same warning: not for security.

If you generate session IDs, reset tokens, API keys or invite codes, find out what produces them. git grep gets you a list of places to read:

git grep -nE \ -e 'srand(48)?\(' -e '[dlm]rand48' \ -e 'Math\.random' -e '"math/rand' \ -e '^[[:space:]]*(import random|from random import)' Enter fullscreen mode Exit fullscreen mode The last two patterns catch Go files importing math/rand and Python files importing random, even when nothing ever calls a seed function. A hit is not a bug. Shuffling a test fixture with Math.random() is fine. A hit that ends up in a token, a password or a URL someone shouldn't guess is the one to fix.

A PRNG seeded from the time is only as secret as the time it ran.

The fix is the operating system's cryptographic generator, which every mainstream runtime exposes. In Python, the secrets module:

import secrets token = secrets. token_urlsafe ( 32 ) Enter fullscreen mode Exit fullscreen mode In Node.js, crypto.randomBytes:

import { randomBytes } from " node:crypto "; const token = randomBytes ( 32 ). toString ( " base64url " ); Enter fullscreen mode Exit fullscreen mode In Go, crypto/rand, not math/rand:

import ( "crypto/rand" "encoding/hex" ) func newToken () ( string, error ) { b:= make ([] byte, 32 ) if _, err:= rand. Read ( b ); err!= nil { return "", err } return hex. EncodeToString ( b ), nil } Enter fullscreen mode Exit fullscreen mode In the browser, MDN points to crypto.getRandomValues().

The riddle isn't fully closed. Anthropic writes that "the fifth binary seed, B-163, fits nothing Claude has tried and remains unexplained," and that "we don't know how the base points for P-224, P-256, P-384, and P-521 were generated." Valsorda adds that of two extra selection criteria, one "still escapes us."

On the AI side, be precise about what is public. Anthropic doesn't name the model beyond "an internal research model," and the half-hour figure is its own account of an internal run. What outsiders can check is the result, and that has been checked: the scripts are public, and Valsorda's Go reimplementation is on GitHub. A question that had stood since the late 1990s fell to a guess about old C libraries plus a standard lattice technique, and a human expert confirmed it in code within a day.

I haven't run either script myself. If you have, or if you went looking through your own repo for Math.random() tokens after reading this, tell me what turned up in the comments.

I build Hermes IDE, a free, source-available IDE (BSL 1.1) for macOS, Windows and Linux that runs Claude Code, Codex and other coding agents side by side, each task on its own branch and worktree. If you want an agent to work through that git grep list without touching your main checkout, download it or read the source on GitHub. It isn't affiliated with Anthropic or any agent vendor.

System design interviews keep coming back to the same small decisions: how you mint short links, IDs and session tokens, and what breaks if someone can predict them. The System Design Pocket Guide: Interviews walks through 15 real designs step by step, trade-offs included, so you can reason about those choices out loud. It's one of 8 short books in the Pocket Guides for Developers series, covering agents, RAG, prompts, observability, events, databases and system design.

Quelle: dev.to (dev.to). Original öffnen