Introduction
RSA is the most frequently attacked cryptosystem in CTF competitions. When correctly implemented with large primes and proper padding, RSA is secure. But CTF challenges deliberately introduce subtle weaknesses: a tiny public exponent, a modulus with small prime factors, two ciphertexts sharing the same modulus, or a private exponent so small it can be recovered by a continued-fraction attack.
This guide covers the attacks you will encounter most often in picoCTF, roughly in order from simplest to most involved. Each section links directly to the picoCTF writeup where that attack was the key to solving the challenge.
Condition: e is tiny (3 or 17) and message is not padded
Condition: n is small enough to factor with a tool or OEIS
Condition: Same n, same message, two different e values
Condition: Private exponent d is unusually small relative to n
Condition: Server will decrypt arbitrary ciphertexts (except the target)
RSA basics
Before attacking RSA you need to be comfortable reading the parameters you are given. A standard RSA public key consists of:
n = p * q # modulus (product of two large primes)e = 65537 # public exponent (usually 65537, sometimes 3)c = m^e mod n # ciphertext# To decrypt you need the private exponent d:d = e^-1 mod phi(n) where phi(n) = (p-1)*(q-1)m = c^d mod n
The security of RSA depends entirely on the difficulty of factoring n. If an attacker can recover p and q, they can compute phi(n), then d, and decrypt any ciphertext.
picoCTF RSA fundamentals challenge
Tests every RSA formula directly - a good benchmark to confirm you can compute d, decrypt ciphertexts, and recover p from n and q before attempting the harder attacks.
Small public exponent (e=3 cube root attack)
When e=3 and the plaintext message m is small enough that m^3 < n, the ciphertext c = m^3 (without the modular reduction). You can recover the message simply by taking the integer cube root of c.
from gmpy2 import iroot# c = m^3 when m^3 < nm, exact = iroot(c, 3)if exact:print(bytes.fromhex(hex(m)[2:]))
More generally, if e is small but m^e > n, you need to combine the cube root attack with Hastad's broadcast attack (multiple ciphertexts of the same message under different moduli) or use a Coppersmith short-pad attack.
Weak modulus (factoring n)
If n is small (under ~512 bits) or was generated from weak primes, you can factor it directly. Three reliable approaches:
FactorDB
FactorDB is a public database of pre-computed factorizations. Paste n into the website or query it programmatically:
pip install factordb-pythonfrom factordb.factordb import FactorDBf = FactorDB(n)f.connect()factors = f.get_factor_list() # [p, q]
SageMath / PARI
# SageMathfactor(n)# Python with sympyfrom sympy import factorintfactorint(n) # works for small n (~20 digits)
Once you have p and q
from Crypto.Util.number import long_to_bytesphi = (p - 1) * (q - 1)d = pow(e, -1, phi) # Python 3.8+ modular inversem = pow(c, d, n)print(long_to_bytes(m))
picoCTF challenges using this technique
Common modulus attack
If the same plaintext m is encrypted under the same modulus n but two different public exponents e1 and e2 that are coprime to each other, you can recover mwithout knowing either private key.
The attack uses the extended Euclidean algorithm to find integers a and b such that a*e1 + b*e2 = 1, then combines the two ciphertexts:
from math import gcddef extended_gcd(a, b):if b == 0:return a, 1, 0g, x, y = extended_gcd(b, a % b)return g, y, x - (a // b) * y_, a, b = extended_gcd(e1, e2)# Handle negative exponents with modular inverseif a < 0:c1 = pow(pow(c1, -1, n), -a, n)a = -aelse:c1 = pow(c1, a, n)if b < 0:c2 = pow(pow(c2, -1, n), -b, n)b = -belse:c2 = pow(c2, b, n)m = (c1 * c2) % n
picoCTF challenges using this technique
Wiener's attack (small private exponent)
When the private exponent d is small relative to n(specifically, when d < n^0.25 / 3), Wiener's theorem lets you recover d from the public key alone using the continued-fraction expansion of e/n.
This happens in challenges where the problem author set an unusually small d for "efficiency", or where e is enormous (close to phi(n)) which implies d is small.
pip install owienerimport owienerd = owiener.attack(e, n)if d is None:print('Wiener attack failed')else:from Crypto.Util.number import long_to_bytesm = pow(c, d, n)print(long_to_bytes(m))
e - if e is close in magnitude to n, Wiener's attack is almost certainly the intended path. Also check if d is hinted to be "small".RSA oracle attack
Some challenges give you a server that will decrypt any ciphertext you send it, except the target ciphertext c. An RSA oracle attack (also called multiplicative homomorphism exploitation) works around this restriction by transforming c into a different ciphertext that decrypts to a related plaintext, then unwrapping the transformation.
Since RSA satisfies Enc(m1) * Enc(m2) = Enc(m1 * m2) mod n, you can multiply the ciphertext by an encryped blinding factor:
# Choose a random blinding factor rr = 2blinded_c = (c * pow(r, e, n)) % n # = Enc(r * m)# Ask the oracle to decrypt blinded_cblinded_m = oracle_decrypt(blinded_c)# Divide out the blinding factorr_inv = pow(r, -1, n)m = (blinded_m * r_inv) % nfrom Crypto.Util.number import long_to_bytesprint(long_to_bytes(m))
picoCTF challenge using this technique
Tools
RsaCtfTool
Tries dozens of RSA attacks automatically given n, e, and c. The fastest first-pass tool for RSA challenges.
pip install rsactftoolRsaCtfTool --n N --e E --uncipher C --attack all
FactorDB
Database of pre-computed prime factorizations. Paste your n directly into the website or use the Python client.
pip install factordb-python
pycryptodome
The standard Python cryptography library for CTF. Used for long_to_bytes, bytes_to_long, and constructing RSA keys.
pip install pycryptodome
SageMath
Mathematical computing environment with built-in prime factoring, lattice reduction (for Coppersmith), and number theory functions.
sudo apt install sagemath
RSA Calculator (browser tool)
Decrypt a ciphertext given p, q, e, and c entirely in your browser - no Python required. Handles arbitrarily large integers using JavaScript BigInt.
Quick reference
| Symptom | Attack |
|---|---|
| e=3 (or small) and small m | Integer e-th root of c |
| n is small (<512 bits) | Factor n with FactorDB / SageMath |
| Same n, two different e values | Common modulus attack |
| e is huge (close to phi(n)) | Wiener's attack (small d) |
| Server decrypts for you (except target) | Blinding / multiplicative oracle |
| Unknown attack type | RsaCtfTool --attack all |