3.8 KiB
keygen/generate
Purpose
RSA key-pair generation per NIST FIPS 186-5 Appendix A.1 and
SP 800-56B Rev. 2 §6.3. Two-prime by default; multi-prime
(primes ∈ {3, 4, 5}) when callers explicitly opt in.
The algorithm samples each prime as a random odd bits-wide
integer with the top two bits forced to 1 (so the product
always reaches the target modulus length), checks coprimality
with e, runs the Miller-Rabin probable-prime test from the
sibling module, and assembles a canonical RsaPrivateKey with
all CRT fields populated.
Features
generateRsaKeyPair(options)returns a fully-formedRsaPrivateKeyready for any encrypt / decrypt / sign / verify surface in the package.AbortSignalplumbed through the prime-search loop — yields viaawait Promise.resolve()every 64 candidates so an aborted signal scheduled viaqueueMicrotasklands within ≤ 100 ms.- Deterministic mode — identical seeded
Rngproduces byte-identical keys. Used by tests and by reproducible-build pipelines. - Miller-Rabin exhaustion guard — caps each prime search at
100 000 candidates and surfaces
CRYPTO_RSA.KEYGEN_FAILEDon hitting the cap (vanishingly rare in practice). - Multi-prime delegation —
primes !== 2routes to the siblinggenerateMultiPrimeKeyfor the RFC 8017 §3 multi-prime path. crypto_rsa.keygen_duration_ms{modulus_bits,primes}histogram.
Usage: Basic Example
import { generateRsaKeyPair } from "@suaveplan/crypto-rsa/keygen/generate";
const key = await generateRsaKeyPair({ modulusBits: 2048 });
// key has n, e, d, p, q, dp, dq, qInv all populated
Usage: Advanced Example
Cancel a slow generation on a timeout via AbortSignal:
import { generateRsaKeyPair } from "@suaveplan/crypto-rsa/keygen/generate";
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
const key = await generateRsaKeyPair({
modulusBits: 4096,
signal: ctrl.signal,
});
} catch (err) {
if (err.code === "CRYPTO_RSA.ABORTED") {
// surfaced from the next yield point in the prime search
}
}
Deterministic seeded generation for tests:
const seedRng = makeSeededRng(0xdeadbeef);
const k1 = await generateRsaKeyPair({
modulusBits: 1024,
rng: seedRng(),
});
const k2 = await generateRsaKeyPair({
modulusBits: 1024,
rng: seedRng(), // fresh from same seed
});
// k1 and k2 byte-identical
API Reference
generateRsaKeyPair(options?): Promise<RsaPrivateKey>
options.modulusBits?: number— defaults to 3072.options.publicExponent?: bigint— defaults to65537n.options.primes?: number— defaults to 2; accepts 3–5.options.rng?: Rng— defaults todefaultRng().options.signal?: AbortSignal— optional cancellation.
samplePrime(bits, e, rng, signal): Promise<bigint>
Building block exposed for tests and for the multi-prime path.
Returns a probable prime with the top two bits set and coprime
to e. Throws CRYPTO_RSA.KEYGEN_FAILED after 100 000
candidates without success.
Implementation Notes
- The top-two-bits-set rule guarantees that any two
bits-wide primesp, qproduce ap * qof exactly2 * bitsbits. The post-multiplication length check is a paranoia guard that fails loudly on a pathological RNG draw. - The
bitssplit on oddmodulusBitsis(modulusBits + 1)/2forpandmodulusBits/2forqso the top prime carries the extra bit. λ(n) = lcm(p-1, q-1)per RFC 8017's modern derivation; this produces a strictly smallerdthanφ(n) = (p-1)(q-1)while remaining cryptographically equivalent.await Promise.resolve()between candidate batches is the cheapest yield point in V8 — it lets the microtask queue drain (so aqueueMicrotask-scheduledsignal.abort()lands) without paying forsetTimeout(0).