CVE-2026-73567: sm-crypto Predictable SM2 Key Generation Vulnerability

CRITICAL
Advisory Identifier: GHSA-vh45-f885-3848
CVSS 9.1
CWE-338
Published
Advisory Summary

CVE-2026-73567 is a CRITICAL (CVSS 9.1) predictable sm2 key generation vulnerability (CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)) affecting sm-crypto libraries in the npm ecosystem. The issue involves predictable sm2 key generation in node.js: default rng uses math.random + wall clock. Remediation is available by upgrading affected packages to version 0.5.0.

CVE-2026-73567 at a Glance

CVE Identifier CVE-2026-73567
GHSA Identifier GHSA-vh45-f885-3848
Severity CRITICAL
CVSS v3.1 Base Score 9.1
CWE Classification CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
Affected Product sm-crypto
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 9.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-73567

To remediate this vulnerability, upgrade all affected sm-crypto dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

sm-crypto fixed: 0.5.0
npm install sm-crypto@0.5.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls sm-crypto

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-73567, inspect your project dependencies using npm:

npm ls sm-crypto

Analyze the terminal output to locate any version of sm-crypto in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.5.0), your application is affected.

Note: The npm ls sm-crypto command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG), where the product uses a pseudo-random number generator (prng) in a security context, but the prng's algorithm is not cryptographically strong. In sm-crypto, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary `sm-crypto` (npm package **0.4.0**, the latest release, published 2026-01-20) generates SM2 private keys and signing ephemeral scalars from a single module-wide RNG instance (`src/sm2/utils.js`: `const rng = new SecureRandom()`). `SecureRandom` is jsbn's PRNG, which seeds an **ARC4** stream from `window.crypto.getRandomValues` when available. **In Node.js — sm-crypto's primary runtime — `window` is `undefined`, so the CSPRNG branch is skipped** and the seed pool is instead filled from `Math.random()` (V8 `xorshift128+`, recoverable from a few outputs) plus `new Date().getTime()` (wall clock, attacker-estimable). Node *does* expose Web Crypto as `globalThis.crypto`, but jsbn checks `window.crypto`, not `globalThis.crypto`, so the secure path is never taken. Consequently every SM2 private key produced by the default `sm2.generateKeyPairHex()` and every signing ephemeral scalar is derived from non-cryptographic sources and is **predictable** by an attacker who can observe a few `Math.random()` outputs and estimate the generation time. This is the library's **default** (no-argument) path; no caller-selected parameter or configuration is required to trigger it. It is reproduced end-to-end against the unmodified real npm packages (`sm-crypto@0.4.0` + `jsbn@1.1.0`); the PoC below runs against the real installed package, not a copy. The defect is still present on the latest published version (0.4.0) and is not covered by any existing `JuneAndGreen/sm-crypto` issue (0 afldl issues exist; the most recent issues are unrelated SM3/HKDF/PBKDF2 feature requests). ## Details `jsbn@1.1.0` `index.js` — RNG pool initialization (fallback taken in Node): ```js if (rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; if (typeof window !== "undefined" && window.crypto) { // <-- false in Node if (window.crypto.getRandomValues) { /* webcrypto */ } ... } while (rng_pptr < rng_psize) { // <-- fallback path t = Math.floor(65536 * Math.random()); // Math.random() rng_pool[rng_pptr++] = t >>> 8; rng_pool[rng_pptr++] = t & 255; } rng_pptr = 0; rng_seed_time(); // + Date.getTime() } ``` `sm-crypto` `src/sm2/utils.js`: ```js const { SecureRandom } = require('jsbn'); const rng = new SecureRandom(); // single module-wide RNG ... function generateKeyPairHex(a, b, c) { const random = a ? new BigInteger(a, b, c) : new BigInteger(n.bitLength(), rng); // uses rng const d = random.mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE); // private key ... } ``` The default (no-argument) call path uses `rng`, the jsbn ARC4 instance seeded from `Math.random()` + time. The same `rng` feeds the signing ephemeral scalar during SM2 signing. ## PoC The PoC runs against the real installed npm packages. It pins `Math.random` and `Date` **before** `require('sm-crypto')` so jsbn's seed pool is built from controlled inputs. Three independent fresh Node processes then produce the **same** SM2 private key, proving the key is a pure deterministic function of those non-cryptographic sources. It also prints a probe confirming the fallback branch is taken in Node. ### One-line reproducer ```bash WORK=$(mktemp -d) && cd "$WORK" && npm init -y >/dev/null \ && npm install sm-crypto@0.4.0 jsbn@1.1.0 >/dev/null \ && export NODE_PATH="$WORK/node_modules" \ && node poc.js probe && node poc.js deterministic && node poc.js deterministic ``` ### `poc.js` ```js /* * PoC for sm-crypto predictable default RNG in Node.js. * * sm-crypto (npm 0.4.0) generates SM2 private keys / ephemeral scalars using * jsbn's SecureRandom. In a browser jsbn seeds ARC4 from window.crypto, but in * Node.js `window` is undefined so the CSPRNG branch is skipped and the pool is * filled from Math.random() plus new Date().getTime(). Both are * non-cryptographic; the time is attacker-estimable and V8's Math.random is a * recoverable xorshift128+ stream. Consequently SM2 keys produced by the * default path are predictable. * * This PoC proves the key is a deterministic function of those two inputs: we * pin Math.random and the clock to fixed values BEFORE sm-crypto (and therefore * jsbn) is loaded, then generate a keypair. Re-running with the same pinned * values reproduces the exact same private key. */ const MODE = process.argv[2] || 'probe'; // 'probe' | 'deterministic' if (MODE === 'deterministic') { // --- pin entropy sources BEFORE requiring sm-crypto/jsbn --- const fixedTime = 1700000000000; let s = 0x12345678 >>> 0; Math.random = function () { // tiny deterministic LCG standing in for the (already non-crypto) Math.random 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); if (MODE === 'probe') { console.log('--- probe ---'); console.log('typeof window =', typeof window, '(undefined in Node => jsbn CSPRNG branch skipped)'); console.log('typeof globalThis.crypto =', typeof globalThis.crypto, '(Node Web Crypto exists but jsbn checks window.crypto, not globalThis.crypto)'); console.log('Math.random sample =', Math.random()); console.log('Date.now() =', Date.now(), '(attacker-estimable, mixed into ARC4 seed)'); } ``` Real captured output: ``` ===== PROBE (real default path, no patching) ===== PRIVATE=6072e45733a4187791ec28ce906fef18c7d33c8529969e1a852833c4349cfc38 --- probe --- typeof window = undefined (undefined in Node => jsbn CSPRNG branch skipped) typeof globalThis.crypto = object (Node Web Crypto exists but jsbn checks window.crypto, not globalThis.crypto) Math.random sample = 0.704452488761137 Date.now() = 1784455686795 (attacker-estimable, mixed into ARC4 seed) ===== DETERMINISTIC (Math.random + Date pinned before require sm-crypto) ===== --- run #1 --- PRIVATE=143268fa0939b4da09eab8c9a2e027a04555b6c433fef4f54fc5edd517c0a6b1 --- run #2 --- PRIVATE=143268fa0939b4da09eab8c9a2e027a04555b6c433fef4f54fc5edd517c0a6b1 ``` The two deterministic runs produce the **identical** SM2 private key, demonstrating the key is a pure function of `Math.random()` + wall-clock time. ## Impact **Private-key recovery / signature forgery of any SM2 keypair generated with the default API in Node.js.** This is the most serious class of defect for a maintained SM2 library: the *default* key-generation path is non-cryptographic on its primary runtime. - **Private-key recovery.** Any SM2 keypair generated with the default API in Node is derived from `Math.random()` + wall-clock time. An attacker who can observe a few `Math.random()` outputs (V8 `xorshift128+` state is recoverable from ~4 observed doubles) and estimate the generation time can reproduce the private key and forge signatures. - **Signing ephemeral reuse / forgery.** The same RNG feeds the ephemeral scalar `k` during SM2 signing; a predictable `k` leaks the private key from a single signature (SM2 is EC-Schnorr-like: `s = (k^-1)(e + d·r) mod n`). - Pre-authentication / no privilege required: anyone who can induce a victim to generate a key or sign a message (the normal API use) is positioned to predict the secret material. ### Suggested fix Seed the RNG from a CSPRNG in Node. The simplest fix in `sm-crypto` is to replace the jsbn ARC4 instance with Web Crypto / `crypto.randomBytes`: ```js // src/sm2/utils.js const nodeCrypto = (typeof require === 'function') ? require('crypto') : null; function csrandBytes(n) { if (nodeCrypto) return nodeCrypto.randomBytes(n); // Node if (globalThis.crypto) { // Web Crypto (browser/Node ≥ 19) const b = new Uint8Array(n); globalThis.crypto.getRandomValues(b); return b; } throw new Error('no CSPRNG available'); } ``` and use it to generate the private key / ephemeral directly, or to reseed the jsbn pool. A separate (upstream) fix belongs in jsbn to check `globalThis.crypto` in addition to `window.crypto`. ### Affected versions - npm `sm-crypto` **0.4.0** (latest, published 2026-01-20). Depends on `jsbn ^1.1.0` (`jsbn@1.1.0`, whose `index.js` RNG is the root cause). - Runtime: Node.js (the primary runtime; in a browser the jsbn CSPRNG branch is taken). ## Credit Reported by the diff/ambidiff security research effort (afldl).

