Solve picoCTF's RSA Challenge Sum-O-Primes
By chance, I came across a picoCTF RSA challenge called Sum-O-Primes. This problem is not difficult, you can do it by knowing the basics of the RSA algorithm. In addition, if you are familiar with the history of the evolution of the RSA algorithm, you can find a second ingenious fast solution.
picoCTF Project
picoCTF is a free computer security education program created by security and privacy experts at Carnegie Mellon University. It uses original content built on the CTF (Capture the Flag) framework to provide a variety of challenges. It provides participants with valuable opportunities to systematically learn cybersecurity knowledge and gain practical experience.
The collection of practice questions for picoCTF is called picoGym. The general problem solution is to search or decipher a string in the format "picoCTF{...}" from the given information, that is, the flag to be captured. As shown in the figure below, picoGym currently contains 271 cybersecurity challenge exercises, covering general skills, cryptography, reverse engineering, forensics, and other fields.
Sum-O-Primes Challenge
There are 50 cryptography-related challenges in picoGym, one of which is Sum-O-Primes. The task of this challenge is simple and explained as follows:
We have so much faith in RSA we give you not just the product of the primes, but their sum as well!
That is, we not only give the product of the two prime numbers used by RSA but also tell you their sum. How are these given? You need to discover by yourself from the rest of the information. After clicking the two links and downloading the file, open the first Python file:
1 | #!/usr/bin/python |
If you have basic Python programming skills and understand the principles of the RSA algorithm, you should be able to read the above program quickly. What it does is:
- Open the file
flag.txt
to read the content. Then use thehexlify
andint
functions to convert it to an integer and store the result in a variableFLAG
. - Call the function
get_prime
to generate two prime numbers, store their sum inx
and their product inn
. Then assign 65537 toe
and calculate the RSA private exponentd
. - Use standard
pow
functions to perform modular exponentiation, which implements RSA encryption to encrypt plaintextFLAG
into ciphertextc
. - Print out
x
,n
, andc
.
Open the second file, which is apparently the output of the first program in Python:
1 | x = 154ee809a4dc337290e6a4996e0717dd938160d6abfb651736d9f5d524812a659b310ad1f221196ee8ab187fa746a1b488a4079cddfc5db08e78be0d96c83c01e9bb42420b40d6f0ad9f220633459a6dc058bb01c517386bfbd2d4811c9b08558b0e05534768581a74884758d15e15b4ef0dbd6a338bf1f52eed4f137957737d2 |
Once you understand the meaning of the question, you can make a judgment immediately —— if you can decrypt the ciphertext c
and retrieve the plaintext FLAG, you can get the original content of flag.txt
, that is, capture the flag.
Conventional Solution
RSA decryption requires a private key exponent d
. Referring to the steps of the RSA algorithm below, it is obvious that this demands integer factorization for large prime numbers p
and q
first.
- Choose two large prime numbers \(p\) and \(q\), compute \(n=pq\)
- Compute Carmichael function \(\lambda(n)=\operatorname{lcm}(p − 1, q − 1)\) the product, \(\operatorname{lcm}\) is a function to find the least common multiple
- Choose any number \(e\) that is less than and coprime to \(\lambda(n)\), then compute \(d\), the modular multiplicative inverse of \(e\) regarding \(\lambda(n)\), \(d\equiv e^{-1}\pmod {\lambda(n)}\)
- \((n,e)\) is the RSA public key, \((n,d)\) the RSA private key
- Use the public key to encrypt the plaintext \(m\), the formula is \(c\equiv m^e\pmod n\)
- Use the private key to decrypt the ciphertext \(c\), the formula is \(m\equiv c^d\pmod n\)
From here, the challenge becomes a problem that, knowing the sum and product of two large prime numbers known, find these two large prime numbers. That is, to solve a system of quadratic linear equations
\[ \left\{ \begin{aligned} p+q &=n \\ p*q &=x \end{aligned} \right. \]
Using the knowledge of elementary mathematics, the above equations can be transformed into a quadratic equation \[p^2 - x * p + n = 0\]
Obviously, \(p\) and \(q\) are its two roots. According to the quadratic formula
\[(p,q)={\frac {x}{2}}\pm {\sqrt {\left({\frac {x}{2}}\right)^{2}-n}}\]
We can get \(p\) and \(q\). The rest of the work is easy. The code to compute \(d\) from \(p\) and \(q\) can be copied directly from lines 28, 30, and 31 in gen.py. The final complete Python problem-solving code is as follows:
1 | import math |
The above program defines a general function solve_rsa_primes
to solve two large prime numbers. After it gets d
, the same pow
function is called to decrypt, and finally the plaintext is converted from a large integer to a byte sequence and printed out. The result of running this program is
1 | b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}' |
BINGO! Capture the Flag successfully!
Note: The function solve_rsa_primes
calls math.isqrt
to compute the integer square root of the given integer. This is indispensable! If it is written incorrectly with math.sqrt
, the following overflow error will occur
1 | >>> |
This error happens because math.sqrt
uses floating-point arithmetic but fails to convert large integers to floating-point numbers.
Quick Solution
The conventional solution to this problem has to solve a quadratic equation, so the integer square root operation is essential. Is there a solution that doesn't need a square root operation? The answer is yes.
In the original RSA paper, the public exponent \(e\) and the private exponent \(d\) have the relationship as the following equation
\[d⋅e≡1\pmod{\varphi(n)}\]
Here the modular is the Euler's totient function \(\varphi(n)=(p-1)(q-1)\). Since \(\varphi(N)\) is always divisible by \(\lambda(n)\), any d
satisfying the above also satisfies \(d⋅e≡1\pmod{\lambda(n)}\), thus the private exponent is not unique. Although the calculated \(d>\lambda(n)\), the square root operation can be avoided when applied to the Sum-O-Primes problem. This is because \[
\begin{aligned}
\varphi(n)&=(p-1)(q-1)\\
&=pq-(p+q)+1\\
&=n-x+1
\end{aligned}
\]
Hereby the formula for computing the private exponent becomes
\[ \begin{aligned} d&≡e^{-1}\pmod{\varphi(n)}\\ &≡e^{-1}\pmod{(n-x+1)} \end{aligned} \]
Now that \(n\) and \(x\) are readily available, this method does not require finding \(p\) and \(q\) first, and naturally, there is no need for a square root operation. The Python code for this new solution is very concise
1 | d1 = pow(e, -1, n - x + 1) |
To compare these two solutions, 4 lines of print and assert statements are added at the end. The execution result of this code is
1 | >>> |
As shown above, this solution also succeeds in capturing the flag. The \(d\) value (d1
) calculated by the new solution is more than 7 times that of the conventional solution.
Click here to download all the code of this article: Sum-O-Primes.py.gz