kavklaw@llm $ cat crypto-cheatsheet.md
Common crypto in CTFs — encoding, ciphers, RSA attacks, hash cracking, and more.
Encoding is reversible without a key. Spot it first before trying to "crack" anything.
# Encode
echo -n "Hello" | base64 # SGVsbG8=
base64 file.txt > encoded.txt
# Decode
echo "SGVsbG8=" | base64 -d # Hello
base64 -d encoded.txt > decoded.txt
# Python
import base64
base64.b64encode(b"Hello") # b'SGVsbG8='
base64.b64decode(b"SGVsbG8=") # b'Hello'
# Identification: charset A-Z a-z 0-9 +/ with = padding
# Variants: base64url uses - and _ instead of + and /
# Encode
echo -n "Hello" | xxd -p # 48656c6c6f
echo -n "Hello" | od -A n -t x1 | tr -d ' \n'
# Decode
echo "48656c6c6f" | xxd -r -p # Hello
python3 -c "print(bytes.fromhex('48656c6c6f').decode())"
# Python
bytes.fromhex("48656c6c6f") # b'Hello'
b"Hello".hex() # '48656c6c6f'
# Identification: only 0-9 a-f characters, even length
# Encode
python3 -c "import urllib.parse; print(urllib.parse.quote('Hello World!'))"
# Hello%20World%21
# Decode
python3 -c "import urllib.parse; print(urllib.parse.unquote('Hello%20World%21'))"
# Hello World!
# Double URL encoding (bypass WAFs)
# H → %48 → %2548
# Binary to text
python3 -c "print(''.join(chr(int(b,2)) for b in '01001000 01101001'.split()))"
# Hi
# Decimal (ASCII) to text
python3 -c "print(''.join(chr(i) for i in [72, 101, 108, 108, 111]))"
# Hello
# Octal to text
python3 -c "print(''.join(chr(int(o,8)) for o in '110 145 154 154 157'.split()))"
# Hello
XOR is the most common "encryption" in easy CTFs. Properties: A ⊕ B = C, C ⊕ B = A, C ⊕ A = B.
# XOR with single byte key
python3 -c "
data = bytes.fromhex('encrypted_hex')
for key in range(256):
result = bytes([b ^ key for b in data])
if b'flag' in result or b'CTF' in result:
print(f'Key {key}: {result}')
"
# XOR with known plaintext
python3 -c "
cipher = bytes.fromhex('cipher_hex')
known = b'flag{'
key = bytes([c ^ k for c, k in zip(cipher, known)])
print(f'Key fragment: {key}')
# If key repeats, extend it
"
# XOR with repeating key
python3 -c "
from itertools import cycle
cipher = bytes.fromhex('cipher_hex')
key = b'secret'
plain = bytes([c ^ k for c, k in zip(cipher, cycle(key))])
print(plain)
"
# XOR tools
xortool encrypted_file # Guess key length and key
xortool -l 4 encrypted_file # Known key length
xortool -c 20 encrypted_file # Known most frequent char (space=0x20)
# Cyber Chef: XOR Brute Force operation
# ROT13
echo "Hello" | tr 'A-Za-z' 'N-ZA-Mn-za-m' # Uryyb
python3 -c "import codecs; print(codecs.decode('Uryyb', 'rot_13'))"
# Brute force all 26 Caesar shifts
python3 -c "
text = 'Khoor Zruog'
for i in range(26):
shifted = ''.join(chr((ord(c) - ord('A' if c.isupper() else 'a') - i) % 26 + ord('A' if c.isupper() else 'a')) if c.isalpha() else c for c in text)
print(f'Shift {i:2d}: {shifted}')
"
# Shift 3: Hello World
# Vigenere cipher (polyalphabetic Caesar)
# Use: https://www.dcode.fr/vigenere-cipher
# Or Kasiski examination to find key length
RSA is everywhere in CTFs. Know the math: n = p × q, φ(n) = (p-1)(q-1), e × d ≡ 1 mod φ(n), c = m^e mod n, m = c^d mod n.
# Basic RSA decryption (given p, q, e, c)
from Crypto.Util.number import inverse, long_to_bytes
p = ... # prime 1
q = ... # prime 2
e = 65537
c = ... # ciphertext
n = p * q
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
# If m^e < n, ciphertext is just m^e
# Take the e-th root to recover m
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes
e = 3
c = ... # ciphertext
m, exact = iroot(c, e)
if exact:
print(long_to_bytes(m))
# Same message encrypted with e=3 to 3 different moduli
# Use Chinese Remainder Theorem
from sympy.ntheory.modular import crt
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes
e = 3
n_list = [n1, n2, n3]
c_list = [c1, c2, c3]
# CRT gives us m^e mod (n1*n2*n3)
combined_c, combined_n = crt(n_list, c_list)
m, exact = iroot(int(combined_c), e)
print(long_to_bytes(int(m)))
# Same n, same m, different e values (gcd(e1,e2) = 1)
from Crypto.Util.number import long_to_bytes
from gmpy2 import gcdext
n = ...
e1 = ...
e2 = ...
c1 = ...
c2 = ...
# Extended GCD: a*e1 + b*e2 = 1
g, a, b = gcdext(e1, e2)
# m = c1^a * c2^b mod n
if a < 0:
c1 = pow(c1, -1, n) # modular inverse
a = -a
if b < 0:
c2 = pow(c2, -1, n)
b = -b
m = (pow(c1, int(a), n) * pow(c2, int(b), n)) % n
print(long_to_bytes(m))
# When d is small (d < n^0.25 / 3)
# e/n approximates k/d via continued fractions
# Use: https://github.com/pablocelayes/rsa-wiener-attack
# Or owiener library:
import owiener
d = owiener.attack(e, n)
if d:
m = pow(c, d, n)
print(long_to_bytes(m))
# If n is small enough to factor
# factordb.com — online factoring database
# Or use yafu / msieve locally
# Python — check factordb
import requests
r = requests.get(f"http://factordb.com/api?query={n}")
factors = r.json()
# sympy factoring (small numbers)
from sympy import factorint
factors = factorint(n) # {p: 1, q: 1}
# Fermat factoring (p and q close together)
from gmpy2 import isqrt
a = isqrt(n) + 1
while True:
b2 = a*a - n
b, exact = iroot(b2, 2)
if exact:
p, q = int(a + b), int(a - b)
break
a += 1
# RsaCtfTool — automated RSA attacks
# https://github.com/RsaCtfTool/RsaCtfTool
python3 RsaCtfTool.py --publickey pub.pem --uncipherfile flag.enc
python3 RsaCtfTool.py -n N -e E --uncipher C
python3 RsaCtfTool.py --publickey pub.pem --private # Extract private key
Common AES pitfalls in CTFs.
# ECB encrypts each block independently — same plaintext block = same ciphertext block
# Vulnerability: block patterns visible, byte-at-a-time attack
# ECB byte-at-a-time oracle attack:
# 1. Find block size (vary input length, watch output length jump)
# 2. Confirm ECB (send repeated blocks, check for repeated ciphertext)
# 3. Decrypt byte-by-byte:
# - Pad input so unknown byte is at end of block
# - Try all 256 values, compare ciphertext blocks
# Detect ECB:
from collections import Counter
blocks = [cipher[i:i+16] for i in range(0, len(cipher), 16)]
if len(blocks) != len(set(blocks)):
print("ECB detected — repeated blocks!")
# CBC: each block XORed with previous ciphertext before encryption
# Vulnerabilities: bit-flipping, padding oracle
# CBC bit-flipping:
# To change byte at position i in plaintext block N,
# XOR the byte at position i in ciphertext block N-1
# with: desired_value XOR original_value
# Padding oracle attack:
# If server reveals "bad padding" vs "bad data", you can decrypt everything
# Tool: PadBuster
padbuster http://target/vulnerable.php CIPHERTEXT_HEX 16 -encoding 0
# Python: padding oracle
# https://github.com/mwielgoszewski/python-paddingoracle
# Identify hash type
hashid 'e99a18c428cb38d5f260853678922e03'
hash-identifier
# Or: https://hashes.com/en/tools/hash_identifier
# Common hash lengths:
# MD5: 32 hex chars (128 bits)
# SHA-1: 40 hex chars (160 bits)
# SHA-256: 64 hex chars (256 bits)
# SHA-512: 128 hex chars (512 bits)
# NTLM: 32 hex chars
# bcrypt: $2b$... (60 chars)
# Linux: $6$... (SHA-512), $5$... (SHA-256), $1$... (MD5)
# Common modes:
# 0 = MD5
# 100 = SHA1
# 1400 = SHA-256
# 1700 = SHA-512
# 1000 = NTLM
# 1800 = sha512crypt ($6$)
# 3200 = bcrypt
# 5600 = NetNTLMv2
# 13100 = Kerberoast
# 18200 = AS-REP Roast
# 16500 = JWT
# Wordlist attack
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
# With rules
hashcat -m 0 hash.txt rockyou.txt -r /usr/share/hashcat/rules/best64.rule
# Brute force
hashcat -m 0 hash.txt -a 3 ?a?a?a?a?a?a # 6 chars, all charsets
# Mask attack
hashcat -m 0 hash.txt -a 3 ?u?l?l?l?d?d?d?d # Ullldddd pattern
# Show cracked
hashcat -m 0 hash.txt --show
# Auto-detect hash type
john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
# Specific format
john --format=raw-md5 hash.txt --wordlist=rockyou.txt
john --format=raw-sha256 hash.txt --wordlist=rockyou.txt
# Extract hashes from files
ssh2john id_rsa > hash.txt
zip2john file.zip > hash.txt
rar2john file.rar > hash.txt
keepass2john database.kdbx > hash.txt
pdf2john file.pdf > hash.txt
office2john file.docx > hash.txt
gpg2john private.key > hash.txt
# Show cracked
john --show hash.txt
# CrackStation — rainbow tables for common hashes
# https://crackstation.net/
# hashes.com — hash lookup
# https://hashes.com/
# cmd5.org — large hash database
# https://cmd5.org/
# Structure: header.payload.signature (base64url encoded)
# Decode:
echo "eyJhbG..." | cut -d. -f1 | base64 -d 2>/dev/null # Header
echo "eyJhbG..." | cut -d. -f2 | base64 -d 2>/dev/null # Payload
# Or: jwt.io for visual decoding
# None algorithm attack:
# Change: {"alg":"HS256"} → {"alg":"none"}
# Remove signature: header.payload.
python3 -c "
import base64, json
header = base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({'user':'admin','role':'admin'}).encode()).rstrip(b'=')
print(f'{header.decode()}.{payload.decode()}.')
"
# HMAC/RSA confusion:
# Server uses RS256 (asymmetric) but accepts HS256 (symmetric)
# Sign with the PUBLIC key using HS256
python3 jwt_tool.py TOKEN -X k -pk public.pem
# Crack weak HMAC secret:
hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256
# kid injection:
# {"alg":"HS256","kid":"../../etc/passwd"}
# Sign with the content of that file as the key
# jwt_tool comprehensive testing:
python3 jwt_tool.py TOKEN -M at # All tests
python3 jwt_tool.py TOKEN -C -d rockyou.txt # Crack
# Inspect certificate
openssl s_client -connect target:443 </dev/null 2>/dev/null | openssl x509 -text
# Look for: CN, SAN (Subject Alternative Names), issuer, dates
# Extract just the hostnames
openssl s_client -connect target:443 </dev/null 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
# Test SSL/TLS vulnerabilities
sslscan target
testssl.sh target
nmap --script=ssl-enum-ciphers,ssl-cert,ssl-heartbleed -p 443 target
# Check for Heartbleed
nmap --script=ssl-heartbleed -p 443 target
# Download certificate
echo | openssl s_client -connect target:443 2>/dev/null | openssl x509 -out cert.pem
# Verify a certificate
openssl verify -CAfile ca.pem cert.pem
# Generate self-signed cert (for evil server)
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
# Pattern: Repeating XOR key
# Clue: Encrypted text, challenge mentions "key" or "XOR"
# Attack: Frequency analysis, known plaintext (flag format)
# Pattern: RSA with small e (e=3)
# Clue: Very small e value, message shorter than n
# Attack: Cube root of ciphertext
# Pattern: RSA with small n
# Clue: n is factorable (check factordb.com)
# Attack: Factor n → get p,q → compute d
# Pattern: ECB mode
# Clue: Identical blocks in ciphertext, block-aligned input
# Attack: Block manipulation, byte-at-a-time oracle
# Pattern: Weak hash / known plaintext
# Clue: Hash values given, limited character set
# Attack: Rainbow tables, hashcat, CrackStation
# Pattern: Custom cipher
# Clue: Source code provided, "homebrew crypto"
# Attack: Read the source, find the reversible operation
# Pattern: Base64/hex multi-encoding
# Clue: Long string of printable characters
# Attack: Decode repeatedly until plaintext appears
# CyberChef "Magic" operation auto-detects encoding chains
# Pattern: Padding oracle
# Clue: Server returns different errors for bad padding vs bad data
# Attack: PadBuster, bit-by-bit decryption
# Pattern: Vigenere / substitution cipher
# Clue: English-looking text but scrambled, historical theme
# Attack: Frequency analysis, quipqiup.com, dcode.fr
# Useful tools:
# - CyberChef: https://gchq.github.io/CyberChef/
# - dcode.fr: https://www.dcode.fr/
# - RsaCtfTool: https://github.com/RsaCtfTool/RsaCtfTool
# - xortool: https://github.com/hellman/xortool
# - PadBuster: https://github.com/AonCyberLabs/PadBuster
# - SageMath: for advanced math (lattice, Coppersmith, etc.)