Deeply Suspicious
Challenge Description
In this challenge, titled "Deeply Suspicious," we are tasked by the government to decrypt a criminal transmission. We're provided with an RSA-encrypted ciphertext, a public key modulus n, the public exponent e = 65537 (0x10001), and a mysterious "leak" described as factorial_mod(p-1,n). The goal is to use this information to recover the original flag.
Challenge.py
import os
from secrets import leak, p, q
from Crypto.Util.number import bytes_to_long
flag=bytes_to_long(os.getenv("FLAG","FLAG{REDACTED}").encode())
n=p*q
e=0x10001
def factorial_mod(a,n):
res=1
for i in range(1,a+1):
res=(res*i)%n
return res
print("One of the secret agents successfully acquired a leak containing sensitive information about criminal activities. However, upon inspection, the obtained data appeared to be of little use.")
print(f"the only thing is known is that factorial_mod(p-1,n)={leak}")
print("We need your expertise to decrypt the data obtained from the criminals' communications and assist us in apprehending them")
print(f"Here is the encrypted data: {pow(flag,e,n)}")
print(f"pubkey: {n}")Given Data
Leak:
factorial_mod(p-1,n) = 22824927568379076930982500035833884888382288289321794400021843661854179808056790322611312747824856598317400953128967870383782759041725450527145280171952983690759348592583081704547466159880771277921341435428233285028150373588149270776449864642195802606252354556577400353001301003517363152170541523074902666086Ciphertext:
6164227335261046013236133009909193595338315991408774765844207109667187128915928980465996058597680704885601362465622012957220331289778975746969084069598993910184561096159841091098186778344646957627998780794592927833051360671416450369393631341830674299813661915292150200182351514371133502253012040862595999782Public Key (n):
109110357740395944076150767016138593567154539844190870651264490180155550490385760166670247677139233067935806231311964850237711104674357706756748405406629009552898220389946539311072284542148083881745639338165914017695425520257578789239678662975735012984867520845825985983850506881070472665206613452398692599947Public Exponent (e):
65537(0x10001)
Analysis
The challenge provides a standard RSA setup where:
n = p * q(product of two primes)The ciphertext is
c = pow(m, e, n)(flag encrypted with the public key)We need to find the private key
dto decrypt:m = pow(c, d, n)
The key piece of information is the "leak": factorial_mod(p-1,n) = (p-1)! mod n. At first glance, this seems unhelpful, but it turns out to be the critical weakness in the encryption.
From the source code:
def factorial_mod(a,n):
res=1
for i in range(1,a+1):
res=(res*i)%n
return res
leak = factorial_mod(p-1,n)This computes (p-1)! mod n, where p is one of the prime factors of n.
Key Insight
Since n = p * q, and p and q are distinct primes, we know:
(p-1)! = 1 * 2 * ... * (p-1)When computed modulo
n, this is equivalent to(p-1)! mod p * (p-1)! mod qdue to the Chinese Remainder Theorem.
By Wilson's Theorem, for a prime p:
(p-1)! ≡ -1 (mod p)Thus,
leak = (p-1)! mod n ≡ -1 (mod p)
Since -1 mod p = p-1, we have:
leak ≡ p-1 (mod p)Therefore,
leak + 1 ≡ p (mod p), meaningleak + 1is a multiple ofp.
Because n = p * q, if we compute gcd(leak + 1, n), it will reveal p (assuming leak + 1 isn't also a multiple of q, which is unlikely given the size of the numbers).
Solution
We can factor n using the leak and then perform standard RSA decryption.
Steps
Factorize
n:Compute
p = gcd(leak + 1, n)Compute
q = n // p
Compute
phi:phi = (p-1) * (q-1)
Compute private key
d:d = inverse(e, phi)
Decrypt the ciphertext:
m = pow(c, d, n)Convert
mto bytes to get the flag
Solver Code
from Crypto.Util.number import inverse, long_to_bytes
import math
leak = 22824927568379076930982500035833884888382288289321794400021843661854179808056790322611312747824856598317400953128967870383782759041725450527145280171952983690759348592583081704547466159880771277921341435428233285028150373588149270776449864642195802606252354556577400353001301003517363152170541523074902666086
ciphertext = 6164227335261046013236133009909193595338315991408774765844207109667187128915928980465996058597680704885601362465622012957220331289778975746969084069598993910184561096159841091098186778344646957627998780794592927833051360671416450369393631341830674299813661915292150200182351514371133502253012040862595999782
n = 109110357740395944076150767016138593567154539844190870651264490180155550490385760166670247677139233067935806231311964850237711104674357706756748405406629009552898220389946539311072284542148083881745639338165914017695425520257578789239678662975735012984867520845825985983850506881070472665206613452398692599947
e = 65537
# Factorize n using the leak
p = math.gcd(leak + 1, n)
q = n // p
# Calculate phi
phi = (p - 1) * (q - 1)
# Calculate private key d
d = inverse(e, phi)
# Decrypt
flag = pow(ciphertext, d, n)
flag = long_to_bytes(flag)
print("Flag:", flag.decode())
# Flag: flag{6nUtkiIHlqyxcQ1rZkyOt3labVrR1l4T}Conclusion
The challenge exploits a clever weakness in RSA by leaking (p-1)! mod n. By recognizing that leak + 1 shares a factor with n, we can efficiently factorize the modulus and recover the private key. This is a classic example of how seemingly obscure mathematical properties (like Wilson's Theorem) can break cryptographic systems when side information is leaked.
Last updated
Was this helpful?
