The fundamental problem
Here's a scenario that's been a problem for millennia: you want to send a secret message to someone, but anyone might be listening. How do you communicate securely? Julius Caesar used a simple letter-shifting cipher. Medieval diplomats used codebooks. During WWII, the Enigma machine encrypted Nazi communications. But modern cryptography solves it with mathematical elegance that would have seemed like magic to all of them.
Modern cryptography rests on a beautiful asymmetry: there are mathematical operations that are easy to perform in one direction but essentially impossible to reverse without special knowledge. Multiplying two large prime numbers is easy - any computer can do it instantly. Factoring the result back into those primes? With numbers large enough (2048 bits), it would take longer than the age of the universe with all the computers on Earth working together.
This is the foundation: we don't need to make decryption impossible, just computationally infeasible. If breaking your encryption would take 10^30 years, it's effectively unbreakable even though it's theoretically possible. Security is measured not in absolutes but in computational hardness.
Encryption in Action
Symmetric encryption: the shared secret
The simplest form of encryption uses a shared secret key. Both parties have the same key, which both encrypts and decrypts the message. This is [[symmetric encryption]], and AES (Advanced Encryption Standard) is the gold standard today, selected by NIST in 2001 after a five-year public competition.
AES operates on 128-bit blocks, scrambling them through multiple rounds of operations: SubBytes (substitution using a carefully designed S-box), ShiftRows (shifting row positions), MixColumns (mathematical mixing within columns), and AddRoundKey (XORing with key material). Each operation serves a specific cryptographic purpose - confusion (making the relationship between key and ciphertext complex) and diffusion (spreading plaintext statistics throughout the ciphertext).
// AES-256-GCM encryption in Node.js
const crypto = require('crypto')
function encrypt(plaintext, key) {
// IV must be unique for every encryption with the same key!
const iv = crypto.randomBytes(12) // 96 bits for GCM
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
let encrypted = cipher.update(plaintext, 'utf8', 'hex')
encrypted += cipher.final('hex')
// GCM provides authentication - tamper detection
const authTag = cipher.getAuthTag()
return {
iv: iv.toString('hex'),
encrypted,
authTag: authTag.toString('hex')
}
}
function decrypt(encrypted, key, iv, authTag) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(iv, 'hex')
)
decipher.setAuthTag(Buffer.from(authTag, 'hex'))
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8') // Throws if tampered!
return decrypted
}
// Key must be exactly 32 bytes for AES-256
const key = crypto.randomBytes(32)The catch with symmetric encryption? Both parties need to have the same key. How do you share a secret key securely in the first place? If you could securely transmit the key, you could just securely transmit the message. This is the key distribution problem, and it plagued cryptography for literally thousands of years.
Diffie-Hellman: the key exchange revolution
In 1976, Whitfield Diffie and Martin Hellman solved the key distribution problem with an idea that seems impossible: two people can establish a shared secret while communicating entirely over a public channel, even if an eavesdropper hears every single message they exchange.
The magic relies on modular arithmetic - the math of clocks and remainders. If I tell you "23 mod 12", you know the answer is 11 (23 ÷ 12 = 1 remainder 11). But if I just tell you "11 mod 12", you can't know if I started with 11, 23, 35, or any other number. This one-wayness is the foundation.
Diffie-Hellman Key Exchange (simplified):
1. Alice and Bob publicly agree on:
- A large prime p (like a 2048-bit prime)
- A generator g (typically 2 or 5)
2. Alice picks a secret number 'a', computes A = g^a mod p
Bob picks a secret number 'b', computes B = g^b mod p
3. They exchange A and B publicly (eavesdropper sees both)
4. Alice computes: B^a mod p = (g^b)^a mod p = g^(ab) mod p
Bob computes: A^b mod p = (g^a)^b mod p = g^(ab) mod p
5. Both now have the same shared secret: g^(ab) mod p!
Eve the eavesdropper knows g, p, A, and B, but cannot
compute g^(ab) without knowing either a or b.
This is the Discrete Logarithm Problem - believed to be hard!Modern TLS uses Elliptic Curve Diffie-Hellman (ECDH), which achieves the same result using elliptic curve mathematics instead of modular exponentiation. ECDH provides equivalent security with much smaller keys - a 256-bit elliptic curve key provides security comparable to a 3072-bit traditional DH key.
RSA: asymmetric encryption
A year after Diffie-Hellman, Rivest, Shamir, and Adleman invented RSA - a system where encryption and decryption use different keys. You can publish one key openly (the public key) and anyone can encrypt a message to you. But only your private key can decrypt it. This is [[asymmetric encryption]].
RSA's security relies on [[prime factorization]]. You pick two large prime numbers p and q, multiply them to get n, then do some modular arithmetic to create a public key (n, e) and private key (n, d). The security relies on the fact that factoring n back into p and q is computationally infeasible for large primes.
RSA Key Generation (with small numbers for illustration):
1. Choose two primes: p = 61, q = 53
2. Compute n = p × q = 3233
3. Compute λ(n) = lcm(p-1, q-1) = lcm(60, 52) = 780
4. Choose e where 1 < e < λ(n) and gcd(e, λ(n)) = 1
Common choice: e = 65537 (but let's use e = 17)
5. Find d where (d × e) mod λ(n) = 1
d = 413 (since 413 × 17 = 7021 = 9×780 + 1)
Public key: (n=3233, e=17) ← Share with everyone!
Private key: (n=3233, d=413) ← Keep absolutely secret!
Encryption: ciphertext = message^e mod n
c = m^17 mod 3233
Decryption: message = ciphertext^d mod n
m = c^413 mod 3233
Real RSA uses 2048+ bit primes (617+ digits each)
Factoring a 2048-bit n is essentially impossible today.Hash functions: one-way fingerprints
A [[hash function]] takes any input - a password, a file, an entire database - and produces a fixed-size output (the hash or digest). The key properties: it's deterministic (same input always gives same output), it's fast to compute, but it's practically impossible to reverse or to find two different inputs with the same hash (collision resistance).
const crypto = require('crypto')
// SHA-256 always produces 64 hex characters (256 bits)
function hash(input) {
return crypto.createHash('sha256').update(input).digest('hex')
}
console.log(hash("Hello"))
// → 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
console.log(hash("Hello!")) // Just one character different!
// → 334d016f755cd6dc58c53a86e183882f8ec14f52fb05345887c8a5edd42c87b7
// Completely different output! This is the "avalanche effect"
// Changing one bit of input changes ~50% of output bits
console.log(hash("a".repeat(1000000))) // 1 million 'a's
// → Still exactly 64 hex characters, computed in millisecondsHashes are used everywhere: password storage (hash the password, store the hash - even if the database is stolen, the passwords aren't revealed), file integrity verification (download a file, check its hash matches the published hash), digital signatures, blockchain proof-of-work, Git commit IDs, and more.
SHA-256 is the workhorse hash function today, producing 256-bit (32-byte) outputs. SHA-3 is a newer alternative based on completely different mathematics (the Keccak sponge construction). BLAKE3 is the fastest modern hash, designed for parallelism. For passwords specifically, use bcrypt, scrypt, or Argon2 - they're deliberately slow to prevent brute-force attacks.
Digital signatures: proving authenticity
Asymmetric cryptography enables something remarkable: [[digital signature]]s. Instead of encrypting with the public key and decrypting with the private key, you reverse the operation - you sign a message hash with your private key, and anyone with your public key can verify that signature.
This is how HTTPS certificates work. When you connect to google.com, Google presents a certificate containing their public key, signed by a Certificate Authority (CA) like DigiCert. Your browser has DigiCert's public key pre-installed, so it can verify the signature, creating a chain of trust from a root CA down to the website. This proves you're really talking to Google, not an impostor.
Digital Signature Process:
Signing (done by sender):
1. Compute hash of message: h = SHA256(message)
2. Sign the hash with private key: signature = Sign(h, privateKey)
3. Send: message + signature
Verification (done by recipient):
1. Compute hash of received message: h' = SHA256(message)
2. Verify signature: Verify(h', signature, publicKey)
3. If verification passes, message is authentic and unmodified
The signature proves:
- Authentication: Only the private key holder could create it
- Integrity: The message hasn't been modified
- Non-repudiation: Signer cannot deny having signedThe TLS handshake: putting it together
When you visit an HTTPS website, all these cryptographic primitives work together in the TLS handshake. It's a beautiful choreography of asymmetric and symmetric encryption, digital signatures, and key exchange, establishing a secure channel in about 100 milliseconds.
TLS 1.3 Handshake (simplified):
Client → Server: ClientHello
- Supported TLS versions
- Supported cipher suites
- Random number
- Key share (ECDH public value)
Server → Client: ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished
- Chosen TLS version and cipher suite
- Server's key share (ECDH public value)
- Server's certificate (with public key, signed by CA)
- Signature proving server owns the private key
- Finished message (encrypted with derived keys)
Client → Server: Finished
- Confirmation that handshake succeeded
Both sides now have:
- Shared secret from ECDH key exchange
- Derived symmetric keys for encryption (AES-GCM typically)
- Verification that they're talking to the right party
All application data is now symmetrically encrypted.
The asymmetric crypto was only used for the handshake!Notice the elegant division of labor: asymmetric cryptography (slow, but enables key exchange and authentication) is used once at the start. Then symmetric cryptography (fast) is used for all the actual data. This hybrid approach gives us both security and performance.
The quantum threat
Quantum computers threaten current cryptography. Shor's algorithm can factor large numbers and solve discrete logarithms efficiently on a quantum computer, which would completely break RSA, Diffie-Hellman, and elliptic curve cryptography. Grover's algorithm weakens symmetric encryption by halving the effective key length (AES-256 becomes as strong as AES-128).
This is why there's active research into post-quantum cryptography - algorithms believed to be secure even against quantum attacks. NIST recently standardized several: CRYSTALS-Kyber for key exchange (based on lattice problems), CRYSTALS-Dilithium and FALCON for signatures (also lattice-based), and SPHINCS+ (hash-based signatures). Google Chrome is already experimenting with hybrid key exchange that uses both classical and post-quantum algorithms.