[CTF] WU - RSA101
CTF InterCampus Ynov 2024
Difficulty Level : Insane
Challenge Category : Crypto
Description :
During my work at the company I have collected 100 public keys and 100 encrypted texts. Can you help me decrypt them?
Solution Steps
Step 1: Understanding the Attack
RSA keys are defined by a modulus ( n = p \times q ), where ( p ) and ( q ) are prime numbers.
If two keys share a common prime factor ( p ), their moduli ( n_1 ) and ( n_2 ) are not coprime, i.e., ( \text{GCD}(n_1, n_2) = p ).
This shared prime can be used to:
- Factorize ( n_1 ) and ( n_2 ).
- Compute private keys for each RSA key.
- Decrypt encrypted messages.
Step 2: Steps to Solve
- Extract Public Keys:
- Read the 100 public keys from the provided PEM files and extract ( n ) and ( e ).
- Find Shared Factors:
- Calculate the GCD for all pairs of ( n ) values to find shared prime factors.
- Factorize ( n ):
- For ( n_1 = p \times q ), where ( p ) is the shared factor, compute ( q = n_1 / p ).
- Compute Private Key:
- Use ( \phi = (p-1) \times (q-1) ) and ( e ) to compute ( d ) using the modular inverse ( d = e^{-1} \mod \phi ).
- Decrypt Messages:
- Use ( d ) and ( n ) to decrypt each ciphertext.
Step 3: Python Script
Key Functions:
-
Load Public Keys:
from Crypto.PublicKey import RSA def load_public_key(file_path): with open(file_path, "rb") as f: return RSA.import_key(f.read())
-
Find Shared Factors:
from Crypto.Util.number import GCD def find_shared_factor(n1, n2): return GCD(n1, n2)
-
Compute Private Key:
from Crypto.Util.number import inverse def compute_private_key(n, e, p, q): phi = (p - 1) * (q - 1) return inverse(e, phi)
-
Decrypt Messages:
def decrypt_rsa(ciphertext, n, d): return pow(ciphertext, d, n)
Full Script:
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse, long_to_bytes, GCD
import os
def load_public_key(file_path):
with open(file_path, "rb") as f:
pem_data = f.read()
public_key = RSA.import_key(pem_data)
return public_key
def load_public_keys_from_folder(folder_path):
public_keys = []
for i in range(100):
public_key = load_public_key(f"{folder_path}/public_key_{i}.pem")
public_keys.append(public_key)
return public_keys
def get_n_and_e(public_key):
return public_key.n, public_key.e
def find_shared_factor(n1, n2):
return GCD(n1, n2)
def compute_private_key(n, e, p, q):
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
return d
def decrypt_rsa(ciphertext, n, d):
return pow(ciphertext, d, n)
public_keys = load_public_keys_from_folder("keys")
ns = [get_n_and_e(key)[0] for key in public_keys]
es = [get_n_and_e(key)[1] for key in public_keys]
shared_factors = []
for i in range(len(ns)):
for j in range(i + 1, len(ns)):
shared_factor = find_shared_factor(ns[i], ns[j])
if shared_factor > 1:
shared_factors.append(shared_factor)
private_keys = []
for shared_factor in shared_factors:
for n in ns:
if n % shared_factor == 0:
p = shared_factor
q = n // p
for e in es:
d = compute_private_key(n, e, p, q)
private_keys.append({'n': n, 'd': d})
try:
with open("encrypted_flags.txt", "r") as f:
lines = f.readlines()
print("Contenu du fichier encrypted_flags.txt :")
for line in lines:
print(line.strip())
if len(lines) < 2:
raise ValueError("Le fichier ne contient pas assez de lignes. Assurez-vous qu'il y a une ligne d'en-tête et une ligne avec les valeurs chiffrées.")
encrypted_flags_line = lines[1].strip()
encrypted_flags_line = encrypted_flags_line[1:-1]
encrypted_flags = encrypted_flags_line.split(", ")
encrypted_flags = [int(flag) for flag in encrypted_flags]
except Exception as e:
print(f"Erreur lors de la lecture ou du traitement du fichier: {e}")
encrypted_flags = []
if encrypted_flags and private_keys:
print("Messages chiffrés trouvés :", encrypted_flags)
for encrypted_flag in encrypted_flags:
for private_key in private_keys:
n = private_key['n']
d = private_key['d']
decrypted_flag = decrypt_rsa(encrypted_flag, n, d)
decrypted_flag_bytes = long_to_bytes(decrypted_flag)
try:
print(f"Decrypted flag with n = {n}: {decrypted_flag_bytes.decode(errors='ignore')}")
except UnicodeDecodeError:
print(f"Decrypted flag with n = {n} (bytes): {decrypted_flag_bytes}")
else:
print("Aucun message chiffré trouvé ou problème avec le fichier.")
