{"id":"CVE-2026-73567","revision":null,"records":[{"revision":"418390517233408f89b5ba8230c1750f","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-73567","aliases":[],"title":"sm-crypto: Predictable SM2 key generation in Node.js: default RNG uses Math.random + wall clock","description":"sm-crypto provides JavaScript implementations of the Chinese cryptographic algorithms SM2, SM3, and SM4. Prior to 0.5.0, the default no-argument sm2.generateKeyPairHex() path in Node.js uses the module-wide SecureRandom instance in src/sm2/utils.js, supplied by jsbn@1.1.0, which seeds an ARC4 stream from Math.random() and new Date().getTime() because window.crypto.getRandomValues is unavailable even though globalThis.crypto exists. An attacker who can observe the process's Math.random() outputs and estimate the key-generation time can reconstruct the seed, recover generated SM2 private keys, and predict signing ephemeral scalars used to forge signatures. This issue is fixed in version 0.5.0.","state":0,"publishedAt":"2026-08-13T17:40:54.016+00:00","modifiedAt":"2026-08-14T18:09:20.763+00:00","affected":[{"ecosystem":"Vendor product","name":"sm-crypto","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"JuneAndGreen\",\n                    \"product\": \"sm-crypto\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 0.5.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":9.1,"severity":"CRITICAL"}],"references":["https://github.com/JuneAndGreen/sm-crypto/security/advisories/GHSA-vh45-f885-3848","https://github.com/JuneAndGreen/sm-crypto/commit/1f9bd7bd160c24efd9c26c8f7fda997c68c823d0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-73567","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-73567","identifiers":["CVE-2026-73567"]},"retained":false,"historicalAffected":[]},{"revision":"68c0f48035b64f9690a5d960451828b4","observedAt":"2026-10-25T15:19:09.409+00:00","statement":{"source":"nvd","id":"CVE-2026-73567","aliases":[],"title":"sm-crypto provides JavaScript implementations of the Chinese cryptographic algorithms SM2, SM3, and SM4. Prior to 0.5.0, the default no-argument sm2.generateKeyPairHex() path in Node.js uses the module-wide SecureRandom instance in src/sm2/…","description":"sm-crypto provides JavaScript implementations of the Chinese cryptographic algorithms SM2, SM3, and SM4. Prior to 0.5.0, the default no-argument sm2.generateKeyPairHex() path in Node.js uses the module-wide SecureRandom instance in src/sm2/utils.js, supplied by jsbn@1.1.0, which seeds an ARC4 stream from Math.random() and new Date().getTime() because window.crypto.getRandomValues is unavailable even though globalThis.crypto exists. An attacker who can observe the process's Math.random() outputs and estimate the key-generation time can reconstruct the seed, recover generated SM2 private keys, and predict signing ephemeral scalars used to forge signatures. This issue is fixed in version 0.5.0.","state":0,"publishedAt":"2026-08-13T18:18:19.387+00:00","modifiedAt":"2026-10-24T15:10:00.19+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":9.1,"severity":"CRITICAL"}],"references":["https://github.com/JuneAndGreen/sm-crypto/commit/1f9bd7bd160c24efd9c26c8f7fda997c68c823d0","https://github.com/JuneAndGreen/sm-crypto/security/advisories/GHSA-vh45-f885-3848"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-73567","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-338"],"productCriteria":[],"sourceDates":{"published":"2026-08-13T18:18:19.387","lastModified":"2026-10-24T15:10:00.190"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-73567","identifiers":["CVE-2026-73567"]},"retained":false,"historicalAffected":[]},{"revision":"5e39eaaa7c3a424294934d909a1b320d","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-vh45-f885-3848","aliases":["CVE-2026-73567"],"title":"sm-crypto: Predictable SM2 key generation in Node.js: default RNG uses Math.random + wall clock","description":"## 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).","state":0,"publishedAt":"2026-07-24T21:50:45+00:00","modifiedAt":"2026-08-13T17:55:56.606058+00:00","affected":[{"ecosystem":"npm","name":"sm-crypto","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"0.5.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"CRITICAL"}],"references":["https://github.com/JuneAndGreen/sm-crypto/security/advisories/GHSA-vh45-f885-3848","https://github.com/JuneAndGreen/sm-crypto/commit/1f9bd7bd160c24efd9c26c8f7fda997c68c823d0","https://github.com/JuneAndGreen/sm-crypto"],"sourceUrl":"https://github.com/advisories/GHSA-vh45-f885-3848","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-vh45-f885-3848","identifiers":["GHSA-vh45-f885-3848","CVE-2026-73567"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-25T23:02:08.9235896+00:00","complete":true,"records":1756,"lastAttemptAt":"2026-10-25T23:02:08.9235896+00:00","lastSuccessAt":"2026-10-25T23:02:09.0869487+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:09.0869513+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-1a687ad2a0b90150aa7d97c9f39e63de695ae2db23e31ebc28df1492c461b23c-5e0a45ae0cf63c80268dd44eaeb20879d480eba4da3306bde3dcd925fa4c114f","complete":true,"records":414047,"lastAttemptAt":"2026-10-25T23:02:08.5196863+00:00","lastSuccessAt":"2026-10-25T23:02:08.4495153+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:08.5196863+00:00","snapshotPublishedAt":"2026-10-25T22:26:55+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-25T23:02:11.7890287+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":4764,"expectedIdentifiers":13474},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-25T23:02:08.9235896+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-25T23:02:08.9235896+00:00","lastSuccessAt":"2026-10-25T23:02:09.087647+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:09.08765+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-204a3fb93a00487e8f7f36db01dc851d","complete":true,"records":13413,"lastAttemptAt":"2026-10-25T23:02:09.6218557+00:00","lastSuccessAt":"2026-10-25T16:19:28.681+00:00","error":null,"lastVerifiedAt":"2026-10-25T16:19:28.681+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13474,"expectedIdentifiers":13474},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-2375a4e597279abf92e8747e140fe8da9d421877cd49cae3ac69a2cb78469b2c","complete":true,"records":9779,"lastAttemptAt":"2026-10-25T23:02:08.8808579+00:00","lastSuccessAt":"2026-10-24T13:19:53.1346702+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:08.8808579+00:00","snapshotPublishedAt":"2026-10-24T03:03:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-16ad89dfea13ab9e9b0108e63a759d63bd4559cc68b58491107d62ee35bbbaf2","complete":true,"records":1965,"lastAttemptAt":"2026-10-25T23:02:08.6737644+00:00","lastSuccessAt":"2026-10-23T16:01:26.4487605+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:08.6737644+00:00","snapshotPublishedAt":"2026-10-21T12:18:07+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-7da67a15aa6e56a0e3081a7646dc74d537e6ef974afc593c2b1794ed01970648","complete":true,"records":2969,"lastAttemptAt":"2026-10-25T23:02:08.8933341+00:00","lastSuccessAt":"2026-10-24T13:19:59.1145547+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:08.8933341+00:00","snapshotPublishedAt":"2026-10-24T08:47:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-11236ada05a0588e51df37636716526867f73fb127392c49680b6f8561496a99","complete":true,"records":230781,"lastAttemptAt":"2026-10-25T23:02:08.7805888+00:00","lastSuccessAt":"2026-10-25T22:04:07.173333+00:00","error":null,"lastVerifiedAt":"2026-10-25T23:02:08.7805888+00:00","snapshotPublishedAt":"2026-10-25T21:33:06+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}