Files
skills/docs/security-stack/crypto/crypto-rsa/src/keygen/generate.md
2026-05-29 08:20:56 +01:00

3.8 KiB
Raw Blame History

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-formed RsaPrivateKey ready for any encrypt / decrypt / sign / verify surface in the package.
  • AbortSignal plumbed through the prime-search loop — yields via await Promise.resolve() every 64 candidates so an aborted signal scheduled via queueMicrotask lands within ≤ 100 ms.
  • Deterministic mode — identical seeded Rng produces 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_FAILED on hitting the cap (vanishingly rare in practice).
  • Multi-prime delegation — primes !== 2 routes to the sibling generateMultiPrimeKey for 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 to 65537n.
  • options.primes?: number — defaults to 2; accepts 3–5.
  • options.rng?: Rng — defaults to defaultRng().
  • 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 primes p, q produce a p * q of exactly 2 * bits bits. The post-multiplication length check is a paranoia guard that fails loudly on a pathological RNG draw.
  • The bits split on odd modulusBits is (modulusBits + 1)/2 for p and modulusBits/2 for q so the top prime carries the extra bit.
  • λ(n) = lcm(p-1, q-1) per RFC 8017's modern derivation; this produces a strictly smaller d than φ(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 a queueMicrotask-scheduled signal.abort() lands) without paying for setTimeout(0).