English · Deutsch

FERRUM-10

A hands-on lesson in stream ciphers — from paper to post-quantum.

This is a four-stage lesson meant to be worked through with a pencil, a sheet of paper, and one other person. By the end, you will have written a cipher by hand, broken the same cipher by hand, and understood why the crypto that actually protects real correspondence looks nothing like what you wrote. That last understanding is the point of the whole exercise.

Lesson 1Encrypt

In 1553, an Italian mathematician named Giovan Battista Bellaso published a cipher. A little over a decade later a French diplomat named Blaise de Vigenère published a similar one, and for historical reasons — not because Vigenère invented it, but because nobody reads Italian in English crypto class — his name stuck. The Vigenère cipher held a reputation for three hundred years as le chiffre indéchiffrable: the undecipherable cipher.

The cipher is simple enough to explain in one paragraph. You pick a keyword, say BUFFALO. You line the keyword up under your plaintext, repeating it as many times as you need:

plain: MEETMEATTHEUSUALPLACE key: BUFFALOBUFFALOBUFFALO

For each column, you convert both letters to numbers (A=0, B=1, …, Z=25), add them, take the result mod 26 (subtract 26 if it's too big), and convert back to a letter. That's the ciphertext for that column. The keyword walks along the plaintext, so each repeated plaintext letter gets encrypted differently depending on which letter of the key it happened to line up with. The E in MEET and the E in THE will almost certainly come out as different ciphertext letters.

That "almost certainly" is the source of Vigenère's three-century reputation. It's also the source of its three-century vulnerability, but we'll get to that in Lesson 2. For now, try it.

Vigenère encrypt / decrypt

press Encrypt to see the result
no worksheet yet

What to notice

Lesson 2Break

For the first 300 years, Vigenère was considered unbreakable because cryptographers of the day only knew one real attack on substitution ciphers: frequency analysis. In English, the letter E is by far the most common (around 12.7% of all letters), followed by T, A, O, I, N. If you replace each letter with a different letter but keep the substitution fixed, the E's just become some-other-fixed-letter, and counting the most common letter in the ciphertext tells you what it maps to. Half an hour later you've broken the whole thing.

Vigenère defeats this by using a different substitution for every column — there's no single letter that stands in for E. So the ciphertext comes out looking roughly flat: every letter appears about the same number of times, and the usual frequency attack finds nothing to bite on.

That defense held until 1863, when a retired Prussian cavalry officer named Friedrich Kasiski published a short pamphlet explaining how to break it. (A British mathematician named Charles Babbage had apparently worked it out around 1854 but kept it to himself.) Kasiski's observation has a kindergarten-level elegance. Here it is:

That's it. That's the whole attack on the first half of the problem. Once you know the key length, say k, you break the ciphertext into k interleaved streams — letters 0, k, 2k, 3k, … form stream 0; letters 1, k+1, 2k+1, … form stream 1; and so on. Each of those streams is a plain Caesar shift by a single unknown letter, and you crack each stream with ordinary frequency analysis. Apply the shift you found to each stream, reassemble, and you have the plaintext.

The index of coincidence

Kasiski's method needs repeated n-grams to exist in the ciphertext, which they usually do in natural-language plaintext — but not always. In 1922 an American cryptographer named William F. Friedman published a statistical tool that finds the key length without needing any repeats at all. It's called the index of coincidence.

The idea: pick two letters at random from a text. What's the probability they're the same letter? For random noise in a 26-letter alphabet, the answer is 1/26, or about 0.0385. For English prose, the answer is about 0.0667, because English is lumpy — E is much more common than Z and so a random pair is more likely to pick two common letters than chance suggests.

Now here's the trick. Split a Vigenère ciphertext into k interleaved streams as described above. If you guessed k right, each stream is a pure Caesar shift of English, and its index of coincidence will be close to 0.067 — English-like. If you guessed k wrong, each stream is still a mess of multiple substitutions and its IC will be down near 0.038 — random-like. Compute the average IC across streams for k = 1, 2, 3, …, 20 and look for the first value of k where the number jumps toward 0.067. That's your key length.

Try it on the ciphertext from Lesson 1. Encrypt a longer message (at least 100 letters — the longer the better), paste it in, and run each of the three tools in order. They all agree in the end.

Cryptanalysis workbench

nothing yet
nothing yet
or force key length:
nothing yet

What to notice

Lesson 3Structure is leakage

The lesson isn't "Vigenère is weak." That's a fact, but it's not the point. The point is the reason it's weak, because that reason generalizes to every other cipher ever designed, and recognizing it is the instinct that separates people who can reason about security from people who can't.

Here's the pattern. Vigenère tried to hide a plaintext by applying a key on top of it. But the key repeated. Repetition is structure. Once you know how to look for the structure — n-gram repeats, coincidence statistics — the key length falls out, and after that the cipher collapses into a bundle of easier problems (Caesar shifts) that were already solved.

The cipher leaked its own key length through its ciphertext. Not directly, not as a number anyone wrote down — but as a measurable statistical property that you can detect with arithmetic available to a motivated teenager.

Vigenère inherits:

Modern ciphers — AES, ChaCha20, the post-quantum pieces in FERRUM-10's back half — are designed around a single principle that Vigenère doesn't satisfy: the ciphertext should be indistinguishable from random to any computationally bounded attacker. If an attacker can tell the ciphertext apart from an equal-length string of coin flips, the cipher leaks, and a leak is all you need to start pulling threads. Modern ciphers go to extraordinary lengths to not leak: they use key streams that are themselves indistinguishable from random, they destroy alignment so repeated plaintext never produces repeated ciphertext, and they attach a message authentication code so that the attacker can't even find out whether a tampered ciphertext decrypts to something sensible, because tampered ciphertext is simply rejected before it's decrypted.

Vigenère fails all three of those tests. It leaks key length; its columns leak language structure; and it has no authentication at all, which means if an attacker flips bits in the ciphertext the recipient just gets slightly-modified gibberish and has no way to tell whether that gibberish is the result of a transmission error or a deliberate attack.

Fixing all three of those is roughly the entire job of 20th-century cryptography. The result is what you'll see in Lesson 4.

Lesson 4The real thing

The cipher tool at ferrum.kmsp42.com/app (password-gated; ask the operator) uses three pieces that, between them, address every weakness you just exploited in Lessons 1 and 2. None of them are hand-workable. You wouldn't do them with pencil and paper because you can't — the numbers involved are too large and the structure is deliberately too uniform. But you should understand what each piece is for, because each one exists as a direct response to a historical failure.

ML-KEM-768 — key exchange without a shared secret

Vigenère assumes you and the recipient already share a secret key. In real life that's the hard part: how do you agree on a secret key with someone you've never met, over a channel an attacker is listening to? The answer is a key encapsulation mechanism. You generate a pair of mathematically-linked keys: one public, one secret. You publish the public key; you keep the secret one. Anyone who wants to send you a message uses your public key to "encapsulate" a fresh random symmetric key — the output is a ciphertext that only your secret key can turn back into that random key. Attacker on the wire sees only the public key and the ciphertext, and neither gives them the shared secret.

ML-KEM-768 is the specific KEM that NIST standardized in 2024 as FIPS 203. It's built on a mathematical problem called Module Learning With Errors, which is believed to be hard even for attackers with a large quantum computer — a property that matters because quantum computers would break the previous generation of KEMs (RSA, Diffie-Hellman) in polynomial time. ML-KEM-768's public keys are about 1,184 bytes; its ciphertexts are about 1,088 bytes; the shared secret it produces is 32 bytes of high-entropy randomness.

HKDF-SHA-512 — turning a shared secret into real keys

Once you have those 32 bytes of shared secret, you can't just use them directly. You need a symmetric encryption key, a MAC key, maybe a nonce seed — multiple distinct keys that must all be derived from the single shared secret without any of them leaking information about any of the others. That's what a key derivation function is for. HKDF (HMAC-based Key Derivation Function) does this in two stages: extract condenses the input into a uniform pseudorandom block, and expand stretches that block into as many named subkeys as you need. The "SHA-512" part names the underlying hash function.

HKDF is boring in the best possible way. It's the piece that makes every other piece of the pipeline work without having to think about how.

ML-DSA-65 — signing messages so recipients can verify them

Encryption alone doesn't tell the recipient that you sent the message. It just tells them that whoever sent it had access to the key. If you want the recipient to know the message is really from you and wasn't altered in flight, you also need a digital signature. You sign with a private key that only you hold; the recipient verifies with your public key. Any alteration to the message breaks the signature, so tampering is detectable.

ML-DSA-65 is NIST's 2024 post-quantum signature standard (FIPS 204), based on the same family of lattice problems as ML-KEM. Verifying keys are about 1,952 bytes; signatures are about 3,309 bytes; the signing key is never transmitted anywhere. In FERRUM-10 it's used to sign the encrypted messages so that a recipient can confirm both "this came from the person who owns this public key" and "nobody altered it."

Why this is the graduation

Every one of these pieces is a direct response to a lesson that cryptographers learned, painfully, from a previous cipher that looked unbreakable until it wasn't. ML-KEM replaces shared-secret assumptions because every real protocol that assumed a pre-shared key eventually failed at the key distribution step. HKDF replaces ad-hoc key handling because every cipher that tried to be clever about "just use the password as a key" eventually got broken through the key handling. ML-DSA replaces "I recognized the writing style so it must be real" because every protocol that didn't authenticate eventually had messages forged.

You broke Vigenère with Kasiski examination in this lesson. Modern crypto exists because people broke other things in other lessons, and the patch they wrote for each break became a piece of what a modern cipher has to do. You can't design a cipher today without understanding that history, and you can't understand that history by being told about it — you have to break something yourself first, which is what you just did.