CVE-2026-73567 Affected sm-crypto Versions

1 component(s)
Fixed Versions:
0.5.0
Product / Package Ecosystem Affected Versions Fixed Version
sm-crypto
npm
< 0.5.0
0.5.0

References

Frequently Asked Questions

What is CVE-2026-73567?
CVE-2026-73567 is a security vulnerability in sm-crypto involving Predictable SM2 key generation in Node.js: default RNG uses Math.random + wall clock under CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
Which sm-crypto versions are affected?
Affected versions include releases prior to 0.5.0 across affected packages: sm-crypto.
What version fixes CVE-2026-73567?
Upgrading to 0.5.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is sm-crypto affected?
Yes, sm-crypto is affected in releases prior to 0.5.0. Upgrade your dependency to 0.5.0 or later.
What is the CVSS score of CVE-2026-73567?
CVE-2026-73567 has a CVSS v3.1 base score of 9.1 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N.
What CWE is associated with CVE-2026-73567?
CVE-2026-73567 is classified under CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) (The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.).
How can I check npm dependencies for CVE-2026-73567?
Run the dependency tree command: 'npm ls sm-crypto'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vh45-f885-3848",
  "modified" : "2026-08-13T17:41:00Z",
  "published" : "2026-07-24T21:50:45Z",
  "aliases" : [ "CVE-2026-73567" ],
  "summary" : "sm-crypto: Predictable SM2 key generation in Node.js: default RNG uses Math.random + wall clock",
  "details" : "## Summary\n\n`sm-crypto` (npm package **0.4.0**, the latest release, published 2026-01-20)\ngenerates SM2 private keys and signing ephemeral scalars from a single\nmodule-wide RNG instance (`src/sm2/utils.js`: `const rng = new SecureRandom()`).\n`SecureRandom` is jsbn's PRNG, which seeds an **ARC4** stream from\n`window.crypto.getRandomValues` when available. **In Node.js — sm-crypto's\nprimary runtime — `window` is `undefined`, so the CSPRNG branch is skipped**\nand the seed pool is instead filled from `Math.random()` (V8 `xorshift128+`,\nrecoverable from a few outputs) plus `new Date().getTime()` (wall clock,\nattacker-estimable).\n\nNode *does* expose Web Crypto as `globalThis.crypto`, but jsbn checks\n`window.crypto`, not `globalThis.crypto`, so the secure path is never taken.\nConsequently every SM2 private key produced by the default\n`sm2.generateKeyPairHex()` and every signing ephemeral scalar is derived from\nnon-cryptographic sources and is **predictable** by an attacker who can observe\na few `Math.random()` outputs and estimate the generation time.\n\nThis is the library's **default** (no-argument) path; no caller-selected\nparameter or configuration is required to trigger it. It is reproduced\nend-to-end against the unmodified real npm packages (`sm-crypto@0.4.0` +\n`jsbn@1.1.0`); the PoC below runs against the real installed package, not a\ncopy. The defect is still present on the latest published version (0.4.0) and\nis not covered by any existing `JuneAndGreen/sm-crypto` issue (0 afldl issues\nexist; the most recent issues are unrelated SM3/HKDF/PBKDF2 feature requests).\n\n## Details\n\n`jsbn@1.1.0` `index.js` — RNG pool initialization (fallback taken in Node):\n\n```js\nif (rng_pool == null) {\n  rng_pool = new Array(); rng_pptr = 0; var t;\n  if (typeof window !== \"undefined\" && window.crypto) {     // <-- false in Node\n    if (window.crypto.getRandomValues) { /* webcrypto */ }\n    ...\n  }\n  while (rng_pptr < rng_psize) {                            // <-- fallback path\n    t = Math.floor(65536 * Math.random());                  //     Math.random()\n    rng_pool[rng_pptr++] = t >>> 8;\n    rng_pool[rng_pptr++] = t & 255;\n  }\n  rng_pptr = 0;\n  rng_seed_time();                                           //     + Date.getTime()\n}\n```\n\n`sm-crypto` `src/sm2/utils.js`:\n\n```js\nconst { SecureRandom } = require('jsbn');\nconst rng = new SecureRandom();                              // single module-wide RNG\n...\nfunction generateKeyPairHex(a, b, c) {\n  const random = a ? new BigInteger(a, b, c)\n                   : new BigInteger(n.bitLength(), rng);     // uses rng\n  const d = random.mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE); // private key\n  ...\n}\n```\n\nThe default (no-argument) call path uses `rng`, the jsbn ARC4 instance seeded\nfrom `Math.random()` + time. The same `rng` feeds the signing ephemeral\nscalar during SM2 signing.\n\n## PoC\n\nThe PoC runs against the real installed npm packages. It pins `Math.random`\nand `Date` **before** `require('sm-crypto')` so jsbn's seed pool is built from\ncontrolled inputs. Three independent fresh Node processes then produce the\n**same** SM2 private key, proving the key is a pure deterministic function of\nthose non-cryptographic sources. It also prints a probe confirming the fallback\nbranch is taken in Node.\n\n### One-line reproducer\n\n```bash\nWORK=$(mktemp -d) && cd \"$WORK\" && npm init -y >/dev/null \\\n  && npm install sm-crypto@0.4.0 jsbn@1.1.0 >/dev/null \\\n  && export NODE_PATH=\"$WORK/node_modules\" \\\n  && node poc.js probe && node poc.js deterministic && node poc.js deterministic\n```\n\n### `poc.js`\n\n```js\n/*\n * PoC for sm-crypto predictable default RNG in Node.js.\n *\n * sm-crypto (npm 0.4.0) generates SM2 private keys / ephemeral scalars using\n * jsbn's SecureRandom. In a browser jsbn seeds ARC4 from window.crypto, but in\n * Node.js `window` is undefined so the CSPRNG branch is skipped and the pool is\n * filled from Math.random() plus new Date().getTime(). Both are\n * non-cryptographic; the time is attacker-estimable and V8's Math.random is a\n * recoverable xorshift128+ stream. Consequently SM2 keys produced by the\n * default path are predictable.\n *\n * This PoC proves the key is a deterministic function of those two inputs: we\n * pin Math.random and the clock to fixed values BEFORE sm-crypto (and therefore\n * jsbn) is loaded, then generate a keypair. Re-running with the same pinned\n * values reproduces the exact same private key.\n */\n\nconst MODE = process.argv[2] || 'probe'; // 'probe' | 'deterministic'\n\nif (MODE === 'deterministic') {\n  // --- pin entropy sources BEFORE requiring sm-crypto/jsbn ---\n  const fixedTime = 1700000000000;\n  let s = 0x12345678 >>> 0;\n  Math.random = function () {\n    // tiny deterministic LCG standing in for the (already non-crypto) Math.random\n    s = (Math.imul(s, 1103515245) + 12345) >>> 0;\n    return s / 0x100000000;\n  };\n  const RealDate = globalThis.Date;\n  class FixedDate extends RealDate {\n    constructor(...a) { super(...(a.length === 0 ? [fixedTime] : a)); }\n  }\n  FixedDate.now = () => fixedTime;\n  globalThis.Date = FixedDate;\n}\n\nconst sm2 = require('sm-crypto').sm2;\nconst kp = sm2.generateKeyPairHex();\nconsole.log('PRIVATE=' + kp.privateKey);\n\nif (MODE === 'probe') {\n  console.log('--- probe ---');\n  console.log('typeof window =', typeof window, '(undefined in Node => jsbn CSPRNG branch skipped)');\n  console.log('typeof globalThis.crypto =', typeof globalThis.crypto, '(Node Web Crypto exists but jsbn checks window.crypto, not globalThis.crypto)');\n  console.log('Math.random sample =', Math.random());\n  console.log('Date.now() =', Date.now(), '(attacker-estimable, mixed into ARC4 seed)');\n}\n```\n\nReal captured output:\n\n```\n===== PROBE (real default path, no patching) =====\nPRIVATE=6072e45733a4187791ec28ce906fef18c7d33c8529969e1a852833c4349cfc38\n--- probe ---\ntypeof window = undefined (undefined in Node => jsbn CSPRNG branch skipped)\ntypeof globalThis.crypto = object (Node Web Crypto exists but jsbn checks window.crypto, not globalThis.crypto)\nMath.random sample = 0.704452488761137\nDate.now() = 1784455686795 (attacker-estimable, mixed into ARC4 seed)\n\n===== DETERMINISTIC (Math.random + Date pinned before require sm-crypto) =====\n--- run #1 ---  PRIVATE=143268fa0939b4da09eab8c9a2e027a04555b6c433fef4f54fc5edd517c0a6b1\n--- run #2 ---  PRIVATE=143268fa0939b4da09eab8c9a2e027a04555b6c433fef4f54fc5edd517c0a6b1\n```\n\nThe two deterministic runs produce the **identical** SM2 private key,\ndemonstrating the key is a pure function of `Math.random()` + wall-clock time.\n\n## Impact\n\n**Private-key recovery / signature forgery of any SM2 keypair generated with\nthe default API in Node.js.** This is the most serious class of defect for a\nmaintained SM2 library: the *default* key-generation path is\nnon-cryptographic on its primary runtime.\n\n- **Private-key recovery.** Any SM2 keypair generated with the default API in\n  Node is derived from `Math.random()` + wall-clock time. An attacker who can\n  observe a few `Math.random()` outputs (V8 `xorshift128+` state is\n  recoverable from ~4 observed doubles) and estimate the generation time can\n  reproduce the private key and forge signatures.\n- **Signing ephemeral reuse / forgery.** The same RNG feeds the ephemeral\n  scalar `k` during SM2 signing; a predictable `k` leaks the private key from a\n  single signature (SM2 is EC-Schnorr-like: `s = (k^-1)(e + d·r) mod n`).\n- Pre-authentication / no privilege required: anyone who can induce a victim\n  to generate a key or sign a message (the normal API use) is positioned to\n  predict the secret material.\n\n### Suggested fix\n\nSeed the RNG from a CSPRNG in Node. The simplest fix in `sm-crypto` is to\nreplace the jsbn ARC4 instance with Web Crypto / `crypto.randomBytes`:\n\n```js\n// src/sm2/utils.js\nconst nodeCrypto = (typeof require === 'function') ? require('crypto') : null;\nfunction csrandBytes(n) {\n  if (nodeCrypto) return nodeCrypto.randomBytes(n);          // Node\n  if (globalThis.crypto) {                                   // Web Crypto (browser/Node ≥ 19)\n    const b = new Uint8Array(n); globalThis.crypto.getRandomValues(b); return b;\n  }\n  throw new Error('no CSPRNG available');\n}\n```\n\nand use it to generate the private key / ephemeral directly, or to reseed the\njsbn pool. A separate (upstream) fix belongs in jsbn to check\n`globalThis.crypto` in addition to `window.crypto`.\n\n### Affected versions\n\n- npm `sm-crypto` **0.4.0** (latest, published 2026-01-20). Depends on\n  `jsbn ^1.1.0` (`jsbn@1.1.0`, whose `index.js` RNG is the root cause).\n- Runtime: Node.js (the primary runtime; in a browser the jsbn CSPRNG branch\n  is taken).\n\n## Credit\n\nReported by the diff/ambidiff security research effort (afldl).",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "sm-crypto"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.5.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/JuneAndGreen/sm-crypto/security/advisories/GHSA-vh45-f885-3848"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/JuneAndGreen/sm-crypto/commit/1f9bd7bd160c24efd9c26c8f7fda997c68c823d0"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/JuneAndGreen/sm-crypto"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-338" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-24T21:50:45Z",
    "nvd_published_at" : null
  }
}