import base64 import hmac import os import pathlib import gmpy2 from flask import Flask, make_response, g, request def generate_challenge(): p = random_prime() q = random_prime() r = random_prime() N_c1 = p * q N_c2 = q * r return N_c1, N_c2 def verify_response(a: int, b: int, N_c1: int, N_c2: int): e = 65537 if not (a > 1 and b > 1) or (a == N_c1 and b == N_c2): raise ValueError('Too easy response, try harder.') # pow(x, y, n) calculates x^y (mod n) if pow(a, e, N_c1) != pow(b, e, N_c2): raise ValueError('Invalid challenge response.') return True def random_prime(): """Return a random 512 bit prime. No malicous mistake here.""" return int(gmpy2.next_prime(int.from_bytes(os.urandom(64), 'big'))) # # Everything below is not relevant for solving the challenge. # Just various utility functions which contain no malicous mistakes. # app = Flask(__name__) CHALLENGE_TEMPLATE = """ aresah

aresah

{message}

My challenge

Nc1 = {N_c1}

Nc2 = {N_c2}

View source code

Your response

""".lstrip() @app.route('/', methods=['GET', 'POST']) def index(): N_c1, N_c2 = generate_challenge() packed_challenge = pack_challenge(N_c1, N_c2) message = '' if request.method == 'POST': try: try: a = int(request.form.get('a', '').strip()) b = int(request.form.get('b', '').strip()) except ValueError: raise ValueError('Response must be integers.') submitted_challenge = request.form.get('challenge', '') N_c1, N_c2 = unpack_challenge(submitted_challenge) verify_response(a, b, N_c1, N_c2) with open('secret.txt') as f: secret = f.read() message = '

%s

' % secret except ValueError as e: message = '

%s

' % str(e) return CHALLENGE_TEMPLATE.format(N_c1=N_c1, N_c2=N_c2, challenge=packed_challenge, message=message) @app.route('/source') def view_source(): with open(__file__) as f: source = f.read() resp = make_response(source) resp.headers['Content-Type'] = 'text/plain' return resp def pack_challenge(N_c1: int, N_c2: int) -> str: challenge_bytes = (N_c1.to_bytes(128, 'big') + N_c2.to_bytes(128, 'big')) mac = hmac.new(get_key(), challenge_bytes, 'sha256').digest() return base64.b64encode(mac + challenge_bytes).decode() def unpack_challenge(packed_challenge: str) -> tuple: try: signed_bytes = base64.b64decode(packed_challenge) except ValueError: raise ValueError('Invalid challenge: base64 decode error') mac, challenge_bytes = signed_bytes[:32], signed_bytes[32:] expected_mac = hmac.new(get_key(), challenge_bytes, 'sha256').digest() if not hmac.compare_digest(mac, expected_mac): raise ValueError('Invalid challenge: MAC error') N_c1 = int.from_bytes(challenge_bytes[:128], 'big') N_c2 = int.from_bytes(challenge_bytes[128:], 'big') return N_c1, N_c2 def get_key(): if not hasattr(g, 'key'): with open('secret-key', 'rb') as f: g.key = f.read() return g.key