asymmetric · level 2

Key Generation

Entropy, key sizes, and why a single bad RNG breaks everything.

150 XP

Key Generation

Every asymmetric primitive — RSA, ECDSA, Ed25519 — starts with the same step: generate a private key from random bits. Get this step wrong and nothing else you do matters. The cipher is fine. The protocol is fine. The implementation is fine. The keys are dead on arrival.

Where the bits come from

A private key is just a string of bytes drawn from a high-quality source of randomness. The phrase "high-quality" is doing real work there.

Source Suitable for keys?
/dev/urandom (Linux, macOS) Yes — kernel CSPRNG, seeded from real entropy
getrandom(2) syscall (Linux) Yes — same pool, blocks only on first boot
BCryptGenRandom (Windows) Yes
arc4random_buf (BSD) Yes
Math.random() Never — predictable PRNG
time.time() seed Never — guessable
/dev/random (Linux) Acceptable but pointless — same algorithm as urandom, just blocks pointlessly

The myth that /dev/random is "more secure" than /dev/urandom is wrong. Both draw from the same kernel CSPRNG. /dev/random adds artificial blocking that does nothing useful in modern kernels. Read urandom.

When the RNG breaks

Every famous key-generation disaster traces back to entropy:

Debian OpenSSL (2008). A maintainer "fixed" a Valgrind warning by removing a line that mixed entropy into the OpenSSL RNG. The result: every Debian-generated key, for two years, came from one of 32,768 possible values. SSH keys, TLS certs, GPG keys — all generated on Debian in that window had to be regenerated. It took years to clean up.

Sony PS3 / ECDSA (2010). Sony reused the same nonce to sign every game. ECDSA leaks the private key when two signatures share a nonce. fail0verflow recovered the master signing key in an afternoon.

Embedded TLS keys (2012). Heninger et al. scanned 12.8 million TLS hosts. Roughly 0.75% shared a key with another host because their boot-time RNG was exhausted before sshd or nginx generated their keys. Routers and IoT devices were the worst offenders.

The pattern is always the same: an attacker doesn't break the cipher; they break the assumption that the keys were random.

Key sizes

You pick a primitive, the primitive picks a key-size discipline. Don't second-guess these unless you have a specific reason.

RSA

Bits Security level When to use
1024 Broken Never (factored in 2010)
2048 ~112 bits Default through 2030 (NIST SP 800-57)
3072 128 bits New deployments past 2030
4096 ~150 bits Long-haul keys, archival, TPM-backed certs

RSA at 4096 is twice as slow as 2048 and 4× slower for signing. Do not pick 4096 unless you actually need it.

Elliptic curves

You get the same security level at a fraction of the size:

Curve Equivalent RSA Where you see it
P-256 / secp256r1 3072-bit RSA TLS, code signing
P-384 7680-bit RSA NSA Suite B, top-secret
Ed25519 3072-bit RSA SSH, Signal, age, JWT
X25519 3072-bit RSA TLS 1.3 ECDHE
secp256k1 3072-bit RSA Bitcoin, Ethereum

A 32-byte ed25519 key is as strong as a 384-byte RSA key. The handshake bytes you save matter at scale.

What about post-quantum?

A 2048-bit RSA key is currently safe but is harvest-now-decrypt-later vulnerable to a sufficiently powerful quantum computer (Shor's algorithm runs in polynomial time). NIST has now standardised lattice-based replacements (Kyber for KEM, Dilithium for signatures). For new long-lived archival keys, hybrid X25519+Kyber is the emerging answer; see the post-quantum lesson.

The right defaults today

A practical decision tree:

Do you control both endpoints?
├── Yes → Ed25519 (signing) + X25519 (key exchange).
└── No  → Does the peer accept ECDSA P-256?
         ├── Yes → ECDSA P-256.
         └── No  → RSA 2048 (or 3072 for keys living past 2030).

The "do I control both endpoints" question is the only one that really matters. Ed25519 is faster, smaller, and immune to the nonce-reuse class of bug — but it's a 2010s primitive and some legacy systems still don't accept it.

What actually happens during key generation

For RSA:

  1. Pick a random odd integer of the target size.
  2. Test it for primality (Miller-Rabin) until you find one. Repeat for q.
  3. Compute n = p × q, φ(n) = (p-1)(q-1).
  4. Pick e (usually 65537), compute d = e⁻¹ mod φ(n).

The expensive step is finding two large primes — typically 100–500ms on a modern CPU for 2048 bits.

For Ed25519:

  1. Read 32 bytes from urandom. That's the private key.
  2. SHA-512 it; take the first 32 bytes; clamp three bits.
  3. Multiply the curve's base point by the clamped value. That's the public key.

Total cost: under a millisecond. No primality testing, no random search. Why ed25519 is faster: there's nothing to hunt for.

What can go wrong even with good entropy

  • Forking after seeding. If you seed an RNG in a parent process and then fork(), both children share the seed and produce identical keys. OpenSSL guards against this; don't reimplement.
  • Cloning a VM image with stored entropy state. If two VMs boot from the same snapshot, they have the same RNG state. Wipe /var/lib/systemd/random-seed before snapshotting.
  • Containers without a real RNG. Older container runtimes shared the host RNG poorly; modern ones use vDSO-backed getrandom and are fine. Verify in your environment.
  • Hardware RNG failures. Intel's RDRAND has had silent-failure modes; production code should mix RDRAND with kernel entropy, not rely on it alone.

What this lesson asks of you

The playground walks five real-world scenarios — fresh VM, archival key, FIPS-required, RSA-only vendor, modern SSH — and you pick the right primitive and size for each. The visualizer shows the key-generation flow for both RSA and Ed25519 side by side so you can see exactly why elliptic-curve key generation is so much faster.

Tools in the wild

4 tools
  • ssh-keygenfree tier

    The reference key generator. `ssh-keygen -t ed25519` is the right answer 95% of the time.

    cli
  • OpenSSLfree tier

    `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048` and friends.

    cli
  • age-keygenfree tier

    Generates an X25519 keypair for file encryption. Tiny output, modern primitives.

    cli
  • Generate keys inside an HSM-backed service — private material never leaves.

    service