Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Vigenère was called 'le chiffre indéchiffrable' for nearly three centuries — from its publication in 1553 until Babbage and Kasiski independently broke it in the mid-1800s. The reason it survived so long is the reason it is worth studying now: it diffuses the single-letter frequency signal that destroyed the Caesar cipher by using a different shift for every position determined by a repeating key. This is the first real glimpse of two ideas that drive all modern symmetric cryptography: the idea that a key should be expanded into per-position keystream material, and the idea that period-based structure (here, the key length) is itself a form of leakage. When we get to stream ciphers like ChaCha20, you'll see the same architecture — generate a long pseudorandom keystream, XOR with plaintext — but with a key whose period is 2^256 instead of 6.
Vigenère encrypts the i-th plaintext letter by shifting it by the i-th letter of the key, where the key is repeated to match the plaintext length. With a key of length L, every L-th letter is encrypted with the same Caesar shift.
Use these three in order. Each builds on the one before.
In one paragraph, explain what the Vigenère cipher is and how repeating a key transforms a single Caesar cipher into something much harder to break by inspection.
Walk me through Vigenère encryption step by step using a concrete example like key 'LEMON' on plaintext 'ATTACKATDAWN', showing the shift applied at each position.
Given a Vigenère ciphertext where you suspect the key length is 6, explain how you would split the ciphertext into 6 separate streams and why each stream is then a Caesar cipher you can attack independently using single-letter frequency analysis.
// main.go
package main
import (
"fmt"
"strings"
)
func vigenereEncrypt(plaintext, key string) string {
pt := strings.ToUpper(strings.ReplaceAll(plaintext, " ", ""))
k := strings.ToUpper(key)
out := make([]byte, 0, len(pt))
for i, ch := range pt {
if ch >= 'A' && ch <= 'Z' {
shift := k[i%len(k)] - 'A'
out = append(out, byte((int(ch-'A')+int(shift))%26)+'A')
}
}
return string(out)
}
func vigenereDecrypt(ciphertext, key string) string {
ct := strings.ToUpper(ciphertext)
k := strings.ToUpper(key)
out := make([]byte, 0, len(ct))
for i, ch := range ct {
if ch >= 'A' && ch <= 'Z' {
shift := int(k[i%len(k)] - 'A')
out = append(out, byte((int(ch-'A')-shift+26)%26)+'A')
}
}
return string(out)
}
func main() {
ct := vigenereEncrypt("ATTACKATDAWN", "LEMON")
fmt.Println(ct) // LXFOPVEFRNHR
fmt.Println(vigenereDecrypt(ct, "LEMON")) // ATTACKATDAWN
}go run main.go