Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
Shannon's 1949 paper formalised what 'security' means for the first time. Before Shannon, breakers and designers traded heuristics. After Shannon, we had a definition: a cipher is perfectly secret if observing the ciphertext does not change the attacker's beliefs about the plaintext at all — Pr[P=p | C=c] = Pr[P=p] for every plaintext and ciphertext. He proved that perfect secrecy requires |K| ≥ |M|: you need at least as many keys as messages. This single inequality is the deepest fact in classical cryptography, because it says perfect secrecy will always be too expensive for general use, and therefore all practical cryptography must be content with computational security — schemes that are unbreakable in practice but not in principle. Every modern security definition (semantic security, IND-CPA, IND-CCA) is a computational relaxation of Shannon's perfect secrecy.
Shannon's definition: a cipher (Gen, Enc, Dec) over message space M and key space K is perfectly secret if for all m1, m2 in M and all ciphertexts c, Pr[Enc(K, m1) = c] = Pr[Enc(K, m2) = c]. Equivalently, the ciphertext distribution does not depend on the plaintext. Shannon's bound says this is achievable only when |K| ≥ |M|.
// main.go
package main
import (
"fmt"
"math/rand"
)
// Empirical demonstration: for a 2-bit OTP, the ciphertext distribution is
// uniform regardless of the plaintext.
func empiricalCiphertextDist(plaintextBits int, nBits int, trials int) map[int]float64 {
counts := make(map[int]int)
nSymbols := 1 << nBits
for i := 0; i < trials; i++ {
k := rand.Intn(nSymbols)
c := plaintextBits ^ k
counts[c]++
}
result := make(map[int]float64)
for c := 0; c < nSymbols; c++ {
result[c] = float64(counts[c]) / float64(trials)
}
return result
}
// Now contrast with a "shorter key" cipher where |K| < |M|.
// Key space {0, 1} (1 bit), message space {0,1,2,3} (2 bits).
// Encrypt by XORing the lowest bit.
func shortKeyCTDist(plaintextBits int, trials int) map[int]float64 {
counts := make(map[int]int)
for i := 0; i < trials; i++ {
k := rand.Intn(2)
c := plaintextBits ^ k
counts[c]++
}
result := make(map[int]float64)
for c := 0; c < 4; c++ {
result[c] = float64(counts[c]) / float64(trials)
}
return result
}
func main() {
fmt.Println("OTP, plaintext = 0b00:", empiricalCiphertextDist(0b00, 2, 100_000))
fmt.Println("OTP, plaintext = 0b11:", empiricalCiphertextDist(0b11, 2, 100_000))
fmt.Println("Short key, plaintext = 0b00:", shortKeyCTDist(0b00, 100_000))
fmt.Println("Short key, plaintext = 0b11:", shortKeyCTDist(0b11, 100_000))
// The short-key cipher leaks: ciphertext distribution depends on the plaintext.
}go run main.go