Description
Even more corrupted key.
Setup
Download the more heavily corrupted RSA private key PEM from the challenge page.
Parse the ASN.1 and figure out which of the nine fields are still intact this time.
wget <challenge_url>/key.pem # download the broken keySolution
Walk me through it- Step 1Dump the ASN.1 and inventory the surviving fieldsRun openssl asn1parse and list which of version, n, e, d, p, q, dp, dq, qinv are intact. In corrupt-key-2 the primes p and q are typically corrupted, but the CRT exponents (dp = d mod (p-1), dq = d mod (q-1)) or the modulus n and exponent d survive. There is still always enough surviving structure to recompute the rest with algebra - no lattice, no Coppersmith, no brute force is needed.bash
openssl asn1parse -in key.pembashbash# Field order: version, n, e, d, p, q, dp, dq, qinvbash# Mark which are nonzero and correctly sized. Common survivingbash# sets for this challenge: {n, e, dp, dq} or {n, e, d}.Learn more
The structure is identical to corrupt-key-1: a PKCS#1 ASN.1
SEQUENCEof nine integers.openssl rsastill refuses the file because the corruption leaves it internally inconsistent, butopenssl asn1parseprints whatever integers remain readable.The challenge corrupts morefields than part 1, which is intimidating but does not change the technique. RSA's fields are so redundant that even a small surviving subset pins down the whole key. The work is choosing the right relation to rebuild the primes from whatever survived.
- Step 2Recover the primes from the surviving CRT fieldsPick the reconstruction that matches your surviving fields. Case A - n, e, d survive: factor n deterministically from (n, e, d) using the standard k = e*d - 1 / repeated-square-root method, which always recovers p and q in polynomial time. Case B - dp (and/or dq) survive with n and e: since e*dp = 1 + k*(p-1) for some small integer k (1 <= k < e), iterate k over that small range, compute a candidate p = (e*dp - 1)/k + 1, and test whether that p divides n. The correct k yields p; then q = n // p.python
# Case A: factor n from (n, e, d) - deterministic, always works python3 -c " import random n = <n>; e = <e>; d = <d> k = e*d - 1 t = k while t % 2 == 0: t //= 2 p = None while p is None: a = random.randrange(2, n - 1) x = pow(a, t, n) if x in (1, n - 1): continue while x != n - 1: y = pow(x, 2, n) if y == 1: g = __import__('math').gcd(x - 1, n) if 1 < g < n: p = g break x = y q = n // p print('p', p); print('q', q) "python# Case B: recover p from a surviving CRT exponent dp python3 -c " n = <n>; e = <e>; dp = <dp> # e*dp = 1 + k*(p-1) for some 1 <= k < e for k in range(1, e): num = e*dp - 1 if num % k: continue p = num // k + 1 if n % p == 0: q = n // p print('k', k); print('p', p); print('q', q) break "Learn more
Factoring n from (n, e, d) is a textbook result: because
e*d - 1is a multiple oflcm(p-1, q-1), a Miller-Rabin-style square-root search finds a nontrivial factor of n in polynomial time with overwhelming probability. This is the go-to move whenever the private exponent survived.Recovering a prime from a CRT exponent uses
dp = d mod (p-1), which meanse*dp = 1 mod (p-1), i.e.e*dp - 1 = k*(p-1)for some integerkin the small range1 ≤ k < e. Trying eachkgives a candidatep; the correct one dividesn. With e = 65537 this is at most a few tens of thousands of trivial trial divisions.Both routes are exact arithmetic. Lattice attacks and Coppersmith are unnecessary because the corrupted key still leaks complete fields, not just partial bits.
- Step 3Rebuild the key and decryptOnce p and q are recovered (verify p * q == n), recompute d = e^-1 mod (p-1)(q-1), reconstruct the key, and decrypt the ciphertext exactly as in corrupt-key-1.python
python3 -c "bashfrom Crypto.Util.number import inverse, long_to_bytesbashn = <n>; e = <e>; p = <recovered_p>bashq = n // pbashassert p * q == n, 'wrong p!'bashd = inverse(e, (p - 1) * (q - 1))bashc = <ciphertext_int>pythonprint(long_to_bytes(pow(c, d, n)))bash"Learn more
The
assert p * q == ncheck confirms the recovered prime is exact before trusting the decryption. If it fails, you picked the wrong surviving-field case or the wrongk; revisit the asn1parse dump and re-identify which fields are intact.This second part reinforces the same point as the first: RSA private keys are heavily over-determined. Even with most fields corrupted, a single surviving exponent or prime is enough to reconstruct the entire key with elementary number theory - which is exactly why partial key leakage is catastrophic in practice.
Flag
picoCTF{...}
More fields corrupted, same idea: read the surviving ASN.1 integers with openssl asn1parse, then reconstruct the primes arithmetically - factor n from (n, e, d) if d survived, or recover p from a surviving CRT exponent via e*dp - 1 = k*(p-1). Then recompute d and decrypt. No lattice or Coppersmith.