← Back to Writeups
CUSTOMEasyCrypto

XOR Key Recovery — Favourite byte & You either know XOR you don't

XESXOR9/10/20263 min read
#crypto#custom#easy#crib_dragging#flag_format_crib#repeating_key_xor_known_plaint#single_byte_xor_brute_force

XOR Key Recovery — Favourite byte & You either know XOR you don't

Platform: cryptohack | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-21 | Status: Solved Techniques: crib_dragging, flag_format_crib, repeating_key_xor_known_plaintext, single_byte_xor_brute_force

Summary

Task: two CryptoHack XOR challenges — single-byte XOR and repeating-key XOR, each given a hex ciphertext. Solution: brute-force the single byte with the crypto{ crib, then XOR the known prefix against the ciphertext to leak the repeating key (myXORkey) and decrypt.

XOR Key Recovery — CryptoHack (General / XOR stage)

Description

Two consecutive CryptoHack General challenges on the XOR stage:

  • Favourite byte (20 pts): "Hidden with XOR using a single secret byte."
  • You either know, XOR you don't (30 pts): "Remember the flag format and how it might help you."

Each provides a hex-encoded ciphertext. The goal in both is to recover the plaintext flag. The common thread is exploiting the known CryptoHack flag format crypto{ as a crib (known-plaintext fragment). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

XOR is its own inverse: pt ^ key = ct implies ct ^ key = pt and ct ^ pt = key.

  • Single-byte XOR: the whole message is XORed with one byte. The keyspace is only 256 values, so an exhaustive search plus a crib (the plaintext must begin with crypto{) recovers the key instantly.
  • Repeating-key XOR (Vigenère-style): the message is XORed with a short key that repeats. XORing the ciphertext against a known plaintext fragment over its length reveals the corresponding bytes of the key. If the key is a short human-readable word, the leaked prefix gives away the whole key. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Challenge 1 — Favourite byte (single-byte XOR)

Brute force all 256 possible key bytes and keep the one whose plaintext starts with the flag format:

python
#!/usr/bin/env python3
ct = bytes.fromhex("73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d")
for k in range(256):
    pt = bytes(b ^ k for b in ct)
    if pt.startswith(b"crypto{"):
        print(hex(k), pt.decode())
# 0x10 crypto{0x10_15_my_f4v0ur173_by7e}

The key byte is 0x10, and the flag even spells out the answer. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Challenge 2 — You either know, XOR you don't (repeating-key XOR)

XOR the ciphertext against the 7-byte known prefix crypto{ to leak the start of the key:

python
#!/usr/bin/env python3
ct = bytes.fromhex("0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104")
known = b"crypto{"

partial = bytes(ct[i] ^ known[i] for i in range(len(known)))
print(partial)            # b'myXORke'  -> obviously the start of "myXORkey"

key = b"myXORkey"
pt = bytes(ct[i] ^ key[i % len(key)] for i in range(len(ct)))
print(pt.decode())        # crypto{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The leaked prefix myXORke makes the full 8-byte key myXORkey obvious. Decrypting with the repeating key yields the flag.

Flags

crypto{0x10_15_my_f4v0ur173_by7e}
crypto{REDACTED}

Bonus — CryptoHack submission API (automation)

To script bulk flag submission on CryptoHack:

  • Endpoint: POST https://www.cryptohack.org/api/submit/{challenge_id}.json — note the /api prefix. The page form action /submit/{id}.json issues a 308 redirect that drops the POST body, so always hit /api/... directly.
  • Body: flag=<value>&_csrf_token=<token>.
  • Required: the authenticated session cookie and header X-Requested-With: XMLHttpRequest.
  • Responses: success → {"code":0,"message":"Success!","solves":N}; already solved → {"code":1001,...}. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Both flags were submitted and confirmed correct (code 0, Success!). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