Here are the writeups for all of Tux’s challenges, with solution code. I believe we were the only team to max crypto :)
This challenge had an unintended solution, so
sig2 aren’t necessary. The main idea is to use the homormophic properties of RSA to recover
n. Since the server already leaks
p, that’s enough to break RSA.
Consider a message
M and its integer square,
M^2. Then their signatures are
Sign(M) := M^d Sign(M^2) := (M^2)^d == (M^d)^2
In other words, we receive some signature
S', where the latter former squared modulo
n. Notably, if we compute
S^2 ourselves, we have the equations
S^2 - S' == 0 (mod n) S^2 - S' == kn
The one issue that remains is that we send string representations of these numbers, which must be valid unicode. Fortunately, Python will accept small bytes like
\x04. Solution code here.
P.S. There is nothing special about squares, you can multiply any two messages together and its signature will be the component signatures squared modulo
n. Squaring just means that I can use fewer signatures :)
This is a standard implementation of the Goldwasser-Micali cryptosystem with an additional “hint” provided.
We know the composite modulus
mod = alpha*beta, as well as some hint
y := alpha ^ (flag2 << 0x1f0)
flag2 is the second half of the flag. Notice that the flag is less than 100 bytes, which guarantees that
flag2 is less than 50 bytes, or equivalently 400 bits.
Given that alpha is 1024 bits, this implies that
alpha share a lot of bits!
flag2 only overwrites 400 middle bits:
y == 128 alpha bits | 400 garbage bits | 0x1f0 alpha bits
This should hopefully remind you of a common attack used in RSA: factoring with known prime bits. We can then construct a polynomial and solve for small roots where
alpha is approximated by
y with those middle 400 bits zeroed out. Recovering
x gives us both
flag2, and solve
flag1 can simply be decrypted now that we know the private key. Solution code here.
This was honestly a very tedious challenge to implement, so I’ll go over the main ideas. You may be familiar with ECDSA nonce reuse, but it turns out that as long as you have to signatures which even have related nonces, ECDSA is totally broken.
More specifically, given two signatures that use nonces
k, k+diff, we have equations
s1 == inv(k) * (h1 + r1*d) (mod n) s2 == inv(k + diff) * (h2 + r2*d) (mod n)
n is the order of the curve. Isolating the nonces:
k == inv(s1) * (h1 + r1*d) k + diff == inv(s2) * (h2 + r2*d)
Subtracting the first equation from the second:
diff == inv(s2) * (z2 + r2*d) - inv(s1) * (z1 + r1*d) diff + inv(s1)*z1 - inv(s2)*z2 == d * (inv(s2)*r2 - inv(s1)*r1) d == (diff + inv(s1)*z1 - inv(s2)*z2) * inv(inv(s2)*r2 - inv(s1)*r1)
Hooray, we have the private key! However, this challenge does not generate nonces like this. Instead, it uses a common base value and adds a random number from 1 to 4096 each time. This implies that
diff := k2-k1 falls between -4095 and 4095. We can easily bruteforce all possible differences and check to see whether the resulting candidate is indeed the private key; that is,
dG == Q.
The issue is that the server only returns 3 values out of
(r1, s1, r2, s2). Furthermore, they are randomly shuffled. Very annoying.
Let’s hope that in the first round, either
r2 is removed. Without loss of generality, suppose it is
r1 (we could simply switch our labels otherwise). We can map
r1 back to a point
P on the curve. This can be done by solving for y in the corresponding equation:
y^2 == x^3 + ax + b (mod p) y == sqrt(x^3 + ax + b) (mod p)
Notice that this modulus is
p, which is not the curve order
n. You can take modular square roots using Tonelli-Shanks, but remember that there will be two solutions:
-y. I think it doesn’t matter which one we use because of symmetry, but one could test both just in case.
r2 is the x-coordinate of
P + diff*G, we can recover it and extract the private key! We’ll also want to calculate one of the nonce values, for future use.
k1 == inv(s1)*(h1 + r1*d) (mod n)
In the remaining 99 rounds, we only have to use one signature to extract the private key. So regardless of which value the server removes, there will always be a full signature in the 3-tuple it sends; call it
(r, s). Let’s also send the same message for both signatures so we always know the hash
Since the nonce generation base value is held constant across rounds, we can bruteforce the difference between
k1 and the new nonce. For every possible nonce
k, the candidate private key is
d := (s*k - h)*inv(r) (mod n)
To review: we hope that the first round returns both
s values. If not, reset the connection. Then brute force the nonce difference to recover the private key and
k1, which approximates the common base value. In any subsequent round, we bruteforce the actual nonce value using
k1. Solution code here.
Our goal is to supply the server with a weak elliptic curve. However, the server has a number of restrictions:
- Curve order cannot be the modulus (no Smart’s attack)
- Curve order must be prime
The vulnerability is that the server checks primality using a custom implementation of Miller-Rabin, which only performs one round. Hence we want to find a composite number that tricks Miller-Rabin with significant probability. It must also have relatively small prime factors, so we can run Pohlig-Hellman efficiently.
This paper is a great resource. It tells us to look for Carmichael numbers whose prime factors are all 3 mod 4. When this is the case for some
m factors, the probability of tricking Miller-Rabin is
Since this is a golf challenge, we want to find Carmichael numbers with bit length as close to 256 as possible. Fortunately, the paper describes how to generate large Carmichael numbers from small ones.
We could also figure out how to generate Carmichael numbers in general, but I found a list of all
C < 10^16 online. The resource is 404, but if you go on Wayback Machine back to 2007, you can download the archive there (lol). My method for choosing which one to use was pretty arbitrary, but I settled on
5 * 13 * 19 * 23 * 37 * 397, which generates:
C := 2546219544036917612349940381838413660553096101698112741559701 == 1837470101 * 5512410301 * 8268615451 * 10106085551 * 16537230901 * 181909539901
This doesn’t satisfy the mod 4 requirement, but it’s adequate for tricking the server.
C has bit length 201, which satisfied the golf requirements at the time that I solved the challenge. Each prime factor has bit length < 40, so Pohlig-Hellman is easily doable.
Next, we have to generate a curve with order
C. I used ecgen, which is an evil, unmaintained tool for generating elliptic curves. Someone needs to rewrite this in Sage. Building this is a pain in the ass, so just download the binary and run the
before_install commands in the Travis CI build.
Sage provides an implementation of Pohlig-Hellman, but it doesn’t let you supply the factorization. I ended up jacking the source code. And this is enough! Just hurl
C at the server until it accepts it and recover the secret key. Solution code here.
Our goal is to factor an RSA modulus, given a set of four hints. The hints are essentially the public keys corresponding to four small private keys. On average, the private keys are 896 bits, which is approximately
In the case where there is a single small private key, there are a number of standard attacks: Boneh-Durfee and Wiener’s attack are the most common. However, these private keys far exceed the conventional bounds of
n^.28. We need to somehow combine our knowledge of 4 small private keys to raise the bound.
There exist variants of Boneh-Durfee which attack these so-called “simultaneous” bivariate equations, and I spent a long time trying to implement one. However, there’s also a Wiener’s variant which is much simpler and works just as well for these bounds. I highly recommend fully reading the paper, since I won’t explain every detail.
The main idea is that we can construct a matrix equation
bM = v such that both
v are small. Do you see where this is going?
LLL strikes again!
v is a short vector in the lattice generated by
M, so we can recover it by performing basis reduction on
M. Then, all we have to do is solve the linear equation for
The paper provides
M for two and three exponents, but only provides a good candidate
b for four small exponents. It’s up to us to construct this lattice.
Again, I won’t into much detail about the reasoning, but each relation represents a column of the lattice. I computed the relation polynomials symbolically, then matched each term to one of the elements in
b and wrote the corresponding
e*n term in the lattice.
Try constructing the relations in the three exponent case and matching them against the lattice for a better idea of the process. I should also point out that the paper has incorrect sixth and seventh elements of
b in that section.
Next, I had to figure out the diagonal matrix which optimizes the lattice’s determinant. To do so, I did a test run with
alpha := .25 and another run with
alpha := .5, noting the size of
v’s elements. Since
log_n(size) has a linear relationship with
alpha, this was sufficient for determining the diagonal entries.
Once we have
x, its elements are multiples of
di and some other terms. We can recover each
di (or some small multiple of it) by taking the gcd of every term containing it. Finally,
ed-1 is some multiple of the totient, which is enough to invert
0x1337 and decrypt the flag. Solution code here.