0CTF 2016 Quals – RSA? (Crypto 2 pts)

It seems easy, right?
Tip: openssl rsautl -encrypt -in FLAG -inkey public.pem -pubin -out flag.enc

Summary: factoring 300-bit modulus into 3 primes, extracting cube roots.

$ openssl rsa -in public.pem -pubin -text
Public-Key: (314 bit)
Exponent: 3 (0x3)
writing RSA key
-----END PUBLIC KEY-----

314 bits only! Let’s factor it! For example, using CADO-NFS. It takes only 30 minutes on my laptop.

Interestingly, there are three factors:

N = 32581479300404876772405716877547 x 27038194053540661979045656526063 x 26440615366395242196516853423447.

Moreover, $gcd(3, \phi(N)) = 3$, meaning that we can’t invert the decryption! We have to compute all possible cube roots of the ciphertext. All such algorithms work modulo prime, but that is not a problem, since we have the factorization: we will compute all cube roots modulo each prime and then use Chinese Remainder Theorem to reconstruct the answer modulo $N$. There are some algorithms described in the following paper: New Cube Root Algorithm Based on Third Order Linear Recurrence Relation in Finite Field. The shortest one is the Cipolla-Lehmer algorithm, so I decided to implement it. Here’s Sage function:

def cube_root(a, q):
    F = GF(q)
    R.<x> = PolynomialRing(F)
    while 1:
        a = F.random_element()
        b = F.random_element()
        fx = x**3 - a*x**2 + b*x - c
        fc = list(factor(fx))
        if len(fc) <= 1:
            root = pow(x, (q**2+q+1)/3, fx)
            root %= x
            return int(root)

Note that it yields some cube root. We need to get all of them (at most 3) modulo each prime. We can either extract random cube roots with cube_root until we get all of them, or we can simply multiply a single root by 3rd roots of unity. We can get these roots by finding a multiplicative generator and raising it to power $(p-1)/3$.

Here’s the code:

p = 26440615366395242196516853423447
q = 27038194053540661979045656526063
r = 32581479300404876772405716877547
c = int(open("flag.enc").read().encode("hex"), 16)
mods = [p, q, r]
rems = []
for mod in mods:
    if gcd(mod - 1, 3) == 1:
        d = inverse_mod(3, mod - 1)
        rems[-1].append( int(pow(c, d, mod)) )
        g = GF(mod).multiplicative_generator()
        u = int(g ** ((mod-1)/3))
        r1 = int(cube_root(c, mod))
        for i in xrange(3):
            rems[-1].append( int(r1 * pow(u, i, mod) % mod) )
    print rems[-1]
from itertools import product
for testrems in product(*rems):
    m = crt(list(testrems), mods)
    assert pow(m, 3, p*q*r) == c % (p*q*r)
    h = hex(m)
    if len(h) & 1:
        h = "0" + h
    print `h.decode("hex")`

And result:

[13374868592866626517389128266735L, 7379361747422713811654086477766L, 5686385026105901867473638678946L]
[13028011585706956936052628027629L, 6149264605288583791069539134541L, 13404203109409336045283549715377L]
"\x02BRD:\xd703\xf3\xbe\xc7\xf4\x85\xc7\x88nf\x9a\xc9'f\xbb\xb4\xd3\x1b\xeb\x0e\xe9\x04\x0f\x15\xad f\xaa\x02y\x04\x04\xfc"

Don’t miss the flag! 0ctf{HahA!Thi5_1s_n0T_rSa~}

Leave a Reply

Your email address will not be published.