# solve_fixed.sage -- run with sage -python or inside sage from sage.all import * from itertools import permutations from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes from Crypto.Util.Padding import pad, unpad
# ====== problem instance (paste from challenge) ====== p = 14668080038311483271
C_rows = [ [11315841881544731102, 2283439871732792326, 6800685968958241983, 6426158106328779372, 9681186993951502212], [4729583429936371197, 9934441408437898498, 12454838789798706101, 1137624354220162514, 8961427323294527914], [12212265161975165517, 8264257544674837561, 10531819068765930248, 4088354401871232602, 14653951889442072670], [6045978019175462652, 11202714988272207073, 13562937263226951112, 6648446245634067896, 13902820281072641413], [1046075193917103481, 3617988773170202613, 3590111338369894405, 2646640112163975771, 5966864698750134707], ]
D_rows = [ [1785348659555163021, 3612773974290420260, 8587341808081935796, 4393730037042586815, 10490463205723658044], [10457678631610076741, 1645527195687648140, 13013316081830726847, 12925223531522879912, 5478687620744215372], [9878636900393157276, 13274969755872629366, 3231582918568068174, 7045188483430589163, 5126509884591016427], [4914941908205759200, 7480989013464904670, 5860406622199128154, 8016615177615097542, 13266674393818320551], [3005316032591310201, 6624508725257625760, 7972954954270186094, 5331046349070112118, 6127026494304272395], ]
msg = b"\xcc]B:\xe8\xbc\x91\xe2\x93\xaa\x88\x17\xc4\xe5\x97\x87@\x0fd\xb5p\x81\x1e\x98,Z\xe1n`\xaf\xe0%:\xb7\x8aD\x03\xd2Wu5\xcd\xc4#m'\xa7\xa4\x80\x0b\xf7\xda8\x1b\x82k#\xc1gP\xbd/\xb5j"
# ===== Build GF(p) matrices ===== P = GF(p) C = matrix(P, C_rows) D = matrix(P, D_rows)
# ===== helper: eigenvalues (nonzero) ===== def eigenvals_nonzero(M): poly = M.charpoly() roots = poly.roots() # list of (root, multiplicity) vals = [] for lam, mult in roots: vals.extend([int(lam)] * mult) return [x % p for x in vals if x % p != 0]
lam_list = eigenvals_nonzero(C) mu_list = eigenvals_nonzero(D)
print("lam_list (nonzero):", lam_list) print("mu_list (nonzero):", mu_list)
if len(lam_list) != len(mu_list): raise RuntimeError("Mismatch in nonzero eigenvalue counts; unexpected.")
# ===== multiplicative order & discrete log ===== R = IntegerModRing(p) def multiplicative_order_mod(x): return Integer(R(x).multiplicative_order())
def dlog(mu, lam): """Return (k, ord) where mu == lam^k (mod p) and ord is multiplicative order of lam.""" ord_l = multiplicative_order_mod(lam) # Sage's discrete_log supports specifying order for faster/robust behavior k = discrete_log(R(mu), R(lam), ord=ord_l) return Integer(k), Integer(ord_l)
# ===== generalized CRT (handles non-coprime moduli) ===== def crt_pair(a1, m1, a2, m2): """ Solve x ≡ a1 (mod m1) x ≡ a2 (mod m2) Return (x_mod, l) where x_mod is solution modulo l = lcm(m1,m2). Raise ValueError if incompatible. """ a1 = Integer(a1); m1 = Integer(m1) a2 = Integer(a2); m2 = Integer(m2) g = gcd(m1, m2) if (a2 - a1) % g != 0: raise ValueError("Incompatible congruences") # Solve using standard method: x = a1 + m1 * t, need m1*t ≡ a2-a1 (mod m2) # Reduce to (m1/g) * t ≡ (a2-a1)/g (mod m2/g) m1p = m1 // g m2p = m2 // g rhs = (a2 - a1) // g inv = inverse_mod(m1p % m2p, m2p) # m1p and m2p are coprime t = (rhs * inv) % m2p x = a1 + m1 * t L = lcm(m1, m2) return Integer(x % L), Integer(L)
# ===== try permutations, compute discrete logs and merge via generalized CRT ===== def try_find_k(): n = len(lam_list) for perm in permutations(mu_list): # compute all (k_i, m_i) for pairing lam_i -> perm[i] pairs = [] ok_pairs = True try: for lam, mu in zip(lam_list, perm): k_i, m_i = dlog(mu, lam) pairs.append((k_i, m_i)) except Exception as e: # discrete log failed for this permutation (rare), skip ok_pairs = False
if not ok_pairs: continue
# iteratively merge congruences try: x, mod = pairs[0] for (ki, mi) in pairs[1:]: x, mod = crt_pair(x, mod, ki, mi) # verify good = True for lam, mu in zip(lam_list, perm): if pow(int(lam), int(x), p) % p != int(mu) % p: good = False break if good: return int(x), int(mod) except ValueError: # incompatible congruences for this perm -> try next perm continue raise RuntimeError("No consistent exponent found across permutations.")
k_mod, L = try_find_k() print("Found k_mod =", k_mod, "mod L =", L)
# ===== lift to actual key in [2**62, p) as generator used randint(2**62, p) ===== LOW = 2**62 HIGH = p # find smallest t s.t. k_mod + t*L >= LOW if k_mod >= LOW: key_int = k_mod else: t = (LOW - k_mod + L - 1) // L key_int = k_mod + t * L
if not (LOW <= key_int <= HIGH): raise RuntimeError("Failed to lift key into expected interval.")
print("Candidate key_int:", key_int)
# ===== Recreate AES key and decrypt ===== key_bytes = pad(long_to_bytes(key_int), 16) cipher = AES.new(key_bytes, AES.MODE_ECB) pt_padded = cipher.decrypt(msg) try: flag = unpad(pt_padded, 64) except ValueError: # If padding removal fails, just print raw padded plaintext flag = pt_padded
print("Recovered flag (raw bytes):", flag) print("Recovered flag (utf-8 try):", None if not all(32<=b<127 for b in flag[:]) else flag.decode())
|