UNPKG

node-rsa

Version:

RSA library for Node.js and browsers

161 lines (139 loc) 8.55 kB
# Changelog ## 2.0.0 — TypeScript rewrite, native `node:crypto` fast paths, security audit fixes Full rewrite of the v1 library in TypeScript with the same public API. The node bundle now routes RSA primitives through `node:crypto` whenever possible, and the browser bundle defaults to native `BigInt`. ### Performance — node bundle uses `node:crypto` natively - **Keygen** uses `crypto.generateKeyPairSync`. 2048-bit drops from ~2.3 s to ~50 ms (~45× faster) on modern hardware; 1024-bit from ~240 ms to ~10 ms. - **PKCS#1 v1.5 and PSS sign/verify** use `crypto.sign` / `crypto.verify`. PSS-SHA256 sign on 2048-bit drops from ~17 ms to sub-millisecond. - OAEP encrypt / PKCS#1 v1.5 encrypt route through `NodeNativeEngine` — also `node:crypto`-backed. ### Performance — browser bundle defaults to native `BigInt` A drop-in BigInteger implementation lives at [src/bigint/big-integer-native.ts](src/bigint/big-integer-native.ts) and uses ES2020's native `BigInt`. The browser bundle picks it at load time; the node bundle stays on the audited jsbn implementation. Round-trips identically through every API; switch back to jsbn with `new NodeRSA(key, { bigIntImpl: 'jsbn' })` if you ever need to. | 2048-bit, JS path | jsbn | native | speedup | |---|---|---|---| | PSS-SHA256 sign | ~16 ms | ~4 ms | **~4×** | | PSS-SHA256 verify | ~0.4 ms | ~0.08 ms | **~5×** | The `bigIntImpl` option (also accepted by `setOptions`) must be set BEFORE the key is imported or generated; switching it on an instance that already has key components throws, since the two implementations produce incompatible BigInteger instances. The browser bundle silently falls back to jsbn on runtimes without `globalThis.BigInt` (i.e. pre-2020 environments). No user action needed. ### Breaking changes - **Min Node.js is now 20**. v1 worked back to Node 8.11; v2 requires Node 20+ for `node:crypto`, `globalThis.crypto`, and modern ESM features. - **Module shape**: ESM-first. `package.json#exports` provides a dual ESM/CJS layout — `import NodeRSA from 'node-rsa'` for ESM, `require('node-rsa').default` for CommonJS. - **Browser default return type is `Uint8Array`** (was `Buffer` via polyfill). Node return type stays `Buffer` (which extends `Uint8Array`, so most existing consumers continue to work). Internal byte handling is `Uint8Array` end-to-end; the Node entry wraps results as `Buffer` at the API boundary. - **No more `Buffer` or `crypto` shims for browsers**. The browser bundle contains zero Node-builtin imports — verified in CI by a `grep` over `dist/index.browser.js`. Bundlers (Vite, Webpack 5, Rollup, esbuild, Parcel) resolve the browser entry via package.json conditional exports. - **`setOptions({environment})` is a deprecated no-op**. Build-time platform conditions decide the runtime now. The option still forces the pure-JS engine path when set to `'browser'`, preserving the v1 semantic that the 61-case test suite relies on. A one-time `console.warn` is emitted on use. - **MD4 is Node-only and provider-gated**. OpenSSL 3 (Node 17+) doesn't load the legacy provider by default, so `crypto.createHash('md4')` throws. v2 probes at module load and reports md4 as unsupported when the provider is absent. The browser bundle never supports MD4. - **`asn1` npm dependency removed**. PKCS#1, PKCS#8, and OpenSSH formats now use a small in-tree DER reader/writer (~150 lines, under [`src/asn1/`](src/asn1)). Byte-identical to v1 output for every fixture key. - **Native PKCS#1 v1.5 `privateDecrypt` is routed through the JS engine on modern Node**. Node has security-deprecated raw PKCS#1 v1.5 decryption (CVE response); v2 transparently falls back to the pure-JS implementation so the call still succeeds. The byte-for-byte plaintext is identical. - **Default signing scheme switched from `pkcs1` (PKCS#1 v1.5) to `pss` (RSASSA-PSS).** PSS is the modern best-practice signing scheme — it has a tighter security reduction and is preferred by RFC 8017 / NIST for new code. Existing signatures produced under the v1 default remain verifiable by passing `signingScheme: 'pkcs1'` explicitly. ```ts // To keep v1's PKCS#1 v1.5 default explicit: const key = new NodeRSA(null, { signingScheme: 'pkcs1' }); const sig = key.sign('msg'); ``` The bare-hash shorthand `setOptions({ signingScheme: 'sha256' })` also resolves to `pss-sha256` (was `pkcs1-sha256` in v1). Set `signingScheme: 'pkcs1-sha256'` explicitly to keep v1 behaviour. - **Custom MGF for PSS now throws on the node bundle.** `node:crypto` only supports MGF1 with hash equal to the signing hash. If you need a non-default MGF, force the pure-JS path with `setOptions({ environment: 'browser' })`. - **Hash algorithms unsupported by the local OpenSSL build now throw at sign/verify time on the node bundle.** Functionally equivalent to v1 (the JS scheme delegated to `nodeBackend.digest` which also threw) — only the error wording and call-site changed. ### Security fixes (no API change) - **OAEP decode is now constant-time** (RFC 8017 §7.1.2). Closes a Manger- style padding-oracle (~10⁵ queries to recover plaintext given a timing oracle). Includes a missing `Y == 0x00` check on the leading byte and a post-decode message-length bound. - **PKCS#1 v1.5 decode is now constant-time** internally (RFC 8017 §7.2.2, Bleichenbacher / ROBOT). Closes the internal differential timing oracle; the valid/invalid binary oracle inherent to PKCS#1 v1.5 remains — use OAEP for untrusted ciphertexts (the README has a security note). - **PSS verify is now constant-time** (RFC 8017 §9.1.2 step 11). - **Private-key operations are blinded** (Kocher 1996 / Brumley-Boneh 2003 defence). Fresh `r ← random coprime to n` masks the variable-time `modPow` from any timing leak on `d`, `dmp1`, or `dmq1`. - **Miller-Rabin uses CSPRNG witnesses** in [2, n-2] (was `Math.random()` over a 168-element fixed table — adversarial-pseudoprime risk) and now honours the caller's full round count (was silently halved). Keygen picks adaptive rounds by bit length per FIPS 186-4 Table C.3. - **Public exponent validated on import**: `1 < e` with e odd (RFC 8017 §3.1). - **RSA primitive bounds-check**: `0 ≤ x < n` enforced in both `$doPrivate` and `$doPublic` (RFC 8017 §3.2). `verify()` translates the resulting out-of-range error to "invalid signature" per §8.x. - **Imported private keys are CRT-consistency-checked**: `n = p·q`, `dp ≡ d mod (p−1)`, `dq ≡ d mod (q−1)`, `q·coeff ≡ 1 mod p`, `e·dp ≡ 1 mod (p−1)`, `e·dq ≡ 1 mod (q−1)`. Closes a Boneh-DeMillo- Lipton fault-injection vector on crafted PEM/PKCS#8/OpenSSH files. - **`generate(B)` refuses `B < 512`** (cryptographically broken) and emits a one-shot `console.warn` for `B < 2048` (below NIST SP 800-56B §6.1.6.2 minimum). - **Fermat-distance defence**: keygen rejects p, q pairs with `|p − q| < 2^(B/2 − 100)` (FIPS 186-4 §B.3.6). - **CRT recombination is branch-free**: removed the data-dependent `while (xp < xq) xp += p` loop. - **OpenSSH parser hardening**: `SshReader.readString` bounds-checks before `subarray`; the two private-section checkints (`checkint1`, `checkint2`) are now validated for equality. - **PKCS#8 parser hardening**: outer version validated against {0, 1} (RFC 5958 §2); inner PKCS#1 version restricted to two-prime (RFC 8017 §A.1.2); algorithm OID whitelist with clear diagnostics for PSS-only (1.2.840.113549.1.1.10) and OAEP-only (.1.1.7) misuse. ### Added - TypeScript types for every public surface (`NodeRSAOptions`, `EncryptionSchemeOptions`, `SigningSchemeOptions`, `HashAlg`, format string union types). - `@noble/hashes` runtime dependency for synchronous SHA/MD/RIPEMD digests in the browser bundle. ~6 KB gzipped, audited, zero-dep. - Bundle size budget (CI-enforced): - `dist/index.browser.js`: <100 KB raw / <30 KB gzipped (currently 90/21) - `dist/index.node.{js,cjs}`: <120 KB raw / <35 KB gzipped (currently 94/22) ### Internal - Modern tooling: `tsup` for build (esbuild), `vitest` for tests (with a workspace running every spec in two projects — `node` and `browser-emulated`), `biome` for lint+format, strict TypeScript with `noUncheckedIndexedAccess` / `exactOptionalPropertyTypes` / `noImplicitOverride` etc. - 1006 test cases across 27 files. The v1 mocha suite of 61 `it()` blocks is ported verbatim and runs in both vitest projects. ## 1.1.1 and earlier See git history.