Description
My RSA key is corrupted. Can you fix it?
Setup
Download the provided RSA private key PEM file from the challenge page.
Notice that openssl rejects it: some fields inside the key are corrupted/zeroed.
wget <challenge_url>/key.pem # download the broken keySolution
Walk me through it- Step 1Parse the PEM as raw ASN.1 to see which fields survivedAn RSA private key stores nine values in order: version, n (modulus), e (public exponent), d (private exponent), p, q, dp = d mod (p-1), dq = d mod (q-1), and qinv = q^-1 mod p. openssl rsa fails on a corrupted key because it cross-checks these fields, but openssl asn1parse decodes the SEQUENCE field by field so you can read the intact integers directly. Identify which fields are correct and which were zeroed or mangled - in corrupt-key-1 only one or two fields are damaged, leaving enough to rebuild everything else.bash
# This usually errors because the fields are inconsistent:bashopenssl rsa -in key.pem -text -noout 2>&1 | headbashbash# Dump the raw ASN.1 integers regardless of consistency:bashopenssl asn1parse -in key.pembashbash# Each INTEGER line is one of: version, n, e, d, p, q, dp, dq, qinvbash# Read off the ones that are intact (nonzero, correct length).Learn more
An RSA private key in PKCS#1 format (RFC 8017) is an ASN.1
SEQUENCEof nineINTEGERvalues in a fixed order:version, n, e, d, p, q, dp, dq, qinv. The PEM file is just that DER structure base64-encoded between-----BEGIN RSA PRIVATE KEY-----markers.openssl rsavalidates the whole key and refuses to load it if the fields are inconsistent, which is why the corrupted file fails.openssl asn1parsedoes no such validation - it walks the ASN.1 and prints each integer, so you can recover every field that was not damaged. The corruption in this challenge zeroes or scrambles a small number of the nine fields; the rest are exact.This is pure key reconstruction from RSA's internal relationships - no lattices, no Coppersmith, no brute force. Knowing
nplus one prime (orn,e, andd) is enough to derive everything. - Step 2Recompute the missing field arithmeticallyUse the intact fields to recompute the corrupted one. If a prime is missing but n and the other prime survive, the missing prime is just n // p. If d is corrupted, recompute d = e^-1 mod (p-1)(q-1). The CRT fields follow: dp = d mod (p-1), dq = d mod (q-1), qinv = q^-1 mod p. Pick whichever relation closes the gap left by the corrupted field.python
python3 -c "bashfrom Crypto.Util.number import inversebash# Fill in the values you read from openssl asn1parse.bashn = <n>; e = <e>bashp = <intact_prime_p> # one prime survivedbashq = n // p # recover the other primebashassert p * q == n # sanity checkbashphi = (p - 1) * (q - 1)bashd = inverse(e, phi) # recover the private exponentbashdp = d % (p - 1)bashdq = d % (q - 1)bashqinv = inverse(q, p)pythonprint('p', p); print('q', q); print('d', d)pythonprint('dp', dp); print('dq', dq); print('qinv', qinv)bash"Learn more
The RSA fields are bound together by exact equations, so any one of them is reconstructible from the others:
q = n / p(exact integer division - verify withp * q == n)d = e^-1 mod (p-1)(q-1)(modular inverse / extended Euclid)dp = d mod (p-1),dq = d mod (q-1)qinv = q^-1 mod p
Whichever field the challenge zeroed, one of these formulas regenerates it. There is no search and no approximation: the survivors fully determine the missing value. The
inverse()helper fromCrypto.Util.numbercomputes the modular inverses with the extended Euclidean algorithm. - Step 3Rebuild the key and decryptWith every field recovered, reconstruct a valid RSA key object and decrypt the provided ciphertext (or simply re-export a repaired PEM if that is what the challenge asks for). The flag falls out of the decryption.python
python3 -c "bashfrom Crypto.PublicKey import RSAbashfrom Crypto.Util.number import inverse, long_to_bytesbashn = <n>; e = <e>; p = <p>bashq = n // pbashassert p * q == nbashd = inverse(e, (p - 1) * (q - 1))bashkey = RSA.construct((n, e, d, p, q))bash# Re-export the repaired key, or decrypt the ciphertext:bashopen('fixed.pem','wb').write(key.export_key())bashc = <ciphertext_int>bashm = pow(c, d, n)pythonprint(long_to_bytes(m))bash"Learn more
RSA.construct((n, e, d, p, q))from pycryptodome rebuilds a complete, consistent key object (it recomputes the CRT parameters internally), so you can either export a clean PEM or decrypt right away withpow(c, d, n).The lesson: RSA private key material is massively redundant. Knowing
nand a single prime - orn,e, andd- is enough to reconstruct the entire key, which is exactly why a "partially" leaked private key should be treated as fully compromised.
Flag
picoCTF{...}
Parse the PEM with openssl asn1parse to read the intact ASN.1 integers, then recompute the corrupted field from RSA's internal relations (q = n // p, d = e^-1 mod phi, dp/dq/qinv), rebuild the key, and decrypt. No lattice or Coppersmith needed.