The difficulty of solving this problem is directly proportional to the number of possible primes that must be tested. The more possibilities the harder it is to find the right pair. This entropy, in turn, is proportional to the bit length of the modulus. In 2019, a team of researchers factored a 795-bit RSA key, making it the biggest key size to be broken at the time. A year later, researchers factorized an 829-bit RSA key. To date, there are no confirmed cases of 1024-bit RSA being factorized, but that doesn't mean it can't be done.
“The only barrier for [factorizing 1024-bit RSA] in public is some engineering effort and funding, and finding a large organization willing to put up enough computing power,” Nadia Heninger, a University of California at San Diego professor specializing in cryptography, wrote in an email. “It could have been done years ago if people wanted to—there's no technical barrier, and multiple large companies certainly have the computing resources.”
Anticipating the inevitable fall of 1024-bit RSA, the US National Institute of Standards and Technology stopped allowing its use in 2013 and will stop allowing the use of 2048-bit RSA in 2031. Microsoft earlier this year announced the deprecation of 1024-bit RSA in Windows.
Castillucci’s task was made more difficult because they didn’t have access to the public key securing the admin account. Instead they had only a JWT—short for a JSON Web Token—that was signed by the key. Castellucci explained how they overcame the limitation:
RSA needs three values to work, the modulus n, the private exponent d, and the public exponent e. An RSA signature is computed as s = md mod n — message m is raised to the power of d modulo n. It’s validated by checking that se mod n ≡ m. With the prime factors of n, it’s trivial to calculate d, and for a 512 bit key finding the prime factors is doable, but I didn’t have n or e. By convention, e is nearly always 65537, but I had no idea what n was. I do, however, know algebra.
Subtracting m from both sides of the signature verification equation gives se mod n − m ≡ 0. Since modular subtraction is associative, that also means that se − m mod n ≡ 0. The modulo operation finds the remainder, so se − m is an integer multiple of n. This is not useful on its own, but it means that with another message and signature, I’d have two different integer multiples of n. Running those through a GCD algorithm would give me n × x where x is a small integer, easily factored out by trial division.
This Isn’t Textbook RSA
The math above only covers “Textbook RSA” operating on raw numbers, which has a number of problems in practice. It can only operate on numbers smaller than the key’s modulus. The numbers also can’t be too small, otherwise various attacks are possible. To address this, the message is hashed and padded using PKCS #1 v1.5 encoding before being signed. Not wanting to deal with the encoding, I went looking for a pre-existing tool. After a few false starts, I found JWT-Key-Recovery, which quickly provided the modulus.
Cracking the Key
The modulus is generated by picking large prime numbers, usually denoted p and q, and multiplying them together. If you want more detail, please see my previous post, Artisanal RSA. The most efficient known algorithm for factoring the modulus back into primes is called general number field sieve (GNFS). This wasn’t my first time cracking an RSA key, but it’d been a while, so I found some instructions. I started cado-nfs on my workstation and let it run overnight. By the time I got done with work the next day, I was feeling impatient and rented a few hundred CPU cores to make it go faster. A few hours and a $70 compute bill later, I had the two prime numbers I needed.
Once in possession of the two primes, the researcher used a custom tool to generate the private key and then signed the JWT provided with a demo account they were using. With a few additional steps, Castellucci had transformed the token to give the same access admins inside GivEnergy had.