1
Published on August 1, 2025
1
Dikasih file public.pem sama encrypted. Dari script Python yang dikasih, kita
bisa liat kalo flag-nya dienkripsi pake RSA, tapi nggak cuma sekali, tapi dua kali.
Jadi, kita harus ngebuka gemboknya satu-satu
Lapisan2 Lapisan enkripsi kedua ini pake RSA standar, tapi ada kelemahan fatalnya. Modulus n (yang ada di public.pem) itu hasil perkalian dari dua bilangan prima, p dan q. Nah, masalahnya, si q ini cuma bilangan prima 16-bit.
Artinya, kita bisa dengan gampang nemuin q dengan cara brute-force, yaitu
nyobain semua bilangan prima di bawah 2^16 (65536) sampe ketemu satu yang
bisa ngebagi n.
Langkah-langkahnya gini:
- Ambil n dari
public.pem: Kita baca public key-nya buat dapetin nilai n. - Cari q: Kita bikin looping buat nyobain semua bilangan prima dari 2 sampe
- Kalo
n % q_candidate == 0, berarti itu q-nya. - Hitung p: Kalo q udah ketemu, p tinggal diitung pake
p = n / q. - Bikin Private Key Palsu: Dengan p dan q, kita bisa ngitung phi dan d, terus kita bisa bikin ulang private key buat lapisan kedua ini.
- Dekripsi File encrypted: Pake private key yang baru kita buat, kita dekripsi
file encrypted. Hasilnya bukan flag, tapi ciphertext dari lapisan pertama.
Kita sebut aja ini
ct_intermediate.
Lapisan1 ini bagian yang sedikit lebih tricky. Ciphertext yang kita dapet dari tahap sebelumnya (ct_intermediate) itu dienkripsi pake kunci RSA yang beda lagi. Kuncinya ini dibikin pake proses yang unik di script aslinya.
Kuncinya ada di sini: bilangan prima p yang kita temuin di lapisan kedua itu ternyata adalah nextprime(n_layer1), di mana n_layer1 adalah modulus yang dipake di lapisan enkripsi pertama.
Jadi, alurnya dibalik:
-
Cari n_layer1: Karena kita udah tau p, berarti n_layer1 adalah bilangan sebelum p. Kita bisa pake fungsi
prevprime()buat nyari kandidatn_layer1dengan cara mundur dari p. -
Verifikasi Kandidat n: Nggak semua bilangan sebelum p itu
n_layer1yang bener. Kita harus verifikasi. Caranya? Kita inget lagi kalon_layer1itu hasil perkalian berantai dari beberapa bilangan prima (p_start, nextprime(nextprime(p_start)), dst). -
Temukan p_start: Buat verifikasi, kita harus nemuin p_start-nya. Daripada nyari buta, kita bisa bikin aproksimasi/perkiraan p_start dengan cara ngitung akar pangkat sekian (misal pangkat 17) dari
n_candidate. Hasilnya bakal deket banget sama p_start yang asli. Kita tinggal cari di sekitar angka itu. -
Rekonstruksi & Validasi: Kalo p_start yang pas udah ketemu, kita simulasiin lagi proses perkalian berantainya. Kalo hasil akhirnya sama dengan n_candidate kita, berarti BINGO! Kita nemu
n_layer1danphi_layer1yang bener. -
Dekripsi Terakhir: Sama kayak sebelumnya, kalo n dan phi udah ada, kita bisa itung d buat lapisan pertama ini. Pake kunci ini, kita dekripsi ct_intermediate dan… dapet deh flag-nya
Berikut solper nya
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.number import inverse, bytes_to_long, long_to_bytes, isPrime
from sympy import nextprime, primerange
from decimal import Decimal, getcontext
def solve_layer2():
print("[+] Starting Layer 2 decryption...")
try:
with open('public.pem', 'rb') as f:
pubkey_pem = f.read()
pubkey = RSA.import_key(pubkey_pem)
n_rsa = pubkey.n
e_rsa = pubkey.e
with open('encrypted', 'rb') as f:
encrypted_data = f.read()
except FileNotFoundError as e:
print(f"[!] Error: {e}. Make sure 'public.pem' and 'encrypted' are in the same directory.")
return None, None
print(f" [-] Layer 2 n_rsa (modulus) loaded.")
print(f" [-] Layer 2 e_rsa (public exponent) = {e_rsa}")
print(" [-] Searching for the 16-bit prime factor 'q'...")
found_q = None
for q_candidate in primerange(2, 2**16):
if n_rsa % q_candidate == 0:
found_q = q_candidate
break
if not found_q:
print("[!] Failed to find the 16-bit prime factor q. Exiting.")
return None, None
q = found_q
p = n_rsa // q
print(f" [-] Found 16-bit prime q = {q}")
print(f" [-] Calculated large prime p.")
# Verify that p is indeed prime, as a sanity check.
if not isPrime(p):
print("[!] Calculated p is not prime. Something is wrong. Exiting.")
return None, None
print(" [-] Verified that p is prime.")
# Reconstruct the private key for layer 2
phi_rsa = (p - 1) * (q - 1)
d_rsa = inverse(e_rsa, phi_rsa)
private_key_layer2 = RSA.construct((n_rsa, e_rsa, d_rsa, p, q))
print(" [-] Reconstructed Layer 2 private key.")
cipher_decrypt = PKCS1_OAEP.new(private_key_layer2)
key_size = private_key_layer2.size_in_bytes()
encrypted_chunks = [encrypted_data[i:i + key_size] for i in range(0, len(encrypted_data), key_size)]
try:
decrypted_chunks = [cipher_decrypt.decrypt(chunk) for chunk in encrypted_chunks]
decrypted_data = b''.join(decrypted_chunks)
except ValueError as e:
print(f"[!] Decryption failed. This may happen if the factored primes are incorrect. Error: {e}")
return None, None
ct_intermediate = bytes_to_long(decrypted_data)
print("[+] Layer 2 decryption successful!")
return ct_intermediate, p
def solve_layer1(ct_intermediate, p_for_key):
"""
Recreates the large 'n' and 'phi' from the first encryption layer by
reverse-engineering the starting prime from the p recovered from layer 2.
"""
print("\n[+] Starting Layer 1 decryption...")
print(" [-] Using the large prime p recovered from Layer 2 as our target.")
getcontext().prec = 3000
p_for_key_dec = Decimal(p_for_key)
for num_primes_guess in [17, 16, 18]:
p_start_approx = int(p_for_key_dec ** (Decimal(1)/Decimal(num_primes_guess)))
print(f"\n [-] Approximating based on {num_primes_guess} primes...")
print(f" [-] Approximate p_start is near {p_start_approx}")
print(f" [-] Searching for the correct 512-bit p_start around this value...")
for i in range(-10000, 10000):
p_start_cand = p_start_approx + i
if p_start_cand.bit_length() != 512 or not isPrime(p_start_cand):
continue
p = p_start_cand
n_candidate = 1
phi_candidate = 1
while True:
n_candidate *= p
phi_candidate *= (p - 1)
if n_candidate.bit_length() >= 8192:
break
p = nextprime(nextprime(p))
# Now, check if the n we generated is the correct one.
# The correct n is the one for which nextprime(n) equals the p we recovered.
if nextprime(n_candidate) == p_for_key:
print(f"\n [+] SUCCESS! Found correct p_start: {p_start_cand}")
print(f" [+] Verified correct n_layer1 with bit length {n_candidate.bit_length()}")
n_layer1 = n_candidate
phi_layer1 = phi_candidate
# With n_layer1 and phi_layer1, we can decrypt the first layer.
e_layer1 = 65537
print(" [-] Calculating private exponent d for Layer 1...")
d_layer1 = inverse(e_layer1, phi_layer1)
print(" [-] Decrypting final layer to get the flag...")
ct_original = pow(ct_intermediate, d_layer1, n_layer1)
flag = long_to_bytes(ct_original)
print("\n[+] DECRYPTION COMPLETE!")
try:
print(f" [+] FLAG: {flag.decode()}")
except UnicodeDecodeError:
print(f" [+] FLAG (raw bytes): {flag}")
return
print("\n[!] Search for p_start failed. The approximation or search range may be off.")
print(" Cannot proceed to decrypt Layer 1.")
def main():
ct_intermediate, p_for_key = solve_layer2()
if ct_intermediate is not None and p_for_key is not None:
solve_layer1(ct_intermediate, p_for_key)
if __name__ == "__main__":
main()