X Xerobit

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...

Mian Ali Khalid · · 4 min read
Use the tool
ROT13 / Caesar Cipher
Encode and decode ROT13 and arbitrary Caesar shifts. Letter frequency analysis. 100% client-side.
Open ROT13 / Caesar Cipher →

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 = C and C 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:

  1. Truly random (not pseudorandom)
  2. At least as long as the message
  3. 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 posts

Related tool

ROT13 / Caesar Cipher

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.