Cryptography basics
The vocabulary, and the one bit-flipping trick that almost every cipher is built on
What encryption actually is
Encryption is a reversible way of scrambling data so that only someone holding a secret can unscramble it. That's the whole idea - everything else is detail. A few words come up constantly, so it's worth pinning them down before anything else:
- Plaintext - the original, readable data. "hello" is plaintext.
- Key - the secret that controls the scrambling. Without it, the process can't be reversed.
- Ciphertext - the scrambled output. It should look like random noise to anyone without the key.
- Cipher - the algorithm that does the scrambling and unscrambling.
plaintext + key → encrypt → ciphertext ciphertext + key → decrypt → plaintext
That's it - two operations, one secret, both directions. Everything in symmetric and asymmetric encryption is a variation on this diagram.
Term - Kerckhoffs's principle
A cipher should stay secure even if an attacker knows exactly how it works - the source code, the algorithm, everything except the key. This is why "we invented our own secret encryption algorithm" is a red flag rather than a selling point: a cipher that only stays safe because nobody has read the code isn't secure, it's unaudited. AES is public. Anyone can read the spec. Its security comes entirely from the key.
XOR - the operation almost all encryption leans on
Most ciphers, at some point deep inside them, come down to XOR (exclusive or) - a bitwise operation that compares two bits and outputs 1 if they're different, 0 if they're the same:
0 XOR 0 = 0 0 XOR 1 = 1 1 XOR 0 = 1 1 XOR 1 = 0
The property that makes XOR useful for encryption: XOR-ing something twice with the same value gets you back where you started. If A XOR key = B, then B XOR key = A. Encryption and decryption become the exact same operation, run twice.
Take the letter H, which is the byte 01001000. XOR it against a key byte, bit by bit:
01001000 'H' as bits
XOR 00101010 key byte
--------
01100010 ciphertext ('b')
01100010 ciphertext
XOR 00101010 same key byte
--------
01001000 back to 'H'Same key, same operation, both directions. In Python:
plaintext = ord("H") # 72
key = 0b00101010 # 42, the "secret"
ciphertext = plaintext ^ key
print(ciphertext) # 98 ('b')
recovered = ciphertext ^ key
print(chr(recovered)) # 'H' - XOR-ing with the key again undoes itBuilding a (bad) cipher out of XOR
Stretch that single-byte trick across a whole message by repeating the key to match its length, XOR-ing byte by byte. This is enough to build a working - if weak - cipher:
def xor_cipher(data: bytes, key: bytes) -> bytes:
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
plaintext = b"HELLO WORLD"
key = b"KEY"
ciphertext = xor_cipher(plaintext, key)
print(ciphertext) # b'\x03\x00\x1f...' - looks like noise
decrypted = xor_cipher(ciphertext, key)
print(decrypted.decode()) # 'HELLO WORLD'
# notice: encrypting and decrypting call the *same function*That's a real cipher - it's about 500 years old, it's called a Vigenère cipher when done on letters instead of bytes, and it is not remotely safe to use. Here's why.
Say the same key ever gets reused for two different messages - which is easy to do by accident if the "key" is really a keystream generated the same way each time. An attacker who has both ciphertexts, but not the key, can XOR the ciphertexts together:
msg1 = b"ATTACKATDAWN" msg2 = b"MEETMEATNOON" key = b"SECRETSECRET"[:len(msg1)] c1 = xor_cipher(msg1, key) c2 = xor_cipher(msg2, key) mystery = xor_cipher(c1, c2) # c1 XOR c2, no key involved at all # = (msg1 XOR key) XOR (msg2 XOR key) # = msg1 XOR msg2 ← the key cancels itself out completely
The key vanishes from the equation entirely. What's left, msg1 XOR msg2, leaks the relationship between two supposedly secret messages - and with a bit of English letter-frequency guessing, both messages are usually fully recoverable without the key ever being known. This exact mistake - reusing key material - has broken real cryptographic systems, including Soviet one-time pads decoded by the Venona project and early WEP Wi-Fi encryption.
What real ciphers do differently
The lesson isn't "avoid XOR" - AES uses XOR internally too. The lesson is that raw XOR against a short, repeated, predictable key is not enough:
- The key needs to produce something that looks completely random, not a short repeating pattern - AES does this by running each block through many rounds of substitution and mixing, keyed by the secret, rather than a simple repeated XOR.
- The same key material must never produce the same output twice. Real ciphers achieve this with a nonce (or IV) - a value that changes every time you encrypt, even with the same key, so the effective keystream is never repeated.
Both of these - AES itself, and why nonces matter so much - are covered next.
Glossary
- Plaintext - the original, readable data.
- Ciphertext - the encrypted, unreadable output.
- Key - the secret input that controls encryption and decryption.
- Cipher - the algorithm doing the encrypting and decrypting.
- Keystream - a stream of pseudo-random bytes derived from the key, XORed against the data. Regenerating this unpredictably every time is most of what makes a cipher strong.
- Nonce / IV - "number used once" / initialisation vector. A value that changes on every encryption so the same key never produces the same keystream twice. Doesn't need to be secret, just unique.
- Block cipher - encrypts data in fixed-size chunks (AES uses 128-bit blocks).
- Stream cipher - encrypts data one byte (or bit) at a time against a generated keystream, like the toy example above.