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 one-time pad is the only encryption scheme in this course that is provably unbreakable — Shannon proved in 1949 that with a uniformly random key as long as the message, used exactly once, ciphertext gives an attacker zero information about the plaintext. So why doesn't everyone just use it? Because the conditions are brutal: keys must be truly random, as long as the message, never reused, and securely distributed before communication — which is the same problem encryption was supposed to solve. The OTP is therefore the theoretical north star against which all practical ciphers are measured. AES with a 256-bit key approximates the OTP: it stretches a short key into a long pseudorandom keystream and XORs. The entire field of symmetric cryptography is the search for ciphers that are 'OTP-like enough' under realistic assumptions about computational hardness.
The OTP encrypts by XORing the plaintext with a key of equal length. Decryption applies the same XOR. Because XOR is its own inverse and a uniformly random key bit makes the ciphertext bit uniformly random, no information about the plaintext leaks.
Use these three in order. Each builds on the one before.
In one paragraph, explain what the one-time pad is and what the three rules — random key, key length equals message length, never reuse the key — are protecting against.
Walk me through why XORing a plaintext bit with a uniformly random key bit makes the ciphertext bit uniformly random regardless of the plaintext, and how that property gives the attacker zero information.
Given that the OTP is information-theoretically secure but practically unusable, how does AES in counter mode approximate the OTP, and what assumption replaces 'truly random key as long as the message' to make AES practical?
// main.go
package main
import (
"crypto/rand"
"fmt"
)
func otpEncrypt(plaintext, key []byte) []byte {
if len(key) != len(plaintext) {
panic("key must equal plaintext length")
}
out := make([]byte, len(plaintext))
for i := range plaintext {
out[i] = plaintext[i] ^ key[i]
}
return out
}
func otpDecrypt(ciphertext, key []byte) []byte {
return otpEncrypt(ciphertext, key) // XOR is its own inverse
}
func main() {
// Generate a fresh, truly random key
pt := []byte("ATTACK AT DAWN")
key := make([]byte, len(pt))
rand.Read(key)
ct := otpEncrypt(pt, key)
fmt.Printf("ciphertext: %x\n", ct)
fmt.Printf("recovered : %s\n", otpDecrypt(ct, key))
// DANGER: key reuse breaks security completely
pt1 := []byte("HELLO WORLD123")
pt2 := []byte("ATTACK AT DAWN")
ct1 := otpEncrypt(pt1, key)
ct2 := otpEncrypt(pt2, key)
// An attacker who sees both can compute pt1 XOR pt2 without the key:
xorOfPlaintexts := make([]byte, len(ct1))
for i := range ct1 {
xorOfPlaintexts[i] = ct1[i] ^ ct2[i]
}
fmt.Printf("pt1 XOR pt2 leaked: %x\n", xorOfPlaintexts)
}go run main.go