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.
The Caesar cipher is the oldest encryption scheme we still teach, and it earns that spot not because it works but because of how spectacularly it fails. A shift cipher has exactly 25 non-trivial keys, so an attacker can try them all by hand in under a minute. The lesson modern cryptography takes from this is Kerckhoffs's principle: security must rest on the secrecy of the key, not the algorithm, and the key space must be so large that brute force is infeasible. Generalising Caesar to an arbitrary substitution gives a 26! key space (about 4 x 10^26), which seems strong until you realise that the cipher leaks the structure of the language — a lesson that returns in every cipher we will study. Understanding why both small key spaces and structure-preserving ciphers fail is the foundation for understanding what AES, RSA, and elliptic curves are designed to defend against.
A Caesar shift maps each letter to the letter k positions later in the alphabet, modulo 26. The encryption and decryption are inverse functions of the same family.
Use these three in order. Each builds on the one before.
In one paragraph, explain what a Caesar cipher is and why shifting letters by a fixed amount is the simplest possible encryption scheme.
Walk me through how Caesar encryption and decryption work step by step using modular arithmetic, and then explain how a general substitution cipher generalises it to any permutation of the alphabet.
Given that the substitution cipher has a key space of 26! ≈ 4 × 10^26 — larger than DES's 2^56 key space — why is it considered insecure while DES at least required dedicated hardware to break? What property of the cipher makes the apparent key-space size misleading?
// main.go
package main
import (
"fmt"
"strings"
"unicode"
)
func caesarEncrypt(plaintext string, k int) string {
var out strings.Builder
for _, ch := range strings.ToUpper(plaintext) {
if unicode.IsLetter(ch) {
shifted := rune((int(ch-'A')+k%26+26)%26) + 'A'
out.WriteRune(shifted)
} else {
out.WriteRune(ch)
}
}
return out.String()
}
func caesarDecrypt(ciphertext string, k int) string {
return caesarEncrypt(ciphertext, -k)
}
func caesarBruteForce(ciphertext string) [][2]string {
results := make([][2]string, 26)
for k := 0; k < 26; k++ {
results[k] = [2]string{fmt.Sprintf("%d", k), caesarDecrypt(ciphertext, k)}
}
return results
}
func main() {
ct := caesarEncrypt("ATTACK AT DAWN", 3)
fmt.Println(ct) // DWWDFN DW GDZQ
for k, pair := range caesarBruteForce(ct) {
fmt.Printf("k=%2d: %s\n", k, pair[1])
}
}
go run main.go