Challenge

Category: Crypto · Points: 200 · CTF: picoCTF 2026


Analysis

The source reveals the key generation flaw immediately:

d = getPrime(256)       # d is only 256 bits
e = inverse(d, phi)     # e is derived from d, not the other way around

Standard RSA picks a small e (like 65537) and computes d. Here it’s invertedd is chosen small and e is derived. This gives:

δ = log(d) / log(n) ≈ 256 / 2095 ≈ 0.122

Wiener’s theorem states that if d < n^0.25, the private key can be recovered in polynomial time. Since 0.122 < 0.25, Wiener fires easily.


The Attack

Since ed ≡ 1 (mod φ(n)), we have e/n ≈ k/d for some small integer k. The convergents of the continued fraction expansion of e/n enumerate rational approximations — one will equal k/d.

For each convergent k/d_cand, verify by checking whether (ed - 1)/k yields a valid φ(n) — i.e., whether x² - (n - φ + 1)x + n = 0 has integer roots.

from Crypto.Util.number import long_to_bytes
from fractions import Fraction

def continued_fraction(n, d):
    while d:
        yield n // d
        n, d = d, n % d

def convergents(cf):
    n0, n1 = 1, 0
    d0, d1 = 0, 1
    for q in cf:
        n0, n1 = q * n0 + n1, n0
        d0, d1 = q * d0 + d1, d0
        yield n0, d0

def wiener(e, n):
    for k, d in convergents(continued_fraction(e, n)):
        if k == 0:
            continue
        if (e * d - 1) % k != 0:
            continue
        phi = (e * d - 1) // k
        # check if phi gives integer roots for (p-1)(q-1)
        b = n - phi + 1
        disc = b * b - 4 * n
        if disc >= 0:
            sq = int(disc ** 0.5)
            if sq * sq == disc:
                return d
    return None

# Given values
n = # paste n here
e = # paste e here
c = # paste c here

d = wiener(e, n)
print(long_to_bytes(pow(c, d, n)))

At δ ≈ 0.122, Wiener recovers d after scanning just a few hundred convergents.


Key Takeaway

Never choose d directly. Always pick e first (e.g. 65537), then compute d = e⁻¹ mod φ(n). A small d sacrifices the entire security of RSA regardless of how large n is.


Flag

picoCTF{sm4ll_d_24ad96a6}