cscg20-rsa

CSCG 2020 Challenge 'RSA Service'
git clone https://git.sinitax.com/sinitax/cscg20-rsa
Log | Files | Refs | sfeed.txt

commit 01987404d7c842e0c8e5b9f38607c26b25468c6c
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 25 May 2024 19:08:17 +0200

Add challenge files and solution

Diffstat:
A.gitignore | 1+
Achall/description | 1+
Achall/rsa-service.tar.xz | 0
Asolve/gen_rsa_values.py | 45+++++++++++++++++++++++++++++++++++++++++++++
Asolve/server.py | 36++++++++++++++++++++++++++++++++++++
Asolve/solve.py | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 146 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/chall/description b/chall/description @@ -0,0 +1 @@ +What did you say? diff --git a/chall/rsa-service.tar.xz b/chall/rsa-service.tar.xz Binary files differ. diff --git a/solve/gen_rsa_values.py b/solve/gen_rsa_values.py @@ -0,0 +1,45 @@ +from sage.all import * +import random +c = 6453808645099481754496697330465 +m = 1067267517149537754067764973523953846272152062302519819783794287703407438588906504446261381994947724460868747474504670998110717117637385810239484973100105019299532993569 +ref = sqrt(m) +succeed = False +while not succeed: + ps = [3] + for i in range(100): + ps.append(next_prime(ps[-1])) +# print ps;exit() + out = [] + for i in range(2): + p = 0 + while not is_prime(p+1): + used = set() + p = 1 + while p <= ref: + tp = random.choice(ps) + used.add(tp) + p *= tp ** random.randint(1, 20) + p *= 2 + # print p + for u in used: + # print u + ps.pop(ps.index(u)) + out.append(p) + print(out) + pp, qp = out + p = pp + 1 + q = qp + 1 + try: + resp = discrete_log(Mod(c, p), Mod(m, p)) + resq = discrete_log(Mod(c, q), Mod(m, q)) + except: + continue + print('tried') + if resp < (p-1)/2 and resq < (q-1)/2: + print('good1') + e = crt([resp, resq], [(p-1)/2, (q-1)/2]) + if e < p*q: + print('e: ', e) + print('p: ',p) + print('q: ',q) + succeed = True diff --git a/solve/server.py b/solve/server.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3.8 +# Note: The server is running a version of pyopenssl patched with this: +# https://github.com/pyca/pyopenssl/pull/897 +# Attacking the pyopenssl wrapper code is not the intended solution. +import OpenSSL.crypto as crypto + +welcome = '''=============== WELCOME TO THE RSA TEST SERVICE =============== +You can send me a message and a key to decrypt it! +If your setup works correctly, you will receive a flag as a reward! +But wait, it is quite noisy here! +''' +question_to_ask = b"Hello! Can you give me the flag, please? I would really appreciate it!" + + +print(welcome) +print("Please give me your private key in PEM format:") +key = "" +while x := input(): + key += x + "\n" + +message = input("Now give me your message: ") +message = b"Quack! Quack!" +print("Did you say '" + message.decode() + "'? I can't really understand you, the ducks are too loud!") + + +key = crypto.load_privatekey(crypto.FILETYPE_PEM, key) +assert key.check() +numbers = key.to_cryptography_key().private_numbers() + +d = numbers.d +N = numbers.p * numbers.q + +if pow(int.from_bytes(message, "big"), d, N) == int.from_bytes(question_to_ask, "big"): + print("CSCG{DUMMY_FLAG}") +else: + print("That was not kind enough!") diff --git a/solve/solve.py b/solve/solve.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +from pwn import process, context +from Crypto.PublicKey import RSA +from OpenSSL.crypto import load_privatekey, FILETYPE_PEM +from sympy.ntheory.modular import crt +from sympy.ntheory import discrete_log +from sympy import nextprime, isprime, prod, sieve +from math import isqrt, gcd +import random + +# We can split the decryption into two discrete logarithm problems: +# m1^((p-1)*j+dp) = m2 (mod p) and m1^((q-1)*k+dq) = m2 (mod q) +# If we construct p and q in a way that the factors of phi(n) are small, +# we can use the Pohlig-Hellman algorithm for finding dp and dq quickly. +# To construct d from dp and dq we use the Chinese-Remainder-Theorem. +# CRT requires the moduli to be coprime, so we use sp = p and sq = q/gcd(p-1,q-1) +# as moduli instead. To expand the resulting solution di to the (p-1)*(q-1) +# ring again, we need to try all solutions di + i * sp * sq * gcd(p-1,q-1) +# for i in [0,gcd(p-1,q-1)[. + +# P.S: Some players solved this by trial, choosing d and factoring +# m1**d - m2 using FactorDB, but that solution seems less intended. + +m1 = int.from_bytes(b"Quack! Quack!", "big") +m2 = int.from_bytes(b"Hello! Can you give me the flag, please? I would really appreciate it!", "big") + +def build_prime(primes, limit): + while True: + ps = [] + while prod(ps) < limit: + ps += [random.choice(primes),] * random.randint(1, 10) + if isprime(prod(ps) * 2 + 1): + return ps, prod(ps), prod(ps) * 2 + 1 + +def build_params(primes): + while True: + pp, sp, p = build_prime(list(primes), isqrt(m2)+1) + _, sq, q = build_prime(list(primes - set(pp)), isqrt(m2)+1) + try: + dp = int(discrete_log(p, m2, m1)) + dq = int(discrete_log(q, m2, m1)) + except: + continue # m2 not in group m1^x mod .. + gcdpq = gcd(p, q) # = 2 by construction + di = int(crt([sp * gcdpq, sq], [dp % (sp * gcdpq), dq % sq])[0]) + for i in range(gcdpq): + d = di + i * sp * sq * gcdpq + if pow(m1, d, p*q) != m2: + continue # wrong i + if gcd(d, (p-1)*(q-1)) != 1: + continue # unlucky + e = pow(d, -1, (p-1)*(q-1)) + return p*q, e, d + +params = build_params(set(sieve[1:100])) +key = RSA.construct(params, consistency_check=False) + +io = process(["python", "server.py"]) +io.sendlineafter(b"PEM format:", key.exportKey("PEM") + b"\n") +io.sendlineafter(b"your message:", b"Quack! :D") +io.readline().decode() +print(io.readline().decode())