XOR Cipher Explained — One-Time Pads, Weak Encryption, and Bitwise XOR
XOR cipher is the basis of stream ciphers and one-time pads. Learn how XOR encryption works, why it's insecure without a truly random key, the Vigenère cipher relationship, and...
XOR cipher encrypts each byte of plaintext by XOR-ing it with a corresponding byte of the key. It’s theoretically perfect (one-time pad) but practically insecure when the key is reused.
Encode text with simple ciphers using the ROT13 Cipher.
How XOR works
Plaintext: H e l l o (ASCII: 72 101 108 108 111)
Key: K E Y K E (ASCII: 75 69 89 75 69)
↓ ↓ ↓ ↓ ↓ XOR each byte
Ciphertext: 3 . 5 7 & (ASCII: 3 40 53 39 42)
XOR properties that make it useful for cryptography:
A XOR B = CandC XOR B = A(symmetric — same operation to encrypt and decrypt)A XOR A = 0(same key cancels out)A XOR 0 = A(XOR with zero returns original)
XOR cipher in Python
def xor_cipher(text: bytes, key: bytes) -> bytes:
"""XOR each byte of text with the repeating key."""
return bytes(t ^ key[i % len(key)] for i, t in enumerate(text))
# Encrypt:
key = b'SECRET'
plaintext = b'Hello, World!'
ciphertext = xor_cipher(plaintext, key)
print(ciphertext.hex()) # "1b000a00131d4b451c0a131a16"
# Decrypt (same operation):
decrypted = xor_cipher(ciphertext, key)
print(decrypted) # b'Hello, World!'
XOR cipher in JavaScript
function xorCipher(text, key) {
const textBytes = new TextEncoder().encode(text);
const keyBytes = new TextEncoder().encode(key);
const result = new Uint8Array(textBytes.length);
for (let i = 0; i < textBytes.length; i++) {
result[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
}
return result;
}
// Convert to hex:
const cipher = xorCipher('Hello, World!', 'SECRET');
const hex = Array.from(cipher).map(b => b.toString(16).padStart(2, '0')).join('');
Why XOR cipher is insecure (when key is reused)
The critical weakness: if you use the same key twice, an attacker can XOR the two ciphertexts together to eliminate the key:
C1 = P1 XOR K
C2 = P2 XOR K
C1 XOR C2 = P1 XOR K XOR P2 XOR K
= P1 XOR P2 (K cancels out!)
An attacker who knows C1 XOR C2 can use frequency analysis on natural language patterns to recover P1 and P2. This is the “two-time pad” attack.
The 1941 Lorenz cipher attack (Colossus): German operators sent two messages with the same key settings (“depths”), giving British cryptanalysts C1 XOR C2 = P1 XOR P2, enabling Alan Turing’s team to crack the cipher.
The one-time pad: theoretically perfect
If the key is:
- Truly random (not pseudorandom)
- At least as long as the message
- Never reused
Then XOR encryption is information-theoretically secure — unbreakable even with unlimited computing power. This is the one-time pad (OTP), patented by Vernam in 1919.
import os
def one_time_pad_encrypt(plaintext: bytes) -> tuple[bytes, bytes]:
"""One-time pad: key is truly random and as long as plaintext."""
key = os.urandom(len(plaintext)) # Cryptographically random
ciphertext = bytes(p ^ k for p, k in zip(plaintext, key))
return ciphertext, key
def one_time_pad_decrypt(ciphertext: bytes, key: bytes) -> bytes:
return bytes(c ^ k for c, k in zip(ciphertext, key))
# Perfect secrecy — but the key must be transmitted securely and never reused!
The practical problem: you need a secure channel to transmit the key. If you have a secure channel, why not transmit the message that way?
XOR in modern cryptography
XOR remains fundamental to modern symmetric encryption:
- AES (Advanced Encryption Standard): Uses XOR extensively in its SubBytes, ShiftRows, MixColumns, and AddRoundKey operations
- Stream ciphers (ChaCha20, RC4): Generate a pseudorandom keystream and XOR it with plaintext
- Block cipher modes (CTR mode): Convert block ciphers into stream ciphers using XOR
- Hash functions: SHA-256 uses XOR in its compression function
# ChaCha20 (modern secure stream cipher using XOR internally):
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
key = os.urandom(32) # 256-bit key
nonce = os.urandom(16)
cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None)
encryptor = cipher.encryptor()
ciphertext = encryptor.update(b'Hello, World!')
Related tools
- ROT13 Cipher — simple letter substitution cipher
- Hash Generator — generate cryptographic hashes
- Base64 — encode binary cipher output as text
Related posts
- Caesar Cipher and ROT13 — How Shift Ciphers Work — The Caesar cipher shifts each letter by a fixed number. ROT13 shifts by 13. Neit…
- Caesar Cipher Explained — ROT13, ROT47, and Shift Ciphers — The Caesar cipher shifts letters by a fixed number of positions. ROT13 is a Caes…
- ROT13 Uses — When and Why ROT13 Encoding Is Used — ROT13 rotates each letter by 13 positions in the alphabet. It's not encryption b…
- ROT13 Decoder — How to Decode ROT13 Encoded Text — ROT13 is a simple letter-substitution cipher that shifts each letter 13 position…
Related tool
Encode and decode ROT13 and arbitrary Caesar shifts. Letter frequency analysis. 100% client-side.
Written by Mian Ali Khalid. Part of the Encoding & Crypto pillar.