Reversing XOR Encryption with SHA-256: Tap Into Hash Walkthrough
Challenge Info:
- Platform: picoCTF 2025 - Tap Into Hash
- Category: Reverse Engineering
- Difficulty: Easy
- Techniques: SHA-256 Hashing, XOR Encryption, Key Recovery
Prerequisites
Before you start, make sure you've got:
- Python basics: You should know how variables and loops work.
- Command line: Just need to know how to run scripts.
- Python 3: Installed on your system.
Understanding the Challenge
We get a source code file (block_chain.py) and an encrypted file (enc_flag). The hint tells us blockchains use hashing - that's our clue.

Figure 1: The challenge description providing the source code and encrypted file.
Analyzing the Files
Two files:
block_chain.py: The script that created the encryption.enc_flag: The encrypted output with the flag we want.
When you open enc_flag, you'll see:
- A Key (looks like a byte string).
- An Encrypted Blockchain (also a byte string).
Source Code Analysis
Let's check out block_chain.py to see what's going on.
The main function does a few things:
Key Generation
Creates a random 32-byte hex key.
random_string = generate_random_string(64)
key = bytes.fromhex(random_string)
print("Key:", key) # <--- They just... gave us the key!
Blockchain Construction
Makes a fake blockchain with some dummy blocks.
Flag Hiding
Takes your input (the flag) and stuffs it right in the middle of the blockchain.
Encryption Process
The script uses a custom function that works on 16-byte chunks.
key_hash = hashlib.sha256(key).digest() # <--- This is important
ciphertext = b''
# Goes through the data 16 bytes at a time
for i in range(0, len(plaintext), block_size):
block = plaintext[i:i + block_size]
cipher_block = xor_bytes(block, key_hash) # XOR magic
ciphertext += cipher_block
Understanding the Cryptography
To crack this, we need to get two things: hashing and XOR.
Why "Tap into Hash"? (SHA-256)
The title is a hint. The script doesn't use the random key directly.
- It takes the random key (which we have).
- Runs it through SHA-256 to get a hash.
- Uses that hash to scramble the data.
How XOR Works
XOR is like a light switch cipher.
- Flip the switch with the Key on your Message → you get Scrambled Data.
- Flip the switch again with the same Key on the Scrambled Data → you get your Message back.
Visual Breakdown
Locking it:
[ Your Message ] ⊕ [ Hash of Key ] ===> [ Scrambled Mess ]
Unlocking it (what we'll do):
[ Scrambled Mess ] ⊕ [ Hash of Key ] ===> [ Your Message ]
XOR Example
Let me show you with actual bits to make this clear.
Say we have:
- Message: The letter 'A' (ASCII 65) → Binary:
01000001 - Key: Random byte (5) → Binary:
00000101
1. Lock it ($P oplus K = C$): Compare each bit. Different = 1, Same = 0.
01000001 (Message 'A')
⊕ 00000101 (Key 5)
----------
01000100 (Encrypted)
Result: 01000100 is 68 in decimal, which is 'D'.
'A' just became 'D'.
2. Unlock it ($C oplus K = P$): Now take 'D' and XOR with the same Key (5).
01000100 (Encrypted 'D')
⊕ 00000101 (Key 5)
----------
01000001 (Result)
Result: 01000001 which is 'A' again. Got it back!
This is exactly what our script does, but with 32 bytes instead of one.
The Mathematics
Here's what's happening:
- $P$ = Your original message (the flag)
- $K$ = The hash we calculate from the key
- $C$ = The encrypted junk in
enc_flag - $oplus$ = XOR symbol
Locking: $$ P oplus K = C $$
Unlocking: $$ C oplus K = P $$
Why This Works For Us
Normally, you'd only have the scrambled data ($C$) and no key ($K$).
But here, the enc_flag file literally gives us:
- The Scrambled Data ($C$): Called "Encrypted Blockchain".
- The Random String: Called "Key".
Since we have the random string, we can calculate the SHA-256 hash ($K$). With both $K$ and $C$, we can get $P$.
Attack Strategy
No brute forcing needed. Just:
- Grab the
Keystring fromenc_flag. - Hash it: Run SHA-256 on that string.
- XOR it back: Take the encrypted bytes and XOR with our hash.
The Solver Script
Here's a Python script that does it all:
import hashlib
import ast
def xor_bytes(a, b):
# XOR two byte strings together
return bytes(x ^ y for x, y in zip(a, b))
def solve():
print("--- Cracking enc_flag ---")
try:
# Read the file
with open("enc_flag", "r") as f:
content = f.read().strip().split('n')
# Find the key and encrypted data
key_line = [line for line in content if "Key:" in line][0]
cipher_line = [line for line in content if "Encrypted Blockchain:" in line][0]
# Parse the byte strings
key_bytes = ast.literal_eval(key_line.split("Key:", 1)[1].strip())
cipher_bytes = ast.literal_eval(cipher_line.split("Encrypted Blockchain:", 1)[1].strip())
except Exception as e:
print(f"Something went wrong: {e}")
return
# 1. Make the XOR key (the "Tap into Hash" part)
# Hash the key with SHA256 like the source code did
full_hash = hashlib.sha256(key_bytes).digest()
# Only need first 16 bytes (block size)
xor_key = full_hash[:16]
# 2. Decrypt
decrypted_data = b""
block_size = 16
for i in range(0, len(cipher_bytes), block_size):
chunk = cipher_bytes[i : i + block_size]
decrypted_block = xor_bytes(chunk, xor_key)
decrypted_data += decrypted_block
# 3. Show the result
plaintext = decrypted_data.decode('utf-8', errors='ignore')
print(f"n[+] Here's what we got:n{plaintext[:100]}...")
# Pull out the flag
import re
match = re.search(r"picoCTF{.*?}", plaintext)
if match:
print(f"n[SUCCESS] FLAG: {match.group(0)}")
if __name__ == "__main__":
solve()
Flag Capture
Run the script and watch it spit out the decrypted blockchain with the flag sitting right in the middle.
Flag: picoCTF{block_3SRhViRbT1qcX_XUjM0r49cH_qCzmJZzBK_60647fbb}

Figure 2: Successful decryption revealing the flag embedded in the blockchain data.
Key Takeaways
Cryptographic Concepts
- XOR encryption is symmetric: same operation encrypts and decrypts
- SHA-256 transforms keys into fixed-size hashes
- Block ciphers process data in fixed chunks (16 bytes here)
- Key leakage completely breaks XOR encryption
Attack Methodology
- Analyze source code to understand encryption logic
- Identify key material in output files
- Replicate hashing process (SHA-256)
- Reverse XOR operation with recovered key
- Extract flag from decrypted plaintext