Symmetric Cryptography
Symmetric (secret-key) cryptography uses the same key for encryption and decryption. It is the workhorse of modern cryptography -- fast enough for bulk data encryption, used in TLS, disk encryption, and secure messaging.
Block Ciphers
A block cipher encrypts fixed-size blocks (typically 128 bits) under a key. It is a keyed pseudorandom permutation: for each key, it defines a bijection on the block space.
Feistel Networks
A construction used by DES, Blowfish, and others. Splits the block into halves (L, R) and applies rounds:
Round i:
L_i = R_{i-1}
R_i = L_{i-1} XOR F(R_{i-1}, K_i)
F is the round function (does not need to be invertible). K_i is the subkey for round i. Decryption uses the same structure with subkeys in reverse order.
Advantage: Decryption works even if F is not invertible. Disadvantage: Only half the block is updated per round (needs more rounds).
DES (Data Encryption Standard)
- Block size: 64 bits. Key size: 56 bits (8 parity bits discarded from 64-bit key).
- 16 Feistel rounds. Each round uses a 48-bit subkey derived from the 56-bit key via key schedule.
- Round function F: Expansion (32→48 bits), XOR with subkey, 8 S-boxes (6→4 bits each, non-linear), permutation.
- Broken: 56-bit key is brute-forcible. DES was cracked in 22 hours by EFF's Deep Crack (1998).
- 3DES: Encrypt-Decrypt-Encrypt with 2 or 3 keys. Key size 112 or 168 bits. Slow; deprecated in favor of AES.
AES (Advanced Encryption Standard)
Rijndael algorithm, selected by NIST in 2001. Not a Feistel cipher -- operates on the full block each round.
- Block size: 128 bits (4x4 byte matrix, called the state).
- Key sizes: 128, 192, or 256 bits (10, 12, or 14 rounds respectively).
AES Round Operations
Each round applies four transformations to the state:
SubBytes: Each byte is substituted via an S-box (a fixed 256-entry lookup table). The S-box is derived from multiplicative inverse in GF(2^8) followed by an affine transformation. Provides non-linearity (confusion).
State byte a_{i,j} → S-box[a_{i,j}]
ShiftRows: Row i is cyclically shifted left by i positions:
Row 0: no shift [a b c d] → [a b c d]
Row 1: shift 1 left [e f g h] → [f g h e]
Row 2: shift 2 left [i j k l] → [k l i j]
Row 3: shift 3 left [m n o p] → [p m n o]
Provides diffusion across columns.
MixColumns: Each column is multiplied by a fixed matrix over GF(2^8):
[2 3 1 1] [s0] [s0']
[1 2 3 1] × [s1] = [s1']
[1 1 2 3] [s2] [s2']
[3 1 1 2] [s3] [s3']
Provides diffusion within columns. Skipped in the final round.
AddRoundKey: XOR the state with the round subkey (derived from the key schedule). This is the only step that introduces key material.
After 4 rounds, every output bit depends on every input bit and every key bit (full diffusion).
AES-NI: Modern x86 CPUs include hardware instructions (AESENC, AESDEC) that perform a full AES round in one instruction. Throughput: ~1 byte/cycle.
Modes of Operation
Block ciphers encrypt fixed-size blocks. Modes define how to encrypt messages of arbitrary length.
ECB (Electronic Codebook)
Each block encrypted independently: C_i = E_K(P_i).
Fatal flaw: Identical plaintext blocks produce identical ciphertext blocks. Leaks patterns (the "ECB penguin" problem). Never use ECB for data encryption.
CBC (Cipher Block Chaining)
Each plaintext block is XORed with the previous ciphertext block before encryption:
C_0 = E_K(P_0 XOR IV)
C_i = E_K(P_i XOR C_{i-1})
IV must be unpredictable (random) for each message. Sequential, so not parallelizable for encryption (but decryption is parallelizable). Requires padding (PKCS#7). Vulnerable to padding oracle attacks (POODLE, Lucky Thirteen) if not used carefully.
CTR (Counter Mode)
Turns a block cipher into a stream cipher. Encrypts a counter to produce a keystream:
Keystream_i = E_K(Nonce || Counter_i)
C_i = P_i XOR Keystream_i
Parallelizable for both encryption and decryption. No padding needed. Random access to any block. Nonce must never repeat with the same key (nonce reuse completely breaks security).
GCM (Galois/Counter Mode)
CTR mode + authentication via GHASH (polynomial evaluation over GF(2^128)).
Provides authenticated encryption with associated data (AEAD): confidentiality + integrity + authentication of both the ciphertext and additional unencrypted data (e.g., packet headers).
Output: (ciphertext, authentication tag). Verification fails if any bit is modified.
The standard choice for TLS 1.3, IPsec, and SSH. Hardware-accelerated via PCLMULQDQ (carry-less multiply).
Stream Ciphers
Generate a pseudorandom keystream from a key and nonce, then XOR with plaintext.
ChaCha20
Designed by Daniel Bernstein. 256-bit key, 96-bit nonce, 32-bit counter.
Core operation: Quarter-round on 4 32-bit words using only add, XOR, and rotate (ARX design). 20 rounds (10 double-rounds). State is a 4x4 matrix of 32-bit words.
a += b; d ^= a; d <<<= 16;
c += d; b ^= c; b <<<= 12;
a += b; d ^= a; d <<<= 8;
c += d; b ^= c; b <<<= 7;
ChaCha20-Poly1305: AEAD construction pairing ChaCha20 with the Poly1305 MAC. Used in TLS 1.3, WireGuard, SSH, and noise protocol. Preferred over AES-GCM on platforms without AES-NI hardware.
Key Derivation Functions
Derive cryptographic keys from passwords or other low-entropy inputs. Designed to be computationally expensive to resist brute-force attacks.
PBKDF2
Applies a PRF (typically HMAC-SHA256) iteratively:
DK = PBKDF2(password, salt, iterations, key_length)
DK = T1 || T2 || ... || Tn
T_i = U_1 XOR U_2 XOR ... XOR U_c
U_1 = PRF(password, salt || INT(i))
U_j = PRF(password, U_{j-1})
NIST recommended. Minimum 600,000 iterations (2024 OWASP guidance). Weakness: can be accelerated by GPUs/ASICs since HMAC-SHA256 is GPU-friendly.
bcrypt
Based on the Blowfish cipher. Cost factor 2^cost rounds (typical: cost=12, so 4096 rounds of key setup). Output includes the algorithm identifier, cost, salt, and hash.
Memory-hard: No. But its Blowfish key schedule is difficult to parallelize on GPUs. Maximum password length: 72 bytes.
scrypt
Designed to be memory-hard: requires large amounts of memory proportional to computation, making GPU/ASIC attacks expensive.
Parameters: N (CPU/memory cost), r (block size), p (parallelism).
Argon2
Winner of the Password Hashing Competition (2015). Recommended for new systems.
Three variants:
- Argon2d: Data-dependent memory access. Resistant to GPU attacks. Vulnerable to side-channel attacks.
- Argon2i: Data-independent memory access. Side-channel resistant. For password hashing.
- Argon2id: Hybrid (Argon2i for first pass, Argon2d for subsequent). Recommended default.
// Hash a password
salt ← GENERATE_RANDOM_SALT()
hash ← ARGON2ID_HASH(password, salt)
hash_string ← hash AS string
// Verify a password
parsed ← PARSE_HASH(hash_string)
ASSERT(ARGON2ID_VERIFY(password, parsed) IS success)
Parameters (OWASP 2024): Argon2id, minimum 19 MiB memory, iteration count 2, parallelism 1.
Practical Considerations
- Key management is harder than algorithm selection. Use established KMS (AWS KMS, HashiCorp Vault).
- Never reuse a nonce with the same key in CTR/GCM/ChaCha20 -- this completely breaks confidentiality.
- Prefer AEAD (AES-GCM, ChaCha20-Poly1305) over unauthenticated modes. Encrypt-then-MAC if AEAD is unavailable.
- AES-256 vs AES-128: AES-128 is sufficient against classical computers. AES-256 provides a margin against quantum computers (Grover's algorithm halves the effective key length).