Snore Signatures (crypto)
These signatures are a bore!
Last updated
These signatures are a bore!
Last updated
The following Python code implements the encryption scheme:
We start with the main()
function. We obtain p
, q
and g
by invoking gen_snore_group()
. In gen_snore_group()
, we generate the primes p
and q
, then calculate r = (p - 1) // q
. Finally, we obtain g
by doing g = pow(h, r, p)
(h is a random int). p
, q
and g
are all given to us as public information.
This is where things get interesting. A for loop iterates 10 times. Within each iteration, we first obtain x
and y
using snore_gen(p, q, g)
. x
is a random integer and y = pow(g, -x, p)
.
We are given y
, but not x
. Then, we get one query to a signing oracle. The oracle takes in a message m
of our choice, then calculates its signature (s, e)
using s, e = snore_sign(p, q, g, x, m)
.
Let's try to understand the math behind snore_sign
.
It first generates a random integer k
, then calculates r
. r, m, p, q
generate e
, and k, x, e, q
generate s
. Expressing this mathematically,
After we get the signature (s, e)
from the query, we are asked to provide a message m
(which we haven't used before), and a valid signature (s, e)
for it (but our e
must be the same as the one obtained from the query, so we can only change s
).
If we can repeat this process of querying the signing oracle and forging a signature 10 times, we get the flag.
Next, let's look at the code for the signature verification:
snore_verify
takes the m
and s
we provide. Then, we calculate rv
followed by ev
. Then we compare ev
and the expected signature e
to check if they match.
Let's try to work out why the verification would work if our given m
and s
were indeed correct.
We are given
Combining this with the equations from snore_sign
, we have
which simplifies to
Then we have
If our provided s
and m
were correct, this equation would be the same as our hash e
.
Query the oracle using a message m
, and obtain the signature (s, e)
.
To forge a signature, send an m'
such that m' = m + ip
, where i
is a positive integer.
Why does this work?
Using pwntools, I wrote a script to repeat this procedure 10 times and obtain the flag from the server. Since it remembers the values of m
from previous queries and prevents us from reusing them, I use the following values of m and m' for the 10 iterations:
So... How do we forge a signature? The key is in identifying a vulnerability in the computation of . When we compute the hash during verification, we should be concatenating rv
to m
instead of adding them together. To forge a signature we can follow the process below:
Since we are reusing s
, we know that it has to be equivalent to k + xe
even though we don't know k
and x
! This would give us
Since m' = m + ip
, . As such, we would get