sm-crypto: Predictable SM2 Key Generation via Weak PRNG in Node.js
The sm-crypto npm package generates SM2 private keys and signing secrets using Math.random() and wall-clock time in Node.js, making every key it produces by default guessable by an attacker.

The problem
In Node.js, sm-crypto delegates all SM2 key and ephemeral-scalar generation to jsbn's SecureRandom, which seeds an ARC4 pool from window.crypto. Because window is undefined in Node, that branch is silently skipped and the pool is filled instead from Math.random() (V8 xorshift128+, recoverable from ~4 outputs) plus new Date().getTime() (wall clock, attacker-estimable).
This is the default, no-argument path. Every sm2.generateKeyPairHex() call and every SM2 signing operation in Node is affected. The same weak RNG feeds the signing ephemeral scalar k, so a predictable k leaks the private key from a single observed signature.
Proof of concept
A working proof-of-concept for this issue in sm-crypto, with the exact payload below.
// Install: npm install sm-crypto@0.4.0 jsbn@1.1.0
// Pin entropy sources BEFORE requiring sm-crypto so jsbn seeds
// its ARC4 pool from controlled values.
const fixedTime = 1700000000000;
let s = 0x12345678 >>> 0;
Math.random = function () {
s = (Math.imul(s, 1103515245) + 12345) >>> 0;
return s / 0x100000000;
};
const RealDate = globalThis.Date;
class FixedDate extends RealDate {
constructor(...a) { super(...(a.length === 0 ? [fixedTime] : a)); }
}
FixedDate.now = () => fixedTime;
globalThis.Date = FixedDate;
const sm2 = require('sm-crypto').sm2;
const kp = sm2.generateKeyPairHex();
console.log('PRIVATE=' + kp.privateKey);
// Run twice: both invocations print the identical private key.
// Captured: PRIVATE=143268fa0939b4da09eab8c9a2e027a04555b6c433fef4f54fc5edd517c0a6b1jsbn checks window.crypto for the CSPRNG path. Node.js exposes Web Crypto only under globalThis.crypto, so the check is always false in Node and the fallback fills the ARC4 seed pool with Math.random() outputs and Date.getTime(). Because both sources are deterministic given attacker-recoverable state, the entire sm2 key and every signing scalar k is predictable.
The fix in 0.5.0 replaces the jsbn RNG call with Node's crypto.randomBytes (or globalThis.crypto.getRandomValues in browser), bypassing the window.crypto path entirely.
The fix
Upgrade to sm-crypto 0.5.0 (patch commit 1f9bd7bd160c24efd9c26c8f7fda997c68c823d0). That release rewires src/sm2/utils.js to generate random bytes via Node's built-in crypto.randomBytes in Node.js and globalThis.crypto.getRandomValues in browsers, replacing the vulnerable jsbn SecureRandom instance.
Any SM2 keypairs or signatures produced with 0.4.0 in Node.js should be considered compromised and regenerated.
Reported by afldl (diff/ambidiff security research).
Related research
- criticalshescape: Shell Injection via Unescaped Parentheses on Windows CMD
- highShescape: Quadratic-Time Denial of Service in Flag Protection
- high · 7.5CVE-2026-44907CVE-2026-44907: react-server-dom-webpack Denial of Service in Server Functions
- high · 8.8Budibase: App-Scoped Builder Privilege Escalation via Public Role Assignment API