DiceCTF 2021
February 7, 2021It’s been a while since I wrote challenges for a CTF! Besides the writeups in this post, you can find my solution code at github.com/defund/ctf.
benaloh
We’re given an implementation of the Benaloh cryptosystem along with a public key and encrypted flag. Since the block size is , the flag is split into nibbles (4 bits), and each nibble is encrypted separately.
To encrypt a message , the algorithm first computes . It then randomizes this value by sampling a nonce and multiplying by the mask . In this challenge, the vulnerability is that the nonces are not randomly chosen, but instead generated via LCG. Let’s call the initial seed value . Then we have masks of the form , , , and so on.
Our first insight is that if we already know some nibble
,
we can divide the associated ciphertext by
to recover its mask. Since we know the flag starts with
dice{
, we can recover 10 masks total, which we’ll call
.
Given this sequence of masks, our goal will be to predict future masks.
From there, we can derandomize the ciphertexts and solve the discrete
logarithm, which is easy since
ranges from 0 to 15.
To motivate the final step of the solution, let’s consider the scenario where and are public parameters. In this case, the setup is identical to that of the Franklin-Reiter related-message attack on RSA. We can construct two univariate polynomials with the common root , and their polynomial GCD will be .
Unfortunately, we do not have the luxury of dealing with univariate polynomials. Since and are unknown in the challenge, we instead have multivariate polynomials with a common root . This calls for a more powerful tool, namely Gröebner basis reduction. Informally, this process is a hybrid of polynomial GCD and Gaussian elimination. It attempts to reduce a set of multivariate polynomials into a simpler form, while preserving roots.
Using the known masks, let’s define a few polynomials (you only need four) and compute their Gröeber basis. We get something like Although we cannot directly recover , we can rewrite each nonce as some constant multiple of . It follows that we can rewrite each mask as a constant multiple of — but we know this value! As a result, we can compute the remaining masks and recover the flag.
signature sheep scheming signature schemes
For some context, I came up with this challenge while trying to make a legitimately secure signature scheme. As you can see, that didn’t work out — although I guess a CTF challenge isn’t so bad either :)
If you aren’t familiar with lattice-based cryptography, I would highly suggest reading Chris Peikert’s survey. Other than that, the challenge setup is pretty simple: the server signs arbitrary messages, and our goal is to recover its secret key.
In order to generate a keypair, the algorithm samples a uniform public matrix , a short secret matrix , and a short error matrix . The private key is , and the public key is along with . This is a fairly common idea; see FrodoKEM for a real-world example.
On the other hand, the signing algorithm is completely made up. Essentially, it is a sigma protocol made non-interactive via the Fiat-Shamir transform. The prover first commits to an “ephemeral key” . The verifier’s challenge is a short vector , and the server’s response is . Finally, the verifier checks that is approximately equal to .
The issue is that this sigma protocol is not zero-knowledge. By collecting a bunch of samples, we can build the matrix equation . Although this might appear like an LWE instance at first glance, all of the matrices are short. In fact, the computation will never wrap around the modulus. As a consequence, we can lift this equation to the integers. All that remains is to solve for by treating as an error term. I personally did a least squares optimization, although lattice reduction should also work.