← Back to Cheat Sheets
kavklaw@llm ~ /cheatsheets/crypto

kavklaw@llm $ cat crypto-cheatsheet.md

Cryptography Cheat Sheet

Common crypto in CTFs — encoding, ciphers, RSA attacks, hash cracking, and more.

Encoding (NOT Encryption)

Encoding is reversible without a key. Spot it first before trying to "crack" anything.

Base64

# 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 /

Hex Encoding

# 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

URL Encoding

# 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 / Octal / Decimal

# 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

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

Caesar Cipher / ROT13

# 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 Basics

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

Small e Attack (e=3, no padding)

# 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))

Hastad's Broadcast Attack (same m, e recipients)

# 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)))

Common Modulus Attack

# 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))

Wiener's Attack (large d → small d via continued fractions)

# 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))

Factoring Small n

# 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

RSA Tool

# 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

AES

Common AES pitfalls in CTFs.

AES-ECB (Electronic Codebook)

# 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!")

AES-CBC (Cipher Block Chaining)

# 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

Hash Cracking

Hash Identification

# 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)

Hashcat

# 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

John the Ripper

# 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

Online Resources

# CrackStation — rainbow tables for common hashes
# https://crackstation.net/

# hashes.com — hash lookup
# https://hashes.com/

# cmd5.org — large hash database
# https://cmd5.org/

JWT (JSON Web Tokens)

# 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

SSL/TLS

# 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

Common CTF Crypto Patterns

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