Encryption Fundamentals
Encryption keeps secrets secret and detects when someone has tampered with data. The cryptographic primitives are mature and standardised; the way to ruin them is almost always at the application layer — wrong mode of operation, reused nonces, predictable IVs, hand-rolled key derivation.
This page is the working set: which primitive solves which problem, and the operational rules that distinguish "secure" from "looks secure."
Two big families
| | Symmetric | Asymmetric |
|---|---|---|
| **Keys** | One shared secret | Public + private pair |
| **Speed** | Fast (GBs/sec on commodity CPUs) | Slow (KBs/sec for RSA, faster for ECC) |
| **Use** | Bulk data encryption | Key exchange, signatures, identity |
| **Examples** | AES, ChaCha20 | RSA, ECDSA, ECDH |
The standard composition: asymmetric to establish a shared symmetric key; symmetric to encrypt the actual data. TLS, Signal, every secure protocol does this. Asymmetric is too slow for bulk; symmetric needs a shared key, which asymmetric provides.
Symmetric encryption
AEAD ciphers
Modern symmetric encryption is AEAD (Authenticated Encryption with Associated Data). One operation provides confidentiality *and* integrity. Don't use older constructions (CBC mode + separate HMAC) for new code.
The two AEAD primitives that matter:
- **AES-GCM** — AES in Galois/Counter Mode. Hardware-accelerated on every modern CPU (AES-NI, ARM crypto extensions). The default for HTTPS, S3 server-side encryption, etc.
- **ChaCha20-Poly1305** — software-fast (no AES-NI needed), constant-time implementations are easy. Used in TLS 1.3 alongside AES-GCM, default in WireGuard.
For new code: AES-GCM if hardware acceleration is available, ChaCha20-Poly1305 otherwise. Both are excellent.
Key sizes
- **AES-128** — 128-bit key. 2^128 brute force; secure for the foreseeable future against classical attackers.
- **AES-256** — 256-bit key. Quantum-resistant against Grover's algorithm (which halves the effective key length); recommended for long-confidentiality data.
- **ChaCha20** — 256-bit key.
Don't use AES-192. It's a strange middle that exists for historical reasons and isn't widely used; AES-128 or AES-256.
Nonce handling: where everyone trips
AEAD ciphers take a nonce (number used once) per encryption. Reusing a nonce with the same key destroys the security of GCM and CCM specifically. ChaCha20-Poly1305 too.
Three correct nonce strategies:
- **Counter** — start at 0, increment per message. Simple, requires you don't crash and reset.
- **Random** — generate from CSPRNG. 96-bit nonces (GCM standard) are safe up to ~2^32 messages per key under birthday-bound logic.
- **Extended-nonce variants** (XChaCha20-Poly1305, XSalsa20-Poly1305) use 192-bit random nonces; collision probability vanishingly small. Use these when you can't reliably manage counters.
Nonce reuse is the single most common AEAD failure. Use a library that manages nonces correctly; don't hand-roll.
Modes you should not use
- **ECB** — deterministic, leaks structural information. Famous for revealing the Linux penguin in encrypted images.
- **CBC without authentication** — vulnerable to padding oracles; malleable.
- **CTR without authentication** — same; ciphertext can be modified meaningfully.
- **Old hash-based MAC + cipher constructions** — easy to combine wrong (encrypt-then-MAC vs MAC-then-encrypt vs encrypt-and-MAC each have different security properties).
If you find yourself needing one of these, you almost certainly want AEAD instead.
Asymmetric encryption and signatures
RSA
The classic. Encryption / signing in the multiplicative group mod a 2048-bit (or larger) composite number.
- **Key size**: 2048-bit minimum, 3072-bit preferred for new keys. Smaller (1024-bit) is broken.
- **Signature**: RSA-PSS for new signatures (PSS, not the older PKCS#1 v1.5 padding). RSA-PKCS#1 v1.5 verification is needed for legacy compatibility.
- **Encryption**: RSA-OAEP padding. Don't use raw RSA or PKCS#1 v1.5 padding — both have known attacks.
In 2026, RSA persists for compatibility but ECC is preferred for new keys.
Elliptic-curve cryptography (ECC)
Same operations (encryption, signatures, key exchange) over an elliptic curve group. Smaller keys for equivalent security:
- **256-bit ECC** ≈ **3072-bit RSA** in security level.
- **384-bit ECC** ≈ **7680-bit RSA**.
Faster, smaller, lower bandwidth. The default for new asymmetric crypto.
Curves to use:
- **Curve25519** (X25519 for ECDH, Ed25519 for signatures) — modern, fast, well-analysed, no obscure parameters. The right default.
- **NIST P-256 (secp256r1)** — older but ubiquitous; required for some compliance regimes (FIPS).
- **NIST P-384, P-521** — for higher security levels.
Avoid:
- **P-224, P-192, B-163** — too short or deprecated.
- **secp256k1** — used by Bitcoin; not the right pick for general crypto (P-256 or Curve25519 instead).
- **Custom curves** — unless you have a specific reason and know what you're doing.
Post-quantum
Shor's algorithm breaks RSA, ECDSA, and ECDH if a sufficiently large quantum computer exists. NIST standardised post-quantum algorithms in 2024:
- **ML-KEM** (Module-Lattice KEM, formerly Kyber) — key encapsulation. Replaces RSA/ECDH for key exchange.
- **ML-DSA** (Module-Lattice DSA, formerly Dilithium) — signatures. Replaces RSA/ECDSA.
For most teams: continue using RSA/ECC; watch for hybrid PQ-classical libraries to mature; plan migration over the next several years. For high-value or long-confidentiality (decades-from-now relevant) data, start adopting hybrid PQ where libraries support it now.
Hash functions
A hash maps arbitrary input to a fixed-length output, ideally with three properties:
- **Pre-image resistance** — given hash `h`, hard to find input `x` with `H(x) = h`.
- **Second pre-image resistance** — given input `x`, hard to find `x' ≠ x` with `H(x) = H(x')`.
- **Collision resistance** — hard to find any `x ≠ x'` with `H(x) = H(x')`.
The collision-resistance bound is roughly half the bit length: SHA-256 has 128-bit collision resistance.
In 2026:
- **SHA-256** — universal default. Hardware-accelerated.
- **SHA-512** — when 256-bit isn't enough; sometimes faster on 64-bit CPUs.
- **SHA-3** (Keccak) — different construction; backup if SHA-2 is ever broken (no signs of that).
- **BLAKE3** — fastest; strong, parallel-friendly. Not yet standardised everywhere but widely deployed.
Avoid: MD5 (broken since 2004), SHA-1 (collisions demonstrated in 2017), SHA-256 truncated to fewer than 128 bits (collision resistance drops below safe threshold).
What hashes are NOT for
- **Password storage.** Use a key derivation function (Argon2id, scrypt, bcrypt). Hashing alone is too fast; brute force is feasible.
- **Encryption.** Hashes are one-way; encryption is reversible. Different problems.
- **Message integrity over a network with an active attacker.** Use HMAC or AEAD. Plain hash + plaintext is vulnerable to length-extension on SHA-2 (use HMAC-SHA-256).
Key derivation
Turn a password or a high-entropy secret into one or more cryptographic keys.
For passwords: **Argon2id**, with appropriate parameters (memory cost, time cost). Parameters tuned to make brute-force expensive while not crippling legitimate logins.
For derivation from already-strong secrets: **HKDF** (HMAC-based Key Derivation Function). Cheap, well-understood, the right choice for "I have a 256-bit secret; I need three 256-bit keys for different purposes."
For older code: PBKDF2 was the standard before Argon2; bcrypt is fine for password hashing if Argon2 isn't available.
Never:
- Use SHA-256 of the password directly. Too fast; brute-force feasible at consumer hardware rates.
- Reuse the master secret as multiple keys. Use HKDF to derive per-purpose keys.
Random number generation
CSPRNG — Cryptographically Secure Pseudo-Random Number Generator. Backed by the OS:
- **`/dev/urandom`** on Linux.
- **`getrandom()`** on modern Linux / glibc.
- **`SecRandomCopyBytes`** on macOS / iOS.
- **`BCryptGenRandom`** on Windows.
Language standard libraries usually wrap these:
- Python: `secrets` module.
- Go: `crypto/rand`.
- Rust: `rand::rngs::OsRng`.
- Java: `SecureRandom` (be careful — older `Random` is not secure).
- JS: `crypto.getRandomValues`.
Never use `rand()`, `Math.random()`, or any non-CSPRNG for cryptographic purposes. They're predictable; they're for simulations and games.
The "don't roll your own" rule
The most often-cited rule in cryptography. It means three different things:
1. **Don't invent your own primitives.** Don't design a new cipher. The state of the art is the result of collective public review by professionals; your hobby cipher will lose.
2. **Don't reimplement existing primitives in production.** Use libraries. Hand-rolled AES will leak side-channel information; the library version is constant-time.
3. **Don't combine primitives in non-standard ways.** Don't invent your own protocol. Use TLS, Signal, age, libsodium constructions. Custom protocols mostly fail to subtle attacks.
What you can do:
- **Use** existing primitives correctly.
- **Compose** them via standardised, well-reviewed protocols.
- **Read** about cryptography to understand what you're using.
Libraries that make doing the right thing easy:
- **libsodium** — Tink, age — high-level APIs that prevent misuse.
- **rustls / BoringSSL / OpenSSL 3.x** — for TLS specifically.
- **Cryptography (Python)** — well-curated set of primitives.
- **Tink (Java/Go/Python/C++)** — Google's misuse-resistant API.
A "what should I use for X" cheat sheet
| Need | Use |
|---|---|
| Encrypt a file with a password | age (`age -p`), or libsodium's `crypto_secretstream` with Argon2id key |
| Encrypt a payload with a known key | AES-GCM or ChaCha20-Poly1305, with a CSPRNG nonce |
| Encrypt for a known recipient | age (`age -r <recipient>`), or libsodium's `crypto_box` |
| Sign data | Ed25519 (`crypto_sign_detached` in libsodium) |
| Authenticate that data hasn't changed | HMAC-SHA-256 |
| Hash a password for storage | Argon2id |
| Generate a session token | 256+ bits from CSPRNG; base64url-encode |
| Hash a file for integrity | SHA-256 or BLAKE3 |
| Establish a shared key over insecure channel | TLS 1.3 if it's a real network; libsodium's `crypto_kx` for in-app |
| Derive multiple keys from a master | HKDF-SHA-256 |
Further reading
- [SslTlsDeepDive]() — TLS as the primary application of these primitives
- [PkiAndCertificates]() — how the asymmetric trust model is structured
- [NumberTheory]() — the math behind RSA / ECC
- [AbstractAlgebra]() — the deeper math behind ECC specifically