Problem
Solution
The program’s source code is as follows:
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
"""
Generates RSA key with k bits
"""
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
if __name__ == "__main__":
flag = open('flag.txt', 'r').read()
flag = flag.strip()
N, cypher = main(flag)
print("N:", N)
print("e:", e)
print("cyphertext:", cypher)
exit()
Nothing particularly stands out here, except perhaps this custom get_primes
function. If we compare across multiple requests, we learn that they share a factor! Factoring given is hard, but if and share a factor, then factoring and is easy since and (likewise for ). With and , we can compute , and therefore decrypt the flag.
Script
from pwn import *
from gmpy2 import gcd, invert
def main():
e = 65537
items = []
for i in range(2):
conn = remote("verbal-sleep.picoctf.net", 53723)
n = conn.recvline()
conn.recvline()
c = conn.recvline()
n = int(n[2:])
c = int(c[12:])
items.append((n, c))
conn.close()
(n1, c1), (n2, c2) = items
p1 = gcd(n1, n2)
if p1 != 1:
q1 = n1 // p1
phi1 = (p1-1) * (q1-1)
d1 = invert(e, phi1)
m1 = pow(c1, d1, n1)
m1_bytes = bytes.fromhex(hex(m1)[2:])
m1_str = m1_bytes.decode("utf-8")
print(m1_str)
if __name__ == "__main__":
main()