Key Generation
Entropy, key sizes, and why a single bad RNG breaks everything.
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:
- Pick a random odd integer of the target size.
- Test it for primality (Miller-Rabin) until you find one. Repeat for
q. - Compute
n = p × q,φ(n) = (p-1)(q-1). - Pick
e(usually 65537), computed = e⁻¹ mod φ(n).
The expensive step is finding two large primes — typically 100–500ms on a modern CPU for 2048 bits.
For Ed25519:
- Read 32 bytes from
urandom. That's the private key. - SHA-512 it; take the first 32 bytes; clamp three bits.
- 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-seedbefore snapshotting. - Containers without a real RNG. Older container runtimes shared the host RNG poorly; modern ones use vDSO-backed
getrandomand 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- clissh-keygenfree tier
The reference key generator. `ssh-keygen -t ed25519` is the right answer 95% of the time.
- cliOpenSSLfree tier
`openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048` and friends.
- cliage-keygenfree tier
Generates an X25519 keypair for file encryption. Tiny output, modern primitives.
- service
Generate keys inside an HSM-backed service — private material never leaves.