↑ ↓ to move · enter to open

C04 · Computers · hashing · number theory · security

encryption & hashing

Each time a page loads over HTTPS, your browser checks fingerprints of data, agrees on a secret key with a server while anyone can listen, and verifies a signature. All three rest on math you can run by hand: hash functions that scramble, modular powers that are easy to compute and hard to undo, and primes that are easy to multiply and hard to pull apart. Small numbers here make the math visible; each lab says what real systems use instead.

LAB 1

locks you can't run backwards

A hash function turns any input, one letter or a whole film, into a fixed-size fingerprint. SHA-256 always returns 256 bits. Flip a single bit of the input and about half of the output bits flip, in a pattern nobody can predict or steer. This page computes real SHA-256 in your browser, so every value below matches any other SHA-256 tool. (Bits, bytes and hex are covered in C00 · Bits & bytes.)

sha-256 · avalanche
UTF-8 bytes · the byte holding the flipped bit is amber
SHA-256(message) · lit = 1
one input bit flipped · amber outline = bit that changed
padding · how SHA-256 fills 64-byte blocks before mixing
message0x80 end markerzeroslength in bits (8 bytes)
0
Try
—
Output bits changed
—
Average over every 1-bit flip
—
Input size
—
Hex digits changed

What a good hash promises

  • Deterministic. The same bytes give the same hash on every machine, forever.
  • Fixed size. 256 bits whether the input is empty or 4 GB.
  • Preimage resistance. Given a hash, the best known way to find an input for it is guessing: about 2256 tries.
  • Collision resistance. Finding any two inputs with the same hash takes about 2128 tries. Lab 2 shows why it is the square root.
bits changed ~ Binomial(256, ½) mean 128 σ = √(256·½·½) = 8

Each output bit acts like a fair coin that is re-flipped whenever the input changes. So most single-bit flips change between 112 and 144 bits, within two standard deviations of 128.

The codePython 3

Hashing is not encryption

There is no key and no way back. That makes a hash useful as a fingerprint: download checksums, Git commit names, and digital signatures, which sign the hash of a document instead of the whole thing.

Websites store a hash of your password instead of the password. A plain SHA-256 is the wrong tool there, because a single graphics card can try billions of guesses per second. Real systems use a salted, deliberately slow function such as Argon2, scrypt or bcrypt.

LAB 2

the birthday attack

Put 23 people in a room and the chance that two share a birthday is just over 50%. The same math sets the price of a hash collision. Here SHA-256 is cut down to its first n bits, a toy hash weak enough to break in your browser. Each run hashes the messages r1:0, r1:1, r1:2, … until two of them share those n bits. With N possible values, that takes about √N tries, far fewer than N.

toy hash · first 20 bits of sha-256
20 bits

How to read this: A match means two of the hashed messages already share a fingerprint. The curve crosses 50% far sooner than you might expect.

  • theory: chance of a match by k tries
  • your runs: share finished by k
  • small ticks on the bottom: where each run found its match
  • run in progress
  • this run's match

Try this: Press Run 50 more and watch the green steps hug the gold curve.

—
Possible hash values N
—
Expected tries (exact)
—
Your runs: mean tries
—
This run

The collision

Why √N

P(no match in k tries) = ∏i<k (1 − i/N) ≈ e−k(k−1)/2N
mean tries ≈ √(πN/2) + 2/3 50% at k ≈ 1.177·√N

The k-th message has k − 1 earlier hashes to clash with, so there are about k²/2 pairs after k tries. A match becomes likely once k²/2 approaches N. The exact 50% point is k ≈ 1.177·√N: 23 for N = 365 birthdays, and 1,206 messages for a 20-bit hash.

The codePython 3

Full-size hashes

A truncated hash is a toy. Full SHA-256 has N = 2256, so a birthday attack needs about 2128 ≈ 3.4 × 1038 hashes. At 1018 hashes per second that is about 1013 years. That square root is why a hash needs twice as many bits as the security level it promises.

MD5 and SHA-1 were broken faster than the birthday bound by exploiting flaws in their internal structure: practical MD5 collisions were published in 2004 and the first SHA-1 collision in 2017. Use SHA-256, SHA-3 or BLAKE2/BLAKE3 instead.

LAB 3

agreeing on a secret in public

Alice and Bob have never met, and every message between them passes Eve, who reads everything. Diffie–Hellman key exchange, published by Whitfield Diffie and Martin Hellman in 1976, still lets them finish with the same secret number. The paint version shows the idea; the numbers underneath are the real arithmetic at toy sizes.

toy group · p = 23
Alice
public · Eve sees this
Bob
⇄

How to read this: Read the rows top to bottom as four steps. 1) Everyone sees the public yellow. 2) Each person picks a secret color. 3) They mix and swap pots in public. 4) Each adds their own secret to the pot they got, and both end with the same color. The paint colors stand for numbers.

  • green number: kept secret
  • amber: sent in public, Eve sees it
  • gold border: the shared secret
