RSA encrypts a message m as c = m^e mod n, where n = p·q is the product of two secret primes and the private exponent d undoes it. The maths is sound; the parameters often aren't. This solver automates the three weaknesses that come up most in CTF cryptography, and each one leaves a recognisable trace in the public values.
How it picks an attack
e is tiny (typically 3) → the message may never have wrapped around the modulus → integer e-th root.
- Two ciphertexts share the same
n, with different e → the same message was encrypted twice under one modulus → common modulus attack.
e is almost as large as n → the private exponent d is probably small → Wiener's attack.
1. Small public exponent
Why it breaks. With e = 3 and a short, unpadded message, m³ can be smaller than n. Then the "mod n" never happens and the ciphertext is simply m³ as an ordinary integer.
The attack. Take the exact integer cube root of c. No key, no factoring: if the root is exact, it is the plaintext. When m³ only slightly exceeds n, the same idea still works by testing c + k·n for small k.
The fix it teaches. Use e = 65537 and real padding (OAEP), so the message is never small.
2. Common modulus
Why it breaks. Two parties share a modulus n but use different public exponents e₁ and e₂, and the same message is encrypted for both.
The attack. If gcd(e₁, e₂) = 1, the extended Euclidean algorithm gives integers a and b with a·e₁ + b·e₂ = 1. Then c₁^a · c₂^b = m^(a·e₁ + b·e₂) = m (mod n), using a modular inverse for whichever of a or b is negative. The plaintext comes out without ever touching a private key.
The fix it teaches. Never reuse a modulus between keys.
3. Wiener's attack
Why it breaks. To make decryption fast, d is chosen small. If d < n^(1/4) / 3, the fraction e/n is so close to k/d (for the k in e·d = 1 + k·φ(n)) that k/d appears among its continued-fraction convergents.
The attack. Expand e/n as a continued fraction and walk its convergents. Each one gives a candidate d and a candidate φ(n) = (e·d − 1)/k; the right one makes x² − (n − φ(n) + 1)·x + n = 0 have integer roots, and those roots are p and q. That recovers the factorisation and the real private key.
The fix it teaches. Keep d full-size; speed up decryption with CRT instead of a small exponent.
Design
Each attack family is a module behind one shared interface, and the solver chooses among them from the parameters it is given. Adding a family means adding a module; the solver's interface does not change.
What it gave me
Writing these attacks once, properly, meant understanding exactly where each one stops working — the bound in Wiener's theorem, the gcd condition on the exponents, the point where m^e wraps around n. That is the difference between running a script and knowing why a key is weak, which is what matters when an RSA deployment has to be reviewed rather than broken.