::::::::::::::::::::::::::::
:: rasti's knowledge cave ::
::::::::::::::::::::::::::::
nothing fancy yet

(Crypto) TSJ CTF 2022 - babyRSA

Created at: 06/03/2025, 23:26:11

.: #ctf-writeups, #crypto :.
Table of Contents
CategoryCrypto
DifficultyMedium
CTFTSJ CTF
Year2022
Challenge Authormaple3142

Description

Synopsis

The point CC belongs to the curve so we create two multivariate polynomials, eliminate one variable using resultants and run small_roots() to obtain qq. Having factored NN, construct the curve in the composite ring Z/nZ\mathbb{Z}/n\mathbb{Z} and find the base point by calculating CC with the inverse of ee modulo the curve’s order.

Source files

You can download the source files from the original CTF repository.

Overview

Let’s first analyze the main script of the challenge; that is challenge.sage.

from Crypto.Util.number import *
import os

proof.arithmetic(False)  # to make sage faster

flag = b"TSJ{not_real_flag}"

p = getPrime(1024)
q = getPrime(512)
n = p * q
e = 65537
E = EllipticCurve(Zmod(n), [p, q])

while True:
    x = ZZ(bytes_to_long(flag + os.urandom(192 - len(flag))))
    try:
        yp = ZZ(E.change_ring(GF(p)).lift_x(x).xy()[1])
        yq = ZZ(E.change_ring(GF(q)).lift_x(x).xy()[1])
        y = crt([yp, yq], [p, q])
        break
    except:
        pass

C = e * E(x, y)
print(n)
print(C.xy())

At first glance, we see there is a standard RSA key generation, i.e. the secret primes p,qp, q, the modulo N=pqN = p*q and the public exponent e=65537e = 65537. Then, an elliptic curve E\mathbb{E} is defined over the composite ring Z/nZ\mathbb{Z}/n\mathbb{Z} with parameters a=pa = p and b=qb = q.

We can describe this curve with the following algebraic relationship:

y2x3+px+q(modN) y^2 \equiv x^3 + px + q \pmod N

The flag is randomly padded and stored in the variable xx. After that, two points with the xx-coordinate are calculated:

  • (x, yp)(x,\ y_p)

    This is a point of the curve that is defined over GF(p)GF(p); say Ep\mathbb{E}_p.

    yp2x3+q(modp)y_p^2 \equiv x^3 + q \pmod {p}

    Note that pxpx is a multiple of pp so it is eliminated (modp)\pmod p.

  • (x, yq)(x,\ y_q)

    This is a point of the curve that is defined over GF(q)GF(q); say Eq\mathbb{E}_q.

    yq2x3+px(modq)y_q^2 \equiv x^3 + px \pmod {q}

    Note that qq is eliminated (modq)\pmod q.

What the function E.change_ring() basically does is changing the ring in which the curve E\mathbb{E} is defined.

Then, ypy_p and yqy_q are combined with the Chinese Remainder Theorem to get the yy-coordinate that belongs to En\mathbb{E_n}.

To summarize, this loop does the same job as the Sage method E.lift_x(x). The reason we can’t use this method now is due to En\mathbb{E}_n being defined over a composite ring and lift_x would try to factor NN to lift the point in Ep\mathbb{E}_p and Eq\mathbb{E}_q, which is a very hard problem since the prime generation is secure. Therefore we conclude that the flag is randomly padded until the point (x, y)(x,\ y) is on En\mathbb{E}_n, where xx is the padded flag.

Finally, the encryption is similar to that of RSA but in the additive group:

  • CC is the ciphertext point
  • ee is the scalar
  • G=E(x,y)G = E(x,y) is the point of En\mathbb{E}_n that we want to retrieve

Idea on how to recover the base point and the problem that lies

How does decryption work in RSA? One raises the ciphertext to the multiplicative inverse of ee modulo the order of the multiplicative group Z/nZ\mathbb{Z}/n\mathbb{Z}; that is ϕ(n)\phi(n).

Similarly, from here, we know that to solve for GG we need to multiply CC by the multiplicative inverse of ee modulo the order of En\mathbb{E}_n.

GCe1(modOn)(1) \begin{aligned} G \equiv Ce^{-1} \pmod {O_n}\quad\quad\quad\quad(1) \end{aligned}

where OnO_n is the order of the curve En\mathbb{E}_n.

Pretty straightforward right? … Hmm, not at all.

