Mega secure
2 files available
Description
My friend keeps bragging about how "mega secure" his new encryption system is, but is it really as secure as he thinks?
Solution
In this challenge a public RSA key and an AES-CBC encrypted payload are provided. The payload contains the concatenated private key components in the following format:len(p) | p | len(q) | q | len(dp) | dp | len(dq) | dq | len(u) | u
, where p
and q
are the prime factors of RSA modulus, dp
and dq
are the CRT exponents and u
is the CRT coefficient.
It's possible to interact with the server to decrypt any message encrypted with the given public RSA key. The decryption process relies on the encrypted payload containing the private key components that is given to the server.
To get the flag a randomly generated message must be signed correctly. To do that, the secret d
must be recovered.
The first thing to notice is that the server doesn't check if the encrypted payload received is correct. Exploiting this, a fault attack can be performed. In fact, randomly changing the value of u
leaks information about the secret q
. Once obtained, d
can be computed to get the flag.
Now consider changing the encrypted keys such that the server obtains (to do this just randomly modify an encrypted block of u
). There are two cases when decrypting a message :
1.
In this case the decryption of with returns . Since , it follows that , while for it holds that . Therefore, there exists such that . Combining this observation it holds that . But and therefore . This implies that regardless of the value of will have no effect on the outcome. For this reason the correct value is returned: .
2.
In this case the decryption of with returns . It holds that and . Therefore, there exist and such that and . Then . Now if and only if and are coprime. However, since the probability of and not being coprime is only , it is almost certain that they are coprime. So it holds that and .
From being able to distinguish the two cases, it can be exploited to recover the secret key and obtain the flag. Below is a script that solves this challenge.
#!/usr/bin/env python3
from Crypto.Util.number import isPrime, bytes_to_long as btl, long_to_bytes as ltb
from pwn import *
r = remote(HOST, PORT)
def decrypt_msg(msg, sk):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b': ', msg)
r.sendlineafter(b': ', sk)
return r.recvline(False).decode()
def get_flag(n, d):
r.sendlineafter(b'> ', b'2')
r.recvuntil(b'Message: ')
m = int(r.recvline(False).decode())
s = pow(m, d, n)
r.sendlineafter(b': ', str(s).encode())
r.recvline()
return r.recvline(False)
if __name__ == "__main__":
# receiving public keys and encrypted secret keys
r.recvuntil(b'n: ')
n = int(r.recvline(False).decode())
r.recvuntil(b'e: ')
e = int(r.recvline(False).decode())
r.recvuntil(b'sk: ')
sk = ltb(int(r.recvline(False).decode()))
iv = sk[:16]
sk = sk[16:]
# randomly modify second-last block, so the server will obtain u' != u
blocks = [sk[i:i + 16] for i in range(0, len(sk), 16)]
blocks[len(blocks) - 1] = os.urandom(16)
new_sk = btl(iv + b''.join(b for b in blocks))
# start finding 1 bit of q per request
q = 0
for i in range(1024, -1, -1):
m = q + 2 ** i
c = pow(m, e, n)
res = decrypt_msg(str(c).encode(), str(new_sk).encode())
if 'Message received' == res:
q += 2 ** i
q += 1
assert (isPrime(q))
# now with q, d can be computed and the message signed
p = n // q
d = pow(e, -1, (p - 1) * (q - 1))
print(get_flag(n, d))