from random import getrandbits, randint from Crypto.Util.number import getPrime, isPrime, inverse from hashlib import sha1 import signal
defgen(l, n): q = getPrime(l) whileTrue: t = getrandbits(n - l) p = t * q + 1 if isPrime(p): break h = randint(1, p - 1) g = pow(h, t, p) x = randint(1, q) y = pow(g, x, p) return (p, q, g, y), x
defsign(pubkey, x, msg, lsb, msb): p, q, g, y = pubkey k = gen_ephemeral_key(150, lsb, msb) r = pow(g, k, p) % q Hm = int(sha1(msg).hexdigest(), 16) s = (Hm + x * r) * inverse(k, q) % q return (r, s)
defverify(pubkey, sig, msg): p, q, g, y = pubkey r, s = sig ifnot0 < r < q ornot0 < s < q: returnFalse w = inverse(s, q) Hm = int(sha1(msg).hexdigest(), 16) u1 = Hm * w % q u2 = r * w % q v = pow(g, u1, p) * pow(y, u2, p) % p % q return v == r
signal.alarm(900) withopen("flag.txt", "r") as f: flag = f.read()
l, n = 160, 1024 pub, x = gen(l, n) print("your pubKey: {}".format(pub)) msb = getrandbits(8) lsb = getrandbits(2)
menu = """ [1] sign message [2] verify signature """
for i inrange(20): print(menu) op = int(input(">").strip()) if op == 1: msg = input("Which message to sign?: ").strip().encode() if msg == b"get flag": print("I'm afraid I can't do that.") break else: sig = sign(pub, x, msg, lsb, msb) print(f"Signature: {sig}") elif op == 2: msg = input("Which message to verify?: ").strip().encode() r = int(input("r:").strip()) s = int(input("s:").strip()) v = verify(pub, (r, s), msg) if v and msg == b"get flag": print(flag) else: print(v) else: print("Invalid option")
审计代码可以发现,这题有点类似0xGame2023的LLL-ThirdBlood
当时那道题的考点是每个k都不一样,但是k的大小为128bit。
这里的每个k的高8位和低2位是一样的。
这里详细说明一下解题过程
初始的尝试
在DSA中有
$$ s \equiv k^{-1}(h + dr) \mod q $$
$$ \therefore k \equiv s^{-1}h + s^{-1}dr \mod q $$
from random import choices from hashlib import sha1 from Crypto.Util.number import * import string from pwn import * from sage.allimport * from tqdm import * import gmpy2 import time
table = string.ascii_lowercase
host = '121.37.182.7'#ip地址 port = 10089#端口
sh = remote(host,port) #建立连接 sh.recvuntil(b"your pubKey:")
pub = eval(sh.recvline().decode().strip())
p,q,g,y = pub
R = [] S = [] H = [] for i inrange(19): sh.recvuntil(b">") sh.sendline(b"1") sh.recvuntil(b"Which message to sign?: ") m = "".join(choices(table,k=16)) msg = m.encode() h = bytes_to_long(sha1(msg).digest()) sh.sendline(msg) sh.recvuntil(b"Signature:") data1 = eval(sh.recvline().decode().strip()) r,s = data1 S.append(s) R.append(r) H.append(h)
n = len(S) r0 = R[0] s0 = S[0] h0 = H[0]
defsign(pubkey, x, msg, k): p, q, g, y = pubkey r = pow(g, k, p) % q Hm = int(sha1(msg).hexdigest(), 16) s = (Hm + x * r) * inverse(k, q) % q return (r, s)
for high in trange(256): for low inrange(4): lowbit = low.bit_length() A = [] B = [] tt = 2**lowbit for i inrange(1,len(R)): a = tt*R[i]*s0 * gmpy2.invert(tt*r0*S[i],q) % q b = (r0*H[i] - R[i]*h0 + R[i]*s0*(high*2**152+low) - r0*S[i]*(high*2**152+low)) * gmpy2.invert(tt*r0*S[i],q) % q A.append(a) B.append(b) n = len(A) Ge = Matrix(ZZ,n+2,n+2) for i inrange(n): Ge[i,i] = q Ge[-2,i] = A[i] Ge[-1,i] = B[i] K = 2**150 Ge[-2,-2] = 1 Ge[-1,-1] = K for line in Ge.BKZ(block_size=30): ifabs(line[-1]) == K: k0_unknown = line[-2] k0 = high*2**152 + k0_unknown*tt + low d = (k0 * s0 - h0) * gmpy2.invert(r0,q) % q ifpow(g,d,p) == y: print(1) sig = sign(pub,d,b"get flag",k0) r,s = sig sh.recvuntil(b">") sh.sendline(b"2") sh.recvuntil(b"Which message to verify?: ") sh.sendline(b"get flag") sh.sendlineafter(b"r:",str(r).encode()) sh.sendlineafter(b"s:",str(s).encode()) print(sh.recvline())
# RCTF{Ev3ry_fighter_h@s_their_signature_style}
Hello,XCTF!
task.py
1 2 3 4 5 6 7
from Crypto.Util.number import bytes_to_long as b2l, isPrime
p, hello = int(input("p = ")), input("hello = ") assert p.bit_length() == 100and isPrime(p) assert b2l(f"{hello}$".encode()) % p == b2l(b"hello") assertlen(hello) == 64andall(x.upper() in"XCTF"for x in hello) print(f'Hello XCTFer! Take your spoils: {open("flag.txt", "r").read()}')
相当于求解 $$ c \equiv 448378203247 \mod p $$ 题目的意思相当于告诉我们hello是由X,C,T,F,x,c,t,f组成的长度为64的字符串,而且以$结尾,求解这个同余式
用鸡块师傅ezmod系列的思路
我们可以写成 $$ c \equiv m_0256^{64} + m_1256^{63} + … + m_{63}256^1 +36 \mod p $$ 36是ord('$')
from Crypto.Util.number import * from tqdm import * from pwn import *
for _ in trange(100000): p = getPrime(100) c = bytes_to_long(b"hello") lenth = 64 c = (c - 36) % p for i inrange(64): c -= 102 * 256^(64-i) c %= p Ge = Matrix(ZZ,lenth+2,lenth+2) T = 2**100 for i inrange(lenth): Ge[i,i] = 1 Ge[i,-1] = 14 * 256**(lenth - i)
Ge[-2,-2] = 1 Ge[-2,-1] = -c Ge[-1,-1] = p Ge[:,-1:] *= T
for line in Ge.BKZ(block_size=30): tm = b"" if line[-1] == 0: for i in line[:-2]: if i == -1: tm += b"X" if i == 0: tm += b"f" if i == 1: tm += b"t" iflen(tm) == 64: print(tm.decode()) print(p) sh = remote("121.37.167.239",10089) sh.sendlineafter(b"p = ",str(p).encode()) sh.sendlineafter(b"hello = ",tm) print(sh.recvall())
from Crypto.Util.number import isPrime, getRandomNBitInteger, GCD from hashlib import sha256 from D3 import arbiter
e = 3 P_SIZE = 1024 DIFFICULTY = 8
is_lucky_prime = ( lambda num: isPrime(num) and num.bit_length() == P_SIZE andbin(int.from_bytes(sha256(str(num).encode()).digest(), "big")).endswith( "0" * DIFFICULTY ) and GCD(num - 1, e) == 1 ) is_lucky_modulus = lambda p, q: p != q and is_lucky_prime(p) and is_lucky_prime(q)
deftask(): print("[RCTF2024] Give me a lucky modulus to break me! 🤯") p, q = [int(input(f"{num} ({P_SIZE}-bit lucky prime): ")) for num in"pq"] if is_lucky_modulus(p, q): output = arbiter(msg=getRandomNBitInteger(1997), e=e, p=p, q=q) print(f"[ARBITER OUTPUT] {output}") else: print("[RCTF2024] DO NOT TRICK ME! 🤬")
defRSA_decrypt(c, e, p, q): work_state = random.choices( population=["Functional-State", "Garbled-State-1", "Garbled-State-2"], weights=[19, 9, 7], k=1, )[0] d = None match work_state: case"Functional-State": φ = (p - 1) * (q - 1) d = pow(e, -1, φ) + 2024 * φ case"Garbled-State-1": φ = (p - 1) * (q - 1) d = rctf_pow(e, -1, φ) case"Garbled-State-2": φ = lcm(p - 1, q - 1) d = rctf_pow(e, -1, φ) m = pow(c, d, p * q) return m, d
defarbiter(msg, e, p, q): c = RSA_encrypt(msg, e, p * q) whileTrue: ms, ds = zip( *[RSA_decrypt(c, e, p, q) for chip in ["chip-1", "chip-2", "chip-3"]] ) iflen(set(ms + (msg,))) == 1: Δ = len(set(ds)) - 1 match Δ: case0: return"😄 My arbiter works perfectly!" case1: return"😅 Something doesn't look right..." case2: returnf'🚨 Error detected! Take your spoils: {open("flag.txt", "r").read()}'