DEV Community

correctover
correctover

Posted on Edited on

Your keys are weaker than your cryptography: CVE-2026-73567 and the dead RNG that seeded SM2 keys

Math.random() is not a key generator. Everyone knows this. And yet — when a widely deployed SM2 library seeded its key material at module load, that's effectively what happened. Except worse: in Node.js, the randomness pool was never even filled.

This is the story of CVE-2026-73567, a vulnerability in which our Node.js impact correction was incorporated into the public advisory by the maintainer — and what it teaches us about the whole class of weak-entropy key compromises.

The discovery

While auditing key-generation paths in SM2 (the Chinese national elliptic-curve standard, mandatory in regulated industries there), I noticed something odd in the dependency chain: a big-number library harvested entropy from Math.random() — the non-cryptographic, browser-grade PRNG — at module initialization, and used it to seed its internal state. That state then fed private-key generation.

The publicly discussed impact assumed ~128 Math.random() calls at startup. When I built an instrumented Node victim and counted, the real number was zero. V8 defers Math.random()'s pool initialization in a way that left the seeding path reading predictable values — the effective root cause was a clock-only seed, and the advisory's root cause was corrected accordingly.

The consequence: given roughly a millisecond-scale time window, private keys were recoverable by enumeration. Sub-second. On an ordinary laptop.

And it didn't stop at static keys. The same RNG stream also fed signing nonces and ephemeral encryption keys. That means a key used only for signing was equally recoverable, and ciphertext from affected versions could be decrypted without the key.

What happened next

Beyond the advisory, our independent reproduction verified that the actual root cause was not merely weak entropy, but a clock-only seed due to dead code in the Math.random() pool-fill — a correction formally recognized and credited in GHSA-vh45-f885-3848.

  • Versions before the fix remain vulnerable forever. Every key generated by an unpatched build in a predictable-time window should be considered compromised.

This is a class, not an incident

One library got caught. But weak entropy is a systematic failure mode across the key-management stack:

  • V8's PRNG (Math.random(), xorshift128+ / MWC1616 eras) can be fully state-recovered from a handful of consecutive outputs — meaning any JS wallet or signing code that ever touches non-CSPRNG randomness is one observation away from compromise.
  • Historical events (Randstorm-class bugs, 2014-era wallet RNG flaws) show the same pattern repeating across ecosystems.
  • Cross-address clustering: weak-RNG victims often reuse exposed material across chains and address types, turning one bad key into a trail of them.
  • The SM2 ecosystem is structurally exposed here: guomi compliance pushes SM2 into places where the surrounding JS/Node key hygiene was never designed for it.

If your product generates keys, signs, or encrypts — in JavaScript especially — the question isn't whether your crypto is strong. It's whether your entropy path is.

What we do about it

At Correctover we recover keys from weak entropy — to prove the exposure before someone malicious does, and to build evidence that holds up:

  • Black-box weak-entropy health checks on key generation and signing paths
  • Real recovery demonstrations in isolated environments, with signed, timestamped evidence receipts (Ed25519 + RFC 3161) — findings a court or auditor can verify without trusting us
  • Coordinated disclosure done right: the advisory process above is our reference case

If you build wallets, custody, or exchange infrastructure and want your entropy path audited against this exact attack class, reach out: wangguigui@correctover.com.


Beyond the advisory, our independent reproduction verified that the actual root cause was not merely weak entropy, but a clock-only seed due to dead code in the Math.random() pool-fill — a correction formally recognized and credited in GHSA-vh45-f885-3848.


Tools and evidence: github.com/DSHCorrectover — runtime verification for AI agents & node infrastructure.

Top comments (0)