CVE-2021-41117 Overview
CVE-2021-41117 affects keypair, an RSA PEM key generator written in JavaScript. The library generates predictable RSA keys due to flawed random number generation in Node.js environments. When window.crypto.getRandomValues() is unavailable, keypair falls back to a Lehmer linear congruential generator (LCG) seeded with Math.random. A coding error causes most seed bytes to be zero, producing identical or easily guessable RSA keys. Attackers can recover private keys, decrypt confidential messages, or impersonate victims authenticating with SSH keys generated by this library. The vulnerability is categorized under [CWE-335] Incorrect Usage of Seeds in Pseudo-Random Number Generator.
Critical Impact
RSA keys generated by keypair in Node.js are guessable, enabling attackers to decrypt protected communications or gain unauthorized access to accounts secured by these keys.
Affected Products
- keypair npm package (juliangruber/keypair)
- Node.js applications using keypair for RSA key generation
- SSH key provisioning workflows depending on keypair
Discovery Timeline
- 2021-10-11 - CVE-2021-41117 published to NVD
- 2024-11-21 - Last updated in NVD database
Technical Details for CVE-2021-41117
Vulnerability Analysis
The keypair library implements its own counter-based AES-CMAC pseudo-random number generator. Seeding occurs in the defaultSeedFile function, which selects an entropy source based on the JavaScript runtime. In browsers, the library uses window.crypto.getRandomValues(). In Node.js, the window object is undefined, so the code attempts to use Node's crypto module. A local variable named crypto is initialized to null, which shadows the intended module reference and prevents the strong CSPRNG path from executing.
With both strong entropy sources bypassed, the library falls back to a Lehmer LCG seeded with Math.random. This alone would be a serious cryptographic weakness. The fallback also contains an encoding bug that amplifies the impact dramatically.
Root Cause
The flaw lives in this seed-write operation: b.putByte(String.fromCharCode(next & 0xFF)). The putByte method itself invokes String.fromCharCode(b) on its argument, producing a double conversion equivalent to String.fromCharCode(String.fromCharCode(next & 0xFF)). The outer call receives a string, not a number, and returns NaN-mapped zero for any input outside the ASCII digit range. Approximately 97% of LCG outputs masked with 0xFF produce zero bytes. The remaining outputs collapse to the ASCII codes for digits 0 through 9.
Attack Vector
Because seed entropy is restricted to a tiny effective key space, the resulting RSA prime values P and Q are repeatedly drawn from a small distribution. Practical observation showed identical RSA-2048 keys produced across independent runs. An attacker who knows a victim used keypair can precompute or brute-force candidate private keys, then decrypt traffic, forge signatures, or authenticate to SSH servers as the victim. Exploitation requires no network interaction with the victim beyond obtaining the public key.
The vulnerability is a logic flaw in seeding code rather than a memory-safety
issue. See the GitHub Security Lab advisory for the complete technical
analysis: https://securitylab.github.com/advisories/GHSL-2021-1012-keypair/
Detection Methods for CVE-2021-41117
Indicators of Compromise
- Duplicate RSA public keys observed across distinct hosts, users, or deployments using keypair
- RSA modulus values sharing common factors when batch-GCD analysis is run against collected public keys
- SSH authorized_keys entries generated programmatically by Node.js services that depend on keypair
Detection Strategies
- Inventory package.json and package-lock.json files across repositories and build artifacts for references to keypair
- Run software composition analysis (SCA) tooling to flag any version of the keypair package as vulnerable
- Audit historical RSA keys generated by Node.js services and compare moduli for duplication or shared prime factors
Monitoring Recommendations
- Alert on new builds or container images that introduce the keypair dependency
- Track SSH key rotation events and validate that replacement keys originate from crypto.generateKeyPairSync or another vetted source
- Log authentication anomalies on services that accept keys historically produced by keypair
How to Mitigate CVE-2021-41117
Immediate Actions Required
- Stop using keypair for any RSA key generation in production code paths
- Treat all RSA keys previously generated by keypair as compromised and rotate them
- Revoke SSH authorized_keys entries and TLS certificates derived from keys produced by the library
Patch Information
No patched release of keypair is identified in the GitHub Security Advisory GHSA-3f99-hvg4-qjwj. Migrate to Node.js built-in crypto.generateKeyPairSync('rsa', ...), the node-forge library used directly with a strong CSPRNG, or jose for application-layer key material. Confirm the replacement uses crypto.randomBytes or crypto.webcrypto.getRandomValues as the entropy source.
Workarounds
- Replace keypair with Node.js built-in crypto.generateKeyPairSync for RSA key generation
- If keypair must remain temporarily, generate keys only in a browser environment where window.crypto.getRandomValues() is available, then transfer them securely
- Add a CI policy that fails builds containing the keypair dependency
# Replace keypair with Node.js built-in crypto
npm uninstall keypair
# Use crypto.generateKeyPairSync instead:
node -e "const { generateKeyPairSync } = require('crypto'); \
const { publicKey, privateKey } = generateKeyPairSync('rsa', { \
modulusLength: 2048, \
publicKeyEncoding: { type: 'spki', format: 'pem' }, \
privateKeyEncoding: { type: 'pkcs8', format: 'pem' } \
}); \
console.log(publicKey);"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