square-and-multiply ·
  • this step
  • final answer
  • faded: still to come
Group
—
Alice sends A = gᵃ mod p
—
Bob sends B = gᵇ mod p
—
Shared secret s
—
Order of g (values gˣ can take)

Why both sides match

A = gᵃ mod p B = gᵇ mod p
Bᵃ = (gᵇ)ᵃ = gᵃᵇ = (gᵃ)ᵇ = Aᵇ (mod p)

Paint works the same way. Mixing is easy and unmixing is hard. Each side stirs a secret color into the public yellow and sends the mix. Each then adds its own secret to the mix it received. Both pots end up holding yellow + Alice's color + Bob's color, added in a different order. Eve only ever sees the two half-mixed pots.

The codePython 3

Square-and-multiply reads the exponent one bit at a time, so a 2048-bit exponent costs about 2048 squarings plus at most as many multiplications, instead of 22048 multiplications.

Eve's problem: the discrete log

Eve knows p, g, A and B. To get s she needs a, which means solving gˣ ≡ A (mod p) for x. Going backwards like this is the discrete logarithm problem. Trying every x takes up to p − 1 steps. Try it.

What real systems use

These group sizes are toys. Smarter algorithms than Eve's (baby-step giant-step, index calculus) need far fewer steps, so real finite-field Diffie–Hellman uses a prime of at least 2048 bits. Most TLS 1.3 connections instead use elliptic-curve Diffie–Hellman with X25519, a 255-bit curve with about 128-bit security, and a fresh key pair for every connection.

Diffie–Hellman alone does not prove who is on the other end: a man in the middle could run one exchange with each side. TLS fixes that by having the server sign the exchange with its certificate key. The agreed secret then keys a fast cipher, AES-GCM or ChaCha20-Poly1305, which encrypts the actual traffic (see C03 · How the internet moves data). Because a large quantum computer running Shor's algorithm would solve discrete logs, many browsers now combine X25519 with ML-KEM, a post-quantum scheme NIST standardized in 2024.

LAB 4

RSA from scratch

RSA, described by Ron Rivest, Adi Shamir and Leonard Adleman in 1977, locks with one key and unlocks with another. Multiply two primes to get n. The public key is (n, e). The private key d can only be computed by someone who knows the primes. Anyone can encrypt with e, only the holder of d can decrypt, and running it the other way round makes a signature anyone can check. Euclid's algorithm, from 003 · Number theory, does the key step.

textbook rsa · toy · no padding
1 · keys
2 · extended Euclid on (φ(n), e) finds d · every row keeps r = s·φ(n) + t·e true
3 · encrypt and decrypt, one block at a time ·
4 · sign with d, verify with e
5 · Eve factors n by trial division
Primes

How to read this: The five boxes are the steps of RSA in order. An amber border marks the step being walked through. In the key chips, green is kept secret, amber is public, and gold is the private key.

Try this: Press Walk through and follow the amber box down the five steps. Then press Tamper with message to see the signature check fail.

—
Public modulus n
—
Private exponent d
—
e · d mod φ(n)
—
Eve's trial divisions

The recipe

n = p·q φ(n) = (p − 1)(q − 1) e·d ≡ 1 (mod φ(n))
encrypt c = mᵉ mod n decrypt m = cᵈ mod n

Why decryption undoes encryption: e·d = 1 + k·φ(n) for some whole number k, and Euler's theorem says mφ(n) ≡ 1 (mod n) when m shares no factor with n. So cd = med = m · (mφ(n))k ≡ m. A short argument with the Chinese remainder theorem covers the rare m that do share a factor. Many libraries use λ(n) = lcm(p − 1, q − 1) in place of φ(n), which gives a smaller d that works the same way.

The codePython 3

Why textbook RSA is a toy

It is deterministic. The same block always encrypts to the same number, so repeated letters show through, and anyone can test a guess by encrypting it with the public key.

It is malleable. Ciphertexts multiply: . An attacker can change a message without reading it.

These keys are tiny. Real RSA uses n of 2048 bits or more (NIST rates 2048-bit RSA at about 112-bit security), e = 65537, OAEP padding for encryption and PSS padding for signatures. The padding adds randomness and structure before the math runs. TLS 1.3 no longer uses RSA to exchange keys at all: it uses Diffie–Hellman from Lab 3, and RSA-PSS or elliptic-curve signatures (ECDSA, Ed25519) to prove who the server is.

Why key size wins

Trial division needs about √n / 2 divisions for two primes of similar size. Each extra bit of n multiplies the work by √2.

Trial division is the slowest method. The best known one, the general number field sieve, grows much more slowly, and in 2020 researchers used it to factor RSA-250, an 829-bit number, with about 2,700 CPU core-years of computing. A 2048-bit n remains far out of reach of every known classical method.