As aforementioned, En\mathbb{E}_n is defined over a composite ring so knowing its order is as hard as factoring NN. NN is more than 1500 bits long so without a quantum computer, we have no luck here.

Solution

q is half the bit length of p

The key to factor NN is notice that qq is half the bit length of pp. That’s about 13\frac{1}{3} the bit length of NN.

13\dfrac{1}{3}? Where did this come from?

That’s because NN can be written as the product of three 512-bit integers, say a,b,ca,b,c.

N=abcN = a \cdot b \cdot c

Since qq is 512 bits, we are certain that one (1) of these three (3) variables must be qq. This makes qq about 13\frac{1}{3} of NN.

Playing with the curve’s formula

Since GG belongs to En\mathbb{E}_n, so does CC.

We know that any point (x, y)(x,\ y) of En\mathbb{E}_n satisfies the following formula:

y2x3+px+q(modN)y^2 \equiv x^3 + px + q \pmod N

Then substituting with C=(Cx,Cy)C = (C_x, C_y) coordinates we get:

Cy2Cx3+pCx+q(modN)C_y^2 \equiv C_x^3 + p \cdot C_x + q \pmod N

Let’s rewrite the relation above as follows:

Cy2Cx3pCxq0(modN)C_y^2 - C_x^3 - p \cdot C_x - q \equiv 0 \pmod N

We know everything apart from pp and qq but we can’t solve for them because we have one relation and two unknowns. Do we know something else about p,qp,q? Well, from the RSA part we know that N=pqN = p \cdot q and NN is known. That’s great! Two equations and two unknowns so there is a unique solution.

Constructing the polynomials

Let’s define the following polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z}.

f(x, y)=nxyg(x, y)=Cy2Cx3xCxy \begin{aligned} f(x,\ y) &= n - x \cdot y\\ g(x,\ y) &= C_y^2 - C_x^3 - x \cdot C_x - y \end{aligned}

Notice that p,qp, q are both roots of these polynomials:

f(p, q)=npq=0g(p, q)=Cy2Cx3pCxq=0 \begin{aligned} f(p,\ q) &= n - p \cdot q = 0\\ g(p,\ q) &= C_y^2 - C_x^3 - p \cdot C_x - q = 0 \end{aligned}

They are multivariate polynomials but maybe we could eliminate one variable? For example, we could substitute p=Nqp = \dfrac{N}{q} in the gg polynomial. One could substitute with pencil and paper and come up with a univariate polynomial in terms of qq but that’s a lot of work (however, it is recommended as an exercise for beginners!).

We could use our beloved resultant that basically does the same thing. You can find more about resultants from 1, 2 and some cool Joseph writeups.

Let’s use Sage to find the resultant of these polynomials. We will basically find a univariate polynomial in terms of qq only.

P.<x,y> = PolynomialRing(Zmod(n))

f = n - x*y
g = Cy^2 - Cx^3 - x*Cx - y

def resultant(f1, f2, var):
  return Matrix.determinant(f1.sylvester_matrix(f2, var))

h = resultant(f, g, x)  # eliminating x is equivalent to eliminating p

print(h)

The output is:

y^2 + 11913771694063495132568425582147978387779218009404951491138444355803251420750777828581495229803905485508710200822306270492779460893035511452060758726696972877404214806553422280705330092204004616281420566339823476408647786409010040145494297930530259483781466478416269629186356995544407404915722209121439224567698019474188565402069644492370517495662654444038623713130993722823437453577026376201959720791194856979494885237541302217843842247547112767879217639883793*y

We have the polynomial:

h(y)y2+Ay(modN) h(y) \equiv y^2 + Ay \pmod N

where AA is the large integer.

For the correct value of qq it holds that:

h(q)0(modN) h(q) \equiv 0 \pmod N

But what can we do now? This polynomial is defined in Z/nZ\mathbb{Z}/n\mathbb{Z} so we can’t apply standard techniques that work in the integers Z\mathbb{Z}.

Recall that qq is half pp’s bit length. This means that qq is a small root of this polynomial, compared to the size of NN. It turns out we can use Coppersmith’s algorithm to find the roots of the polynomial above. These roots are also known as small roots.

Coppersmith’s algortihm

It might be a bit complex to describe how the algorithm works but the intuition behind is that when we are looking for something small defined over something big, then Coppersmith’s algorithm should do the trick.

This is why we cared about qq being the half pp’s bit length.

