Cryptography techniques for CTF challenges. Use when attacking encryption, hashing, ZKP, signatures, or mathematical crypto problems.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add aibot88/sec_skill_store --skill ctf-crypto --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ctf Crypto?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aibot88-ctf-crypto)More formats (shields.io, HTML) on the badges page.
---
name: ctf-crypto
description: Cryptography techniques for CTF challenges. Use when attacking encryption, hashing, ZKP, signatures, or mathematical crypto problems.
user-invocable: false
allowed-tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "Task", "WebFetch", "WebSearch"]
---
# CTF Cryptography
Quick reference for crypto challenges. For detailed techniques, see supporting files.
## Additional Resources
- [prng.md](prng.md) - PRNG attacks (Mersenne Twister, LCG, time-based seeds, password cracking)
- [historical.md](historical.md) - Historical ciphers (Lorenz SZ40/42)
- [advanced-math.md](advanced-math.md) - Advanced mathematical attacks (isogenies, Pohlig-Hellman, LLL, Coppersmith)
---
## ZKP Attacks
- Look for information leakage in proofs
- If proving IMPOSSIBLE problem (e.g., 3-coloring K4), you must cheat
- Find hash collisions to commit to one value but reveal another
- PRNG state recovery: salts generated from seeded PRNG can be predicted
- Small domain brute force: if you know `commit(i) = sha256(salt(i), color(i))` and have salt, brute all colors
## Graph 3-Coloring
```python
import networkx as nx
nx.coloring.greedy_color(G, strategy='saturation_largest_first')
```
## CBC-MAC vs OFB-MAC Vulnerability
- OFB mode creates a keystream that can be XORed for signature forgery
- If you have signature for known plaintext P1, forge for P2:
```
new_sig = known_sig XOR block2_of_P1 XOR block2_of_P2
```
- Don't forget PKCS#7 padding in calculations!
- Small bruteforce space? Just try all combinations (e.g., 100 for 2 unknown digits)
## Weak Hash Functions
- Linear permutations (only XOR, rotations) are algebraically attackable
- Build transformation matrix and solve over GF(2)
## GF(2) Gaussian Elimination
```python
import numpy as np
def solve_gf2(A, b):
"""Solve Ax = b over GF(2)."""
m, n = A.shape
Aug = np.hstack([A, b.reshape(-1, 1)]) % 2
pivot_cols, row = [], 0
for col in range(n):
pivot = next((r for r in range(row, m) if Aug[r, col]), None)
if pivot is None: continue
Aug[[row, pivot]] = Aug[[pivot, row]]
for r in range(m):
if r != row and Aug[r, col]: Aug[r] = (Aug[r] + Aug[row]) % 2
pivot_cols.append((row, col)); row += 1
if any(Aug[r, -1] for r in range(row, m)): return None
x = np.zeros(n, dtype=np.uint8)
for r, c in reversed(pivot_cols):
x[c] = Aug[r, -1] ^ sum(Aug[r, c2] * x[c2] for c2 in range(c+1, n)) % 2
return x
```
## RSA Attacks
- Small e with small message: take eth root
- Common modulus: extended GCD attack
- Wiener's attack: small d
- Fermat factorization: p and q close together
- Pollard's p-1: smooth p-1
- Hastad's broadcast attack: same message, multiple e=3 encryptions
## RSA with Consecutive Primes
**Pattern (Loopy Primes):** q = next_prime(p), making p ≈ q ≈ sqrt(N).
**Factorization:** Find first prime below sqrt(N):
```python
from sympy import nextprime, prevprime, isqrt
root = isqrt(n)
p = prevprime(root + 1)
while n % p != 0:
p = prevprime(p)
q = n // p
```
**Multi-layer variant:** 1024 nested RSA encryptions, each with consecutive primes of increasing bit size. Decrypt in reverse order.
## Multi-Prime RSA
When N is product of many small primes (not just p*q):
```python
# Factor N (easier when many primes)
from sympy import factorint
factors = factorint(n) # Returns {p1: e1, p2: e2, ...}
# Compute phi using all factors
phi = 1
for p, e in factors.items():
phi *= (p - 1) * (p ** (e - 1))
d = pow(e, -1, phi)
plaintext = pow(ciphertext, d, n)
```
## AES Attacks
- ECB mode: block shuffling, byte-at-a-time oracle
- CBC bit flipping: modify ciphertext to change plaintext
- Padding oracle: decrypt without key
## AES-CFB-8 Static IV State Forging
**Pattern (Cleverly Forging Breaks):** AES-CFB with 8-bit feedback and reused IV allows state reconstruction.
**Key insight:** After encrypting 16 known bytes, the AES internal shift register state is fully determined by those ciphertext bytes. Forge new ciphertexts by continuing encryption from known state.
## Classic Ciphers
- Caesar: frequency analysis or brute force 26 keys
- Vigenere: Kasiski examination, index of coincidence
- Substitution: frequency analysis, known plaintext
### Vigenère Cipher
**Known Plaintext Attack (most common in CTFs):**
```python
def vigenere_decrypt(ciphertext, key):
result = []
key_index = 0
for c in ciphertext:
if c.isalpha():
shift = ord(key[key_index % len(key)].upper()) - ord('A')
base = ord('A') if c.isupper() else ord('a')
result.append(chr((ord(c) - base - shift) % 26 + base))
key_index += 1
else:
result.append(c)
return ''.join(result)
def derive_key(ciphertext, plaintext):
"""Derive key from known plaintext (e.g., flag format CCOI26{)"""
key = []
for c, p in zip(ciphertext, plaintext):
if c.isalpha() and p.isalpha():
c_val = ord(c.upper()) - ord('A')
p_val = ord(p.upper()) - ord('A')
key.append(chr((c_val - p_val) % 26 + ord('A')))
return ''.join(key)
```
**When standard keys don't work:**
1. Key may not repeat - could be as long as message
2. Key derived from challenge theme (character names, phrases)
3. Key may have "padding" - repeated letters (IICCHHAA instead of ICHA)
4. Try guessing plaintext words from theme, derive full key
## Elliptic Curve Attacks (General)
**Small subgroup attacks:**
- Check curve order for small factors
- Pohlig-Hellman: solve DLP in small subgroups, combine with CRT
**Invalid curve attacks:**
- If point validation missing, send points on weaker curves
- Craft points with small-order subgroups
**Singular curves:**
- If discriminant Δ = 0, curve is singular
- DLP becomes easy (maps to additive/multiplicative group)
**Smart's attack:**
- For anomalous curves (order = field size p)
- Lifts to p-adics, solves DLP in O(1)
```python
# SageMath ECC basics
E = EllipticCurve(GF(p), [a, b])
G = E.gens()[0] # generator
order = E.order()
```
## ECC Fault Injection
**Pattern (Faulty Curves):** Bit flip during ECC computation reveals private key bits.
**Attack:** Compare correct vs faulty ciphertext, recover key bit-by-bit:
```python
# For each key bit position:
# If fault at bit i changes output → key bit i affects computation
# Binary distinguisher: faulty_output == correct_output → bit is 0
```
## Useful Tools
```bash
# Python setup
pip install pycryptodome z3-solver sympy gmpy2
# SageMath for advanced math (required for ECC)
sage -python script.py
```
## Common Patterns
```python
from Crypto.Util.number import *
# RSA basics
n = p * q
phi = (p-1) * (q-1)
d = inverse(e, phi)
m = pow(c, d, n)
# XOR
from pwn import xor
xor(ct, key)
```
## Z3 SMT Solver
Z3 solves constraint satisfaction - useful when crypto reduces to finding values satisfying conditions.
**Basic usage:**
```python
from z3 import *
# Boolean variables (for bit-level problems)
bits = [Bool(f'b{i}') for i in range(64)]
# Integer/bitvector variables
x = BitVec('x', 32) # 32-bit bitvector
y = Int('y') # arbitrary precision int
solver = Solver()
solver.add(x ^ 0xdeadbeef == 0x12345678)
solver.add(y > 100, y < 200)
if solver.check() == sat:
model = solver.model()
print(model.eval(x))
```
**BPF/SECCOMP filter solving:**
When challenges use BPF bytecode for flag validation (e.g., custom syscall handlers):
```python
from z3 import *
# Model flag as array of 4-byte chunks (how BPF sees it)
flag = [BitVec(f'f{i}', 32) for i in range(14)]
s = Solver()
# Constraint: printable ASCII
for f in flag:
for byte in range(4):
b = (f >> (byte * 8)) & 0xff
s.add(b >= 0x20, b < 0x7f)
# Extract constraints from BPF dump (seccomp-tools dump ./binary)
mem = [BitVec(f'm{i}', 32) for i in range(16)]
# Example BPF constraint reconstruction
s.add(mem[0] == flag[0])
s.add(mem[1] == mem[0] ^ flag[1])
s.add(mem[4] == mem[0] + mem[1] + mem[2] + mem[3])
s.add(mem[8] == 4127179254) # From BPF if statement
if s.check() == sat:
m = s.model()
flag_bytes = b''
for f in flag:
val = m[f].as_long()
flag_bytes += val.to_bytes(4, 'little')
print(flag_bytes.decode())
```
**Converting bits to flag:**
```python
from Crypto.Util.number import long_to_bytes
if solver.check() == sat:
model = solver.model()
flag_bits = ''.join('1' if model.eval(b) else '0' for b in bits)
print(long_to_bytes(int(flag_bits, 2)))
```
**When to use Z3:**
- Type system constraints (OCaml GADTs, Haskell types)
- Custom hash/cipher with algebraic structure
- Equation systems over finite fields
- Boolean satisfiability encoded in challenge
- Constraint propagation puzzles
## Cascade XOR (First-Byte Brute Force)
**Pattern (Shifty XOR):** Each byte XORed with previous ciphertext byte.
```python
# c[i] = p[i] ^ c[i-1] (or similar cascade)
# Brute force first byte, rest follows deterministically
for first_byte in range(256):
flag = [first_byte]
for i in range(1, len(ct)):
flag.append(ct[i] ^ flag[i-1])
if all(32 <= b < 127 for b in flag):
print(bytes(flag))
```
## ECB Pattern Leakage on Images
**Pattern (Electronic Christmas Book):** AES-ECB on BMP/image data preserves visual patterns.
**Exploitation:** Identical plaintext blocks produce identical ciphertext blocks, revealing image structure even when encrypted. Rearrange or identify patterns visually.
## Padding Oracle Attack
**Pattern (The Seer):** Server reveals whether decrypted padding is valid.
**Byte-by-byte decryption:**
```python
def decrypt_byte(block, prev_block, position, oracle):
for guess in range(256):
modified = bytearray(prev_block)
# Set known bytes to produce valid padding
pad_value = 16 - position
for j in range(position + 1, 16):
modified[j] = known[j] ^ pad_value
modified[position] = guess
if oracle(bytes(modified) + block):
return guess ^ pad_value
```
## Atbash Cipher
Simple substitution: A↔Z, B↔Y, C↔X, etc.
```python
def atbash(text):
return ''.join(
chr(ord('Z') - (ord(c.upper()) - ord('A'))) if c.isalpha() else c
for c in text
)
```
**Identification:** Challenge name hints ("Abashed" ≈ Atbash), preserves spaces/punctuation, 1-to-1 substitution.
## Substitution Cipher with Rotating Wheel
**Pattern (Wheel of Mystery):** Physical cipher wheel with inner/outer alphabets.
**Brute force all rotations:**
```python
outer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}"
inner = "QNFUVWLEZYXPTKMR}ABJICOSDHG{" # Given
for rotation in range(len(outer)):
rotated = inner[rotation:] + inner[:rotation]
mapping = {outer[i]: rotated[i] for i in range(len(outer))}
decrypted = ''.join(mapping.get(c, c) for c in ciphertext)
if decrypted.startswith("METACTF{"):
print(decrypted)
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!