Description
A message has been encrypted using RSA, but this time something feels... more crowded than usual. Can you decrypt it? Download the message.txt .
Setup
Download message.txt and inspect the RSA parameters (n, e, c).
Notice that n is 'more crowded than usual' - it has more than 2 prime factors.
cat message.txtSolution
Walk me through it- Step 1Identify multi-prime RSAThe modulus n is a product of 4 (or more) smaller primes rather than the standard 2. This makes n much easier to factor because each prime factor is proportionally smaller.
Learn more
Standard RSA uses a modulus
n = p * qwhere p and q are two large primes of roughly equal size (~1024 bits each for a 2048-bit key). The security rests on the integer factorization problem: given n, it is computationally infeasible to find p and q. For a 2048-bit n, the best known algorithms (General Number Field Sieve) require roughly 1018 operations.Multi-prime RSA (defined in RFC 3447) uses three or more prime factors:
n = p1 * p2 * p3 * ... * pk. This is sometimes used to speed up private-key operations using the Chinese Remainder Theorem. However, if the total bit length of n is fixed but shared among more primes, each individual prime is proportionally smaller - and smaller primes are much easier to find.For a 1024-bit n with 4 prime factors, each prime is roughly 256 bits. A 256-bit prime can be factored by Pollard's rho algorithm in seconds on a modern laptop. The name "cluster RSA" refers to this clustering of many small primes, which makes the modulus trivially factorable despite appearing large.
- Step 2Factorise the modulusPaste n into factordb.com first - most CTF moduli are precomputed there. Otherwise run sympy's factorint() or Sage's factor().bash
# Online: factor n at factordb.combashsage -c "print(factor(YOUR_N))"pythonpython3 -c " from sympy import factorint n = YOUR_N print(factorint(n)) "Learn more
FactorDB (factordb.com) is a collaborative database of known factorizations. CTF authors typically pick moduli that have been precomputed and submitted by previous solvers, so FactorDB returns the factors instantly. Always check FactorDB first.
Don't expect Pollard's rho to chew through 256-bit primes on its own. Rho runs in O(p1/2) per prime, which for a 256-bit factor is roughly 2128 operations - infeasible. The toy example below uses 5-bit primes purely for illustration. The real reason these challenge moduli are easy is that the factors are precomputed (factordb) or the primes are intentionally weak (small, smooth, or close together).
If FactorDB does not have it, try
sympy.factorint()with default heuristics, then specialised tools likeyafu,msieve, orcado-nfsfor larger moduli. SageMath'sfactor(n)wraps PARI/GP and chooses the best algorithm automatically; this is the standard tool for CTF cryptography.See the RSA Attacks for CTF post for the full taxonomy: small e, common modulus, Wiener, Coppersmith, and shared-prime attacks.
- Step 3Compute phi(n) and the private keyFor n = p1*p2*p3*p4, phi(n) = (p1-1)*(p2-1)*(p3-1)*(p4-1). Verify gcd(e, phi) == 1 first, then compute d = e^(-1) mod phi(n).python
python3 << 'EOF' from math import prod, gcd from sympy import mod_inverse primes = [p1, p2, p3, p4] # from factorisation n = prod(primes) e = YOUR_E c = YOUR_C phi = prod(p - 1 for p in primes) assert gcd(e, phi) == 1, "e and phi(n) must be coprime to invert" d = mod_inverse(e, phi) m = pow(c, d, n) print(m.to_bytes((m.bit_length() + 7) // 8, 'big')) EOFLearn more
Euler's totient function phi(n) counts integers from 1 to n that are coprime to n. For a product of distinct primes, phi(p1*p2*...*pk) = (p1-1)*(p2-1)*...*(pk-1). This formula is the algebraic foundation of RSA decryption: the private exponent d satisfies
e*d ≡ 1 (mod phi(n)), computed via the extended Euclidean algorithm.The decryption is
m = c^d mod nusing Python's built-inpow(c, d, n), which uses fast modular exponentiation (square-and-multiply) and is efficient even for thousand-bit numbers. The plaintext integer m is then converted to bytes:hex(m)[2:]strips the '0x' prefix, andbytes.fromhex()converts the hex string to bytes.An edge case: if m's hex representation has an odd number of digits (e.g., 'f' instead of '0f'),
bytes.fromhex()will fail. Usem.to_bytes((m.bit_length() + 7) // 8, 'big')for a robust conversion that always produces correctly-padded bytes.Multi-prime RSA worked example (toy parameters): primes = [11, 13, 17, 19] n = 11 * 13 * 17 * 19 = 46189 (~16 bits) phi = 10 * 12 * 16 * 18 = 34560 e = 7 (coprime to phi: gcd(7, 34560) = 1) Modular inverse via extended Euclidean (or Python pow(7,-1,34560)): d = 24673 Check: 7 * 24673 = 172711 = 5 * 34560 + 1 so 7d = 1 (mod phi) Encrypt m = 1234: c = 1234^7 mod 46189 square-and-multiply: 1234^2 = 1522756 mod 46189 = 32079 1234^4 = 32079^2 mod 46189 = ... After three squarings and two multiplies, c works out to 39204. Decrypt: m = 39204^24673 mod 46189 = 1234. Recovered. Why factoring is fast here: Each prime is ~5 bits in this toy, so trial division finishes in microseconds. For a real 1024-bit cluster-RSA n with k=4 primes, each factor is 256 bits and Pollard's rho is O(p^(1/2)) = O(2^128) per prime - infeasible from scratch. The CTF moduli are easy because factordb has them precomputed (or the primes are intentionally weak: small, smooth, or close together). Defense: stick to 2-prime RSA with primes ~n^(1/2). Multi-prime RSA is only safe when each individual prime is still 1024+ bits, i.e. for ridiculously large n.
Alternate Solution
Skip the scripting and use the RSA Calculator on this site. Paste in the four fields it asks for: n (the modulus), e (public exponent), d (private exponent - compute as pow(e, -1, phi) with phi = product of (p-1) over the prime factors), and the ciphertext. It handles BigInt values natively, so no Python or SageMath required.
Flag
picoCTF{clust3r_rs4_f4ct0r3d_...}
Multi-prime RSA (4 prime factors) is weak because each factor is ~n^(1/4), making factorisation trivial with Pollard's rho or factordb.