Let’s get a bit more technical now. Coppersmith’s method will return the small roots of our polynomial modulo a factor of NN, say pp, without having to factor NN at all. Pretty instance, right? That’s lattices for you!

Why is this so important?

While the equations are defined modulo NN, Coppersmith’s small roots algorithm finds a small root modulo a factor of NN and in our case modulo pp.

Sage’s small_roots() function is an implementation of Coppersmith’s algorithm. However, it requires some parameters:

  • XX

    That’s an upper bound for the small root we are looking for. In our case, qq is at most 25122^{512} so X=2512X = 2^{512}.

  • beta (or β\beta)

    That’s a value such that pNβp \geq N^\beta, where pp is a factor of NN. We know that pn23p \approx n^{\frac{2}{3}} or equivalently pn23p \geq n^{\frac{2}{3}} so:

    β=23=0.666\beta = \dfrac{2}{3} = 0.666\dots

You can check here for more information.

Factoring NN

Now it’s time to factor NN. Let’s run the following code:

roots = h.small_roots(X=2^512, beta=0.66)
print(roots)
[0, 7560550953987228717927043411195097606178780260722416435854220484370855427179572047127883844297336386784419855728350626032040641635456814848906770345908561]

The second root looks like a candidate for qq.

print(N % q == 0)
True

Boom! We have factored NN.

q = int(h.small_roots(X=2^512, beta=0.66)[1])
p = N // q
assert N == p * q
print(f'p = {p}')
print(f'q = {q}')
p = 143466851392554970695990704123817779733897135669358867616227016983904822448652872447294618655211767603232776070689066195417250663712048624942911364907905504389182987025672966027320571640054927174423881068214579019052804849273855736938414398455136976371108924577231955088915205951155616111863602559370112932349
q = 7560550953987228717927043411195097606178780260722416435854220484370855427179572047127883844297336386784419855728350626032040641635456814848906770345908561

Now we have to find OnO_n. Since En\mathbb{E}_n is defined over a composite ring, it holds that:

On=OpOq O_n = O_p \cdot O_q

where OpO_p the order of the curve Ep\mathbb{E}_p and OqO_q the order of the curve Eq\mathbb{E}_q.

Knowning OnO_n, we can compute GG as shown in (1)(1).

Full solve script

from sage.matrix.matrix2 import Matrix
from Crypto.Util.number import long_to_bytes

def resultant(f1, f2, var):
  return Matrix.determinant(f1.sylvester_matrix(f2, var))

Cx = 1079311510414830031139310538989364057627185699077021276018232243092942690870213059161389825534830969580365943449482350229248945906866520819967957236255440270989833744079711900768144840591483525815244585394421988274792758875782239418100536145352175259508289748680619234207733291893262219468921233103016818320457126934347062355978211746913204921678806713434052571635091703300179193823668800062505275903102987517403501907477305095029634601150501028521316347448735695
Cy = 950119069222078086234887613499964523979451201727533569872219684563725731563439980545934017421736344519710579407356386725248959120187745206708940002584577645674737496282710258024067317510208074379116954056479277393224317887065763453906737739693144134777069382325155341867799398498938089764441925428778931400322389280512595265528512337796182736811112959040864126090875929813217718688941914085732678521954674134000433727451972397192521253852342394169735042490836886
N = 1084688440161525456565761297723021343753253859795834242323030221791996428064155741632924019882056914573754134213933081812831553364457966850480783858044755351020146309359045120079375683828540222710035876926280456195986410270835982861232693029200103036191096111928833090012465092747472907628385292492824489792241681880212163064150211815610372913101079146216940331740232522884290993565482822803814551730856710106385508489039042473394392081462669609250933566332939789
e = 65537

P.<p,q> = PolynomialRing(Zmod(N))

f = N - p*q
g = Cy^2 - Cx^3 - p*Cx - q
h = resultant(f, g, p).univariate_polynomial()

q = int(h.small_roots(X=2^512, beta=0.66)[1])
p = N // q

assert N == p * q

E = EllipticCurve(Zmod(N), [p,q])
Ep = EllipticCurve(GF(p), [p,q])
Eq = EllipticCurve(GF(q), [p,q])

On = Ep.order() * Eq.order()

C = E(Cx, Cy)
G = int(pow(e, -1, On)) * C
flag = long_to_bytes(int(G[0]))
flag = re.search(rb'(TSJ{.*})', flag).group(1)

print(flag)

Output:

b"TSJ{i_don't_know_how_to_come_up_with_a_good_flag_sorry}"