Post

Understanding XOR Encryption and Decryption

A practical and beginner-friendly guide to how XOR encryption works and why it's reversible by design.

Understanding XOR Encryption and Decryption

XOR encryption is one of the simplest forms of symmetric encryption. It’s so simple you can implement it in just a few lines of code, yet it teaches powerful concepts used in cryptography.

This post will break down what XOR is, why it works the way it does, and show Python examples you can run yourself.


🎯 What is XOR?

XOR stands for exclusive OR, a bitwise operation that:

  • Compares two bits.
  • Returns 1 if only one of them is 1.
  • Returns 0 if they are both the same.

Truth Table:

ABA XOR B
000
011
101
110

✨ Key Properties of XOR

XOR is interesting because it has a self-inverse property:

If you XOR something twice with the same key, you get the original back.

Mathematically:

1
(A XOR B) XOR B = A

This means encryption and decryption use the same operation.


🔐 XOR Encryption Explained

Imagine you have:

  • plaintext: your data
  • key: a secret byte

You encrypt:

1
ciphertext = plaintext XOR key

To decrypt:

1
plaintext = ciphertext XOR key

That’s it—no fancy division or special functions.


🧠 Why XOR is Not Like Multiplication

Some people think:

If XOR is like multiplication, then decryption must be division.

This is a misconception. XOR is more like flipping switches:

  • The first XOR flips some bits.
  • The second XOR flips them back.

✅ No division involved.


🧩 Example with a Single Byte

Let’s encrypt and decrypt 'A' (ASCII 65) using key 0x5A (90):

Encryption:

Decimal:

1
65 XOR 90 = 27

Binary:

1
2
3
4
5
01000001
XOR
01011010
=
00011011

Result: 27 (a non-printable character).

Decryption:

1
27 XOR 90 = 65

Binary:

1
2
3
4
5
00011011
XOR
01011010
=
01000001

Recovered: ASCII 65 = 'A'.


🐍 Python Code Example

Here’s a minimal Python script to try it yourself:

1
2
3
4
5
6
7
8
9
10
11
12
plaintext_byte = ord('A')   # 65
key_byte = 0x5A             # 90

# Encrypt
ciphertext_byte = plaintext_byte ^ key_byte

# Decrypt
decrypted_byte = ciphertext_byte ^ key_byte

print("Plaintext:", chr(plaintext_byte))
print("Ciphertext byte:", ciphertext_byte)
print("Decrypted:", chr(decrypted_byte))

Expected output:

1
2
3
Plaintext: A
Ciphertext byte: 27
Decrypted: A

🧵 XOR with a String

To encrypt an entire string with a (potentially longer) key, you can cycle through the key bytes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def xor_encrypt(plaintext, key):
    encrypted = []
    for i in range(len(plaintext)):
        encrypted_byte = ord(plaintext[i]) ^ ord(key[i % len(key)])
        encrypted.append(encrypted_byte)
    return bytes(encrypted)

def xor_decrypt(ciphertext, key):
    decrypted = []
    for i in range(len(ciphertext)):
        decrypted_byte = ciphertext[i] ^ ord(key[i % len(key)])
        decrypted.append(chr(decrypted_byte))
    return ''.join(decrypted)

# Example usage
plaintext = "HELLO"
key = "K"

ciphertext = xor_encrypt(plaintext, key)
print("Ciphertext bytes:", list(ciphertext))

decrypted = xor_decrypt(ciphertext, key)
print("Decrypted text:", decrypted)

Sample output:

1
2
Ciphertext bytes: [3, 14, 0, 7, 4]
Decrypted text: HELLO

  • Fast: XOR is a low-level bitwise operation.
  • Simple: The same function encrypts and decrypts.
  • Self-inverse: XORing twice with the same key recovers the plaintext.
  • Symmetric: Both parties need only the shared key.

But: XOR is not secure against known-plaintext attacks if you reuse the key (this is called a “many-time pad”). For real security, you need a truly random key used only once (a one-time pad).


🪄 Quick Recap

✅ XOR is not like multiplication.
✅ You don’t need division to reverse it.
✅ Just XOR twice with the same key.
✅ This principle is used in stream ciphers and other cryptographic primitives.


This post is licensed under CC BY 4.0 by the author.