diff --git a/challenges/simple_crypto_2/artifacts/challenge.txt b/challenges/simple_crypto_2/artifacts/challenge.txt new file mode 100644 index 0000000..660744a --- /dev/null +++ b/challenges/simple_crypto_2/artifacts/challenge.txt @@ -0,0 +1,6 @@ +# medium RSA small-e challenge +N = 91813390007952257520908297808279632243955362889815286167907140838934911365014972149123401101682784641359247048109057889069678367998937088273511666829514811030614134525368787938001190378600155061882455512334368300124077595203460600886518877687690004073274882639351418220469549765051148429786356924383698271629 +e = 3 +c = 4458558515803461237145026743305640630148312910698322569811739867610856049547128043485815862799994233227055062762758268119518724531945172835904119268771302415045961514268578491050758830181 + +# Hint: e=3 and the plaintext is small; try taking the integer cube root of c. diff --git a/challenges/simple_crypto_2/artifacts/solution.txt b/challenges/simple_crypto_2/artifacts/solution.txt new file mode 100644 index 0000000..cba91db --- /dev/null +++ b/challenges/simple_crypto_2/artifacts/solution.txt @@ -0,0 +1,5 @@ +MAINTAINER SOLUTION (do not ship) +p = 11630896538188225394074869921640075627446227110703422229469987567564105807509455339373342945562307191931320698463500720161102759581795617099179029855031291 +q = 7893921995307703505129848644825923758496078807017747630803146372067449274064745701994483786635901152984191912985406172754069544328335710106515619727428119 +flag = flag{medium_rsa_120370281} +m (int) = 164587995846537883893515757521491883234003138646799028929966461 diff --git a/challenges/simple_crypto_2/challenge.json b/challenges/simple_crypto_2/challenge.json new file mode 100644 index 0000000..8c8101f --- /dev/null +++ b/challenges/simple_crypto_2/challenge.json @@ -0,0 +1,7 @@ +{ + "name": "simple_crypto_2", + "description": "A medium RSA challenge", + "categories": ["crypto"], + "flag": "flag{medium_rsa_120370281}", + "flag_regex": "flag\\{\\S+\\}" +} diff --git a/challenges/simple_crypto_2/source/create_medium_rsa.py b/challenges/simple_crypto_2/source/create_medium_rsa.py new file mode 100644 index 0000000..f9e2f91 --- /dev/null +++ b/challenges/simple_crypto_2/source/create_medium_rsa.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import secrets +from pathlib import Path + +# ---------- utilities ---------- +def is_probable_prime(n, k=8): + if n < 2: return False + small_primes = [2,3,5,7,11,13,17,19,23,29] + for p in small_primes: + if n % p == 0: + return n == p + d = n - 1 + r = 0 + while d % 2 == 0: + d //= 2 + r += 1 + for _ in range(k): + a = secrets.randbelow(n - 3) + 2 + x = pow(a, d, n) + if x == 1 or x == n - 1: + continue + for _ in range(r - 1): + x = (x * x) % n + if x == n - 1: + break + else: + return False + return True + +def gen_prime(bits): + while True: + p = secrets.randbits(bits) | (1 << (bits - 1)) | 1 + if is_probable_prime(p): + return p + +# ---------- params ---------- +FLAG = "flag{medium_rsa_120370281}" +e = 3 +m = int.from_bytes(FLAG.encode(), "big") +m3 = m**3 + +# Determine output directory relative to this script +SCRIPT_DIR = Path(__file__).resolve().parent +ART = SCRIPT_DIR.parent / "artifacts" +ART.mkdir(parents=True, exist_ok=True) + +# pick two 512-bit primes until N > m^3 +while True: + p = gen_prime(512) + q = gen_prime(512) + if p == q: continue + N = p * q + if N > m3 + 1000: + break + +c = pow(m, e, N) + +# Write challenge +with open(ART / "challenge.txt", "w") as f: + f.write("# medium RSA small-e challenge\n") + f.write(f"N = {N}\n") + f.write(f"e = {e}\n") + f.write(f"c = {c}\n") + f.write("\n# Hint: e=3 and the plaintext is small; try taking the integer cube root of c.\n") + +# Write maintainer solution +with open(ART / "solution.txt", "w") as f: + f.write("MAINTAINER SOLUTION (do not ship)\n") + f.write(f"p = {p}\nq = {q}\n") + f.write(f"flag = {FLAG}\n") + f.write(f"m (int) = {m}\n") + +print("Created medium RSA challenge in:", ART) diff --git a/challenges/simple_crypto_2/source/verify.py b/challenges/simple_crypto_2/source/verify.py new file mode 100644 index 0000000..b7815a0 --- /dev/null +++ b/challenges/simple_crypto_2/source/verify.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +""" +verify_medium.py + +Usage: + python3 verify_medium.py path/to/challenge.txt "CTF{...}" + +Checks whether candidate_flag^e mod N == c (for e=3 challenge). +Exits 0 if correct, 1 otherwise. +""" +import sys +from pathlib import Path + +def read_challenge(path): + N = e = c = None + with open(path, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): continue + if line.startswith("N ="): + N = int(line.split("=",1)[1].strip()) + elif line.startswith("e ="): + e = int(line.split("=",1)[1].strip()) + elif line.startswith("c ="): + c = int(line.split("=",1)[1].strip()) + if None in (N,e,c): + raise ValueError("Could not parse N, e, c from challenge file") + return N,e,c + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: python3 verify_medium.py path/to/challenge.txt \"flag{...}\"") + sys.exit(2) + path = Path(sys.argv[1]) + candidate = sys.argv[2] + N,e,c = read_challenge(path) + + m = int.from_bytes(candidate.encode(), "big") + c2 = pow(m, e, N) + if c2 == c: + print("CORRECT") + sys.exit(0) + else: + print("INCORRECT") + sys.exit(1)