from Crypto.Util.number import long_to_bytes, inverse import hashlib, gmpy2, itertools, string
KEY = '5ae9b7f211e23aac3df5f2b8f3b8eada' P = 8950704257708450266553505566662195919814660677796969745141332884563215887576312397012443714881729945084204600427983533462340628158820681332200645787691506 n = 44446616188218819786207128669544260200786245231084315865332960254466674511396013452706960167237712984131574242297631824608996400521594802041774252109118569706894250996931000927100268277762882754652796291883967540656284636140320080424646971672065901724016868601110447608443973020392152580956168514740954659431174557221037876268055284535861917524270777789465109449562493757855709667594266126482042307573551713967456278514060120085808631486752297737122542989222157016105822237703651230721732928806660755347805734140734412060262304703945060273095463889784812104712104670060859740991896998661852639384506489736605859678660859641869193937584995837021541846286340552602342167842171089327681673432201518271389316638905030292484631032669474635442148203414558029464840768382970333 c = 42481263623445394280231262620086584153533063717448365833463226221868120488285951050193025217363839722803025158955005926008972866584222969940058732766011030882489151801438753030989861560817833544742490630377584951708209970467576914455924941590147893518967800282895563353672016111485919944929116082425633214088603366618022110688943219824625736102047862782981661923567377952054731667935736545461204871636455479900964960932386422126739648242748169170002728992333044486415920542098358305720024908051943748019208098026882781236570466259348897847759538822450491169806820787193008018522291685488876743242619977085369161240842263956004215038707275256809199564441801377497312252051117441861760886176100719291068180295195677144938101948329274751595514805340601788344134469750781845 e = 65537
for tup in itertools.product(string.ascii_lowercase + string.digits, repeat=6): kb = ''.join(tup).encode() if hashlib.md5(kb).hexdigest() == KEY: key = tup_digit = int.from_bytes(kb, 'big') break p = P ^ key q = gmpy2.iroot(n // (p**3), 2)[0] while q**2 != n // (p**3): q += 1 phi = p*p*(p-1)*q*(q-1) d = inverse(e, phi) print(long_to_bytes(pow(c, d, n)).decode())
FLAG
1
flag{ECC_1s_4w3s0m3_but_n0t_perf3ct}
Copper!!!
题目信息
题号:495
类型:RSA 已知 p 高位 Coppersmith
题面
1024 位 RSA,e = 65537。给出 gift = p >> 242 << 242,即 p 的高 270 位已知,低 242 位未知。
分析
1024 位 RSA 泄露了 p 的高 270 位,仅低 242 位未知。把未知部分视作多项式 f(x) = gift + x 的小根(x < 2^242),Coppersmith 定理在 beta = 0.5 时允许恢复约 N^{0.25} 量级的小根,调小 epsilon = 0.01 可把求解上界撑到 2^242。求得 x0 后恢复完整 p,q = n // p 常规解密。
解题步骤
设 p = gift + x0,x0 < 2^242,即求 f(x) = gift + x 在模 p 意义下的小根。
关键细节:sage 实现先将 f 转成整系数多项式再构建格(change_ring(ZZ)),移位多项式 x^j * N^(m-i) * f^i 与 x^i * f^m 的系数用整数;若取模 N 会导致前 m*δ 行为全零。
解出 x0 得 P = gift + x0,Q = n // P,常规 RSA 解密。
EXP
1 2 3 4 5 6 7 8
R.<x> = PolynomialRing(Zmod(n)) p = high_p + x x0 = p.small_roots(X=2^242, beta=0.5, epsilon=0.01)[0] P = int(p(x0)) Q = n // P assert n == P * Q d = inverse_mod(65537, (P-1) * (Q-1)) print(long_to_bytes(power_mod(c, d, n)))
import uuid from Crypto.Util.number import getPrime, bytes_to_long import random
flag = "flag{" + str(uuid.uuid4()) + "}" message_int = bytes_to_long(flag.encode())
p = getPrime(message_int.bit_length() + 3) a = getPrime(p.bit_length())
print(f"a = {a}") print(f"p = {p}")
hint_values = [random.randint(1, p - 1)]
for _ inrange(5): next_value = (a * hint_values[-1] + message_int) % p hint_values.append(next_value)
print("hint =", hint_values)
分析
生成器是 GF(p) 上的一阶仿射递推(左位移寄存器的一种离散形式):
1
h_{i+1} = a·h_i + m (mod p)
其中 m 是明文对应的大整数。a, p 与整条 hint 序列全部公开。这一结构与 LCG(线性同余生成器)同源——只要两个相邻状态已知,累加的常数 m 立即被移除:
1
m ≡ h_1 − a·h_0 (mod p)
单次模差分解即可,无需恢复种子 h_0 以外的任何信息。flag 是 UUID 形式,明文比特数 ≈ 3 × 37 字节,p 恰好取 message_int.bit_length()+3 位素数,保证 0 ≤ m < p 的还原唯一性。
解题步骤
取 hint[0] 与 hint[1];
计算 m = (hint[1] − a·hint[0]) mod p;
long_to_bytes(m) 还原 flag。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13
from Crypto.Util.number import long_to_bytes
a = 295789025762601408173828135835543120874436321839537374211067344874253837225114998888279895650663245853 p = 516429062949786265253932153679325182722096129240841519231893318711291039781759818315309383807387756431 hint = [184903644789477348923205958932800932778350668414212847594553173870661019334816268921010695722276438808, 289189387531555679675902459817169546843094450548753333994152067745494929208355954578346190342131249104, 511308006207171169525638257022520734897714346965062712839542056097960669854911764257355038593653419751, 166071289874864336172698289575695453201748407996626084705840173384834203981438122602851131719180238215, 147110858646297801442262599376129381380715215676113653296571296956264538908861108990498641428275853815, 414834276462759739846090124494902935141631458647045274550722758670850152829207904420646985446140292244]
m = (hint[1] - a*hint[0]) % p print(long_to_bytes(m).decode())
FLAG
1
flag{c3bc3ead-01e3-491b-aa2d-d2f042449fd6}
SageMath 使用指南
题目信息
题号:48
类型:SageMath 有限群阶数计算
题面
Sage 9.3.
题目用 SageMath 的置换群与矩阵群接口定义了一连串经典有限群,将其阶数连续乘入 key,最后对阶数乘积的二进制表示截取前 42×8 位,与密文 c 逐位异或:
key=1 G = PSL(2, 11) key*=G.order() G = CyclicPermutationGroup(11) key*=G.order() G = AlternatingGroup(114) key*=G.order() G = PSL(4, 7) key*=G.order() G = PSU(3, 4) key*=G.order() G = MathieuGroup(12) key*=G.order()
from Crypto.Util.number import * from sympy import primerange
defprime_factors(n): res, d = [], 2 while d * d <= n: while n % d == 0: res.append(d) n //= d d += 1if d == 2else2 if n > 1: res.append(n) return res
deffind_primitive_root(p): phi = p - 1 facs = set(prime_factors(phi)) for g inrange(2, p): ifall(pow(g, phi // q, p) != 1for q in facs): return g
flag = b'flag{??????????????????}'# len == 24 inner = flag[5:-1] n = len(inner) // 3 parts = [inner[i*n:(i+1)*n] for i inrange(3)]
p, g, h = [], [], [] for i inrange(3): p.append(getPrime(48)) g.append(find_primitive_root(p[i])) x = bytes_to_long(parts[i]) h.append(pow(g[i], x, p[i]))
from math import isqrt from Crypto.Util.number import long_to_bytes
p = [189869646048037, 255751809593851, 216690843046819] g = [5, 3, 3] h = [78860859934701, 89478248978180, 81479747246082]
defbsgs(g, h, p): m = isqrt(p - 1) + 1 baby = {} cur = 1 for j inrange(m): baby.setdefault(cur, j) cur = cur * g % p ginv_m = pow(g, p - 1 - m, p) # g^(-m) cur = h for i inrange(m): if cur in baby: x = i * m + baby[cur] ifpow(g, x, p) == h: return x cur = cur * ginv_m % p returnNone
defprintable(bs): returnall(32 <= b < 127for b in bs)
inner = b"" for i inrange(3): x0 = bsgs(g[i], h[i], p[i]) assert x0 isnotNone k = 0 chosen = None while x0 + k * (p[i] - 1) < (1 << 48): x = x0 + k * (p[i] - 1) seg = long_to_bytes(x, 6) if printable(seg): chosen = seg break k += 1 assert chosen isnotNone inner += chosen
cb = [c2[i:i+16] for i inrange(0, len(c2), 16)] c1b = [c1[i:i+16] for i inrange(0, len(c1), 16)] m1b = [m1[i:i+16] for i inrange(0, len(m1), 16)]
perm = [cb.index(blk) for blk in c1b] # 块级置换 defxor(a, b): returnbytes(x ^ y for x, y inzip(a, b))
P = [None] * 4 for j inrange(4): D = xor(m1b[j], IV1 if j == 0else c1b[j-1]) # AESdec_K(cb[perm[j]]) p = perm[j] P[p] = xor(D, IV2 if p == 0else cb[p-1]) # 还原 pad(m2) 的第 p 块
m2 = b''.join(P) print(unpad(m2, 16).decode())
输出:
1
flag{cbc_dancing_1s_the_best_XD_miaowu~_wangang~}
FLAG
1
flag{cbc_dancing_1s_the_best_XD_miaowu~_wangang~}
被泄露的素数
题目信息
题号:55
类型:RSA 素数高位泄露
题面
RSA 公钥参数 n、e 与密文 ciphertext.bin 直接给出,同时泄露了素数 p 的部分高位内容 partial_p.txt。题目源码:
import math, mpmath as mp from fpylll import IntegerMatrix, LLL from Crypto.Util.number import long_to_bytes, inverse
mp.mp.dps = 2000
defpoly_mul(a, b): r = [0]*(len(a)+len(b)-1) for i, x inenumerate(a): for j, y inenumerate(b): r[i+j] += x*y return r
defpoly_pow(p, k): r = [1] for _ inrange(k): r = poly_mul(r, p) return r
defsmall_roots_p(p0, N, X, beta=0.4): """单变量 Coppersmith:求 x0<X 使 (p0+x0)|N,取 LLL 第一行多项式整数根。""" delta = 1 epsilon = beta / 8 m = int(math.ceil(max(beta**2/(delta*epsilon), 7*beta/delta))) t = int(math.floor(delta*m*(1/beta - 1))) ncols = delta*m + t f = [p0, 1] g = [] for i inrange(m): g.append([c * (N**(m-i)) for c in poly_pow(f, i)]) fm = poly_pow(f, m) for i inrange(t): g.append([0]*i + fm) mat = IntegerMatrix(len(g), ncols) for gi, gg inenumerate(g): for j inrange(min(len(gg), ncols)): if gg[j]: mat[gi, j] = gg[j] * (X**j) LLL.reduction(mat) row = [mat[0, c] for c inrange(ncols)] cff = [row[j] // (X**j) for j inrange(ncols)] roots = mp.polyroots([mp.mpf(c) for c in cff[::-1]], maxsteps=1000, extraprec=2000) for rt in roots: ifabs(rt.imag) < mp.mpf('1e-500') andabs(rt.real - mp.nint(rt.real)) < mp.mpf('1e-500'): x0 = int(mp.nint(rt.real)) if0 < x0 < X and N % (p0 + x0) == 0: return x0 returnNone
n = int(open('public_key.pem').read().split('n = ')[1].split('\n')[0].strip()) e = 65537 hx = open('partial_p.txt').read().strip()[3:] # 去掉 ??? p_masked = int(hx, 16) phstr = bin(p_masked)[2:] # 679 bit
X = 1 << 342 for i inrange(4, 8): # 枚举最高 3 bit p_high = int(bin(i)[2:] + phstr, 2) p0 = p_high << 342 x0 = small_roots_p(p0, n, X, beta=0.4) if x0 isnotNone: p = p0 + x0 q = n // p print('p bits:', p.bit_length(), 'q bits:', q.bit_length()) c = int.from_bytes(open('ciphertext.bin', 'rb').read(), 'big') d = inverse(e, (p-1)*(q-1)) print('FLAG:', long_to_bytes(pow(c, d, n)).decode()) break
输出:
1 2
p bits: 1024 q bits: 1024 FLAG: flag{wh3n_th3_m0dul3_i3_bi9_en0ugh_U_c@n_c0ns1der_u3ing_coppersmith}
p = random_prime(2**20) m = len(flag) - 1 A = matrix(Zmod(p), m, len(flag), [random.randint(p//2, p-1) for _ inrange(m*len(flag))]) x = vector([ord(i) for i in flag]) b = A * x
lines = open('output.txt').read().splitlines() p = int(lines[0]) A = [list(r) for r in ast.literal_eval(lines[1])] b = ast.literal_eval(lines[2]) rows = len(b); n = len(A[0])
definv_mod(a, mod): returnpow(a, -1, mod)
defsolve_linear(Mr, rhs, p): N = len(Mr) M = [Mr[i][:] + [rhs[i]] for i inrange(N)] for c inrange(N): piv = next(i for i inrange(c, N) if M[i][c] % p != 0) M[c], M[piv] = M[piv], M[c] inv = inv_mod(M[c][c] % p, p) M[c] = [(x * inv) % p for x in M[c]] for i inrange(N): if i != c and M[i][c] % p != 0: f = M[i][c] % p M[i] = [(vi - f*vj) % p for vi, vj inzip(M[i], M[c])] return [M[i][N] for i inrange(N)]
A_left = [row[:n-1] for row in A] a = [row[n-1] for row in A] x0 = solve_linear(A_left, b, p) xk = [(-v) % p for v in solve_linear(A_left, a, p)]
for t inrange(p): x = [(x0[i] + t*xk[i]) % p for i inrange(rows)] + [t % p] ifall(32 <= c <= 126for c in x): s = ''.join(chr(c) for c in x) if s.startswith('flag{'): print('t =', t) print('FLAG:', s) break
输出:
1 2
t = 125 FLAG: flag{1f59622f-ccbc-45c0-b9f5-731a51343027}
FLAG
1
flag{1f59622f-ccbc-45c0-b9f5-731a51343027}
GCL
题目信息
题号:57
类型:广义线性同余生成器 GCL
FLAG:动态 FLAG
题面
加密流程给出公共参数:c = m ^ key 与 10 个”礼物”数值,来自一个 GCL(Generalized LCG)生成器:
1 2 3 4 5 6 7 8 9 10 11
p = getPrime(length+1) a = random.randint(2, p-1) b = random.randint(2, p-1) s = random.randint(2, p-1) gift = [] # 收集连续 10 个 s 值 whilelen(gift) < 10: s = (a * inverse(s, p) + b) % p if s != 0: gift.append(s) key = (a * inverse(s, p) + b) % p # key = s_11 return m ^ key, gift
分析
递推为分式变换 s_{k+1} = a/s_k + b (mod p),p、a、b 都不公开。对三个连续项有:
1 2
s_{k+1}·s_k ≡ a + b·s_k (1) s_{k+2}·s_{k+1} ≡ a + b·s_{k+1} (2)
Bs = [B(k) for k inrange(8)] g = 0 for k inrange(1, 8): diff = Bs[k] - Bs[0] g = gcd(g, int(diff.numerator)) p = g for pr in (2, 3, 5, 7, 11, 13, 17, 19, 23): while p % pr == 0: p //= pr assert gmpy2.is_prime(p)
s1, s2, s3 = gift[0], gift[1], gift[2] b = (s2 * (s3 - s1) % p) * pow(s2 - s1, -1, p) % p a = (s1 * s2 - b * s1) % p key = (a * pow(gift[9], -1, p) + b) % p # key = s_11 print(long_to_bytes(c ^ key).decode())
输出:
1
flag{2eac1c79-8abd-465e-82f4-96beffed69e4}
FLAG
1
flag{2eac1c79-8abd-465e-82f4-96beffed69e4}
独一无二
题目信息
题号:58
类型:AES-ECB + 随机碰撞
FLAG:动态 FLAG
题面
题目先把随机 16 字节 d 作为 AES-ECB 密钥加密 flag 得到 ct,然后用 d 的数值 D = b2l(d) 作为 ECDSA 私钥,对两条已知消息用同一个随机数 k 各签了一次名:
1 2 3 4 5 6
d = os.urandom(16); D = b2l(d) ct = AES.key(d).ECB_encrypt(pad(flag, 16)) E = EllipticCurve(Zmod(p), [A, B]); G = E.gens()[0] k = random.randint(1, n-1); Q = k*G; r = int(Q[0]) % n s1 = (k^-1 * (e1 + r*D)) % n s2 = (k^-1 * (e2 + r*D)) % n
分析
两次签名共用 nonce k,因此 r 相同,构成经典的 ECDSA nonce reuse。因为两次签名两条式子只有 e 不同,可以做差消去 D 直接解出 k,再代入任意一式解出私钥 D:
from Crypto.Util.number import long_to_bytes as l2b, bytes_to_long as b2l from Crypto.Util.Padding import unpad from Crypto.Cipher import AES
ct = bytes.fromhex('d17f52da7a9c54b87b1b0973bc4a3623166ece646dd6762905413387c531fc9d23e5f2494091c39677ae5dc35566d1ee') n = 278302096557935581738338462024559946959 r = 264579573280920819291511588977260661069 s1 = 157195048165685698821267525173525379816 s2 = 61286613457098845815723227657607632607 e1 = b2l(b"If you used the same random number when signing,") e2 = b2l(b" then you need to be careful.")
k = ((e1 - e2) * pow(s1 - s2, -1, n)) % n # nonce reuse D = ((s1 * k - e1) * pow(r, -1, n)) % n # 私钥
p = getPrime(32) pieces = [flag[i:i+3] for i inrange(0, len(flag), 3)] c = [bytes_to_long(x.encode()) for x in pieces] # 14 个未知系数 x = [random.randint(1, p-1) for _ inrange(14)] for i inrange(100): s = sum(c[i]*x[-14+i] for i inrange(14)) x.append(s % p) print(x[-28:])
M = [[xs[j+i] % p for i inrange(n)] + [xs[14+j] % p] for j inrange(n)] for col inrange(n): piv = next(r for r inrange(col, n) if M[r][col] % p != 0) M[col], M[piv] = M[piv], M[col] iv = pow(M[col][col] % p, -1, p) M[col] = [(v * iv) % p for v in M[col]] for r inrange(n): if r != col and M[r][col] % p != 0: f = M[r][col] % p M[r] = [(a - f*b) % p for a, b inzip(M[r], M[col])] c = [M[i][n] for i inrange(n)]
print('FLAG:', ''.join(long_to_bytes(ci).decode('latin1') for ci in c))
输出:
1
FLAG: flag{188a9250-bd02-4746-8ddd-a32d9c1bb11a}
FLAG
1
flag{188a9250-bd02-4746-8ddd-a32d9c1bb11a}
共轭迷宫
题目信息
题号:60
类型:四元数密钥交换
题面
基于四元数的密钥交换。flag 每 9 字节切成四段,转大整数作为四元数 g 的四分量;a、b 是分别绕 g 虚部方向旋转 45°、60° 的单位四元数(弱密钥生成),各方用共轭法交换:
1 2 3 4
g = Quaternion(w, x, y, z) # w,x,y,z 为 flag 四段整数 P_A = a * g * a.inv() # Alice 公钥 P_B = b * g * b.inv() # Bob 公钥 K = a * P_B * a.inv() # 共享密钥
题目给出 norm_squared = w²+x²+y²+z²、共享密钥 K 各分量、以及 flag 每段整数后 6 位十进制数。
分析
a 的旋转轴正是 g 的虚部方向,对 g 做共轭 a·g·a⁻¹ 不会改变该方向上的分量,标量分量也不变,因此 a·g·a⁻¹ = g,进而 P_A = P_B = g。共享密钥:
1
K = a·P_B·a⁻¹ = a·g·a⁻¹ = g (单位化后的 g)
所以直接用 K 各分量 × ||g||(即 sqrt(norm_squared))就能还原 g 四个整数分量。g 是单位四元数,K 的每个分量乘以范数得原始整数;小数截断误差用”后 6 位十进制数”对齐修正。
解题步骤
norm = sqrt(norm_squared)。
每个 K 分量 × norm 向下取整,再微调使末 6 位等于题给数值(分量为正,可单调递减调整)。
norm = nsq.sqrt() parts = [] for v, t inzip(K, tails): comp = int(Decimal(v) * norm) while comp % 1000000 != t: comp -= 1 parts.append(comp)
print('FLAG:', b''.join(p.to_bytes(9, 'big') for p in parts).decode())
输出:
1
FLAG: flag{hav3_U_f1nd_ouT_@bout_tr1ck?XD}
FLAG
1
flag{hav3_U_f1nd_ouT_@bout_tr1ck?XD}
三重密钥锁
题目信息
题号:61
类型:格 CVP(三重 HNP)
题面
flag 三等分转大整数,得到三个 ~128 位的密钥 a, b, c,在 512 位素数 p 下线性组合后校验:
1 2 3 4
p = random_prime(2^512, lbound=2^511) a, b, c = encode_flag_to_abc(flag) # 每段 < 2^128 k = random.randint(1, p-1); m = random.randint(1, p-1); n = random.randint(1, p-1) f = (k*a + m*b + n*c) % p # 公开 p,k,m,n,f
分析
已知 f ≡ k·a + m·b + n·c (mod p),未知 a,b,c ≈ 2^128。这是一个三重 HNP,可化为 4 维格上的最近向量问题 (CVP):对任意整数 a,b,c,t,格点 (k·a + m·b + n·c + t·p, a, b, c) 属于由 (k,1,0,0)、(m,0,1,0)、(n,0,0,1)、(p,0,0,0) 生成的格。目标向量 (f,0,0,0) 的最近格点就是 (f,a,b,c)(第一坐标恰为 f 因 ka+mb+nc ≡ f)。
from fractions import Fraction from fpylll import IntegerMatrix, LLL from Crypto.Util.number import long_to_bytes
p = 10424356578148041779853991789187969944186570125402901113699573185144158488847151089093649435805832723680640302469301322004769382556869280204369016044400623 k = 2016425917343526209264752974016973527106088400191647819396444997081866888816818440804306653900752825844532111319244334210470353279795203950886189568717273 m = 9640575609666038466312358795458735166723157003124018050805657432015561577987823522956739610343817276374800232163184447140344754253531140765054930193240661 n = 8539207304708818916453730202381072788689351891251165656488809155919585187699733568697903825636944248694317545906020707873051567183468920809837554174735591 f = 3760813688323379339493776734416231127517302841171887658445242754803946122769018586447782634756726656702581791734772105099204609201876825961922712387326893
B = IntegerMatrix(4, 4) for i, r inenumerate([[k, 1, 0, 0], [m, 0, 1, 0], [n, 0, 0, 1], [p, 0, 0, 0]]): for j, v inenumerate(r): B[i, j] = v LLL.reduction(B) red = [[B[i, j] for j inrange(4)] for i inrange(4)]
defdot(a, b): returnsum(x*y for x, y inzip(a, b))
Bv = [[Fraction(x) for x in r] for r in red] m_ = 4 Bstar = [[Fraction(0)] * 4for _ inrange(m_)] for i inrange(m_): v = Bv[i] for j inrange(i): proj = dot(v, Bstar[j]) / dot(Bstar[j], Bstar[j]) v = [v[t] - proj*Bstar[j][t] for t inrange(4)] Bstar[i] = v
target = [Fraction(f), Fraction(0), Fraction(0), Fraction(0)] b = target[:] for i inrange(m_-1, -1, -1): # Babai 最近平面 proj = dot(b, Bstar[i]) / dot(Bstar[i], Bstar[i]) c = round(proj) b = [b[t] - c*Bv[i][t] for t inrange(4)] v = [target[t] - b[t] for t inrange(4)]
题目把 flag 当成一个以 x 为基底的”大整数”求值,再用 32 位随机数 a, b 线性包裹后给出三组数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import uuid from random import getrandbits as grb
flag = "flag{" + str(uuid.uuid4()) + "}" a, b = [grb(32) for _ inrange(2)] # a, b 都是 32 位
deff(s, j): n = len(s) returnsum(ord(s[i]) * j**(n-1-i) for i inrange(n))
pd = {} for _ inrange(3): x = grb(32) pd[x] = a*f(flag, x) + b
分析
f(flag, x) 等于把 flag 的每个字符 ASCII 码当作 base-x 进制下的数字位:高位是 ord('f'),低位是 ord('}')。所以只要能确定 a, b,把 (y - b) // a 在 base-x 下展开就能逐位还原出 flag 的 ASCII 码。
y_i = a·f(flag, x_i) + b,于是任意两个 y_i 之差都是 a 的倍数:
1
y1-y2 = a*(f(flag,x1)-f(flag,x2))
对三组差取 gcd 得到 g = a * c,其中 c 是叉积差值的公因子。所以真正的 a 是 g 的某个因子。b 不一定是 y % a(b 可能大于 a),因此枚举 a = gcd 的每个因子,并遍历 b = (y % a) + k*a, k = 0,1,2,...,只要 base-x 展开后的每一位都在 [32, 126] 且以 flag{ 开头、} 结尾即命中。
xs = list(pd.keys()) ys = list(pd.values()) M = 2**32
g = gcd(abs(ys[0]-ys[1]), abs(ys[1]-ys[2]))
defexpand(val, base): digs = [] while val: digs.append(val % base) val //= base return digs[::-1]
x1 = xs[0] for a insorted(sympy.divisors(g), reverse=True): if a < 2: continue b0 = ys[0] % a ifnotall(y % a == b0 for y in ys): continue for k inrange((M-1-b0)//a + 1): b = b0 + k*a digs = expand((ys[0]-b)//a, x1) iflen(digs) != 42ornotall(32 <= d <= 126for d in digs): continue s = ''.join(chr(d) for d in digs) if s.startswith('flag{') and s.endswith('}'): print('a =', a) print('b =', b) print('FLAG =', s) raise SystemExit(0)
输出:
1 2 3
a = 2146801046 b = 2680882101 FLAG = flag{a9eef27e-2229-4110-a28f-42f7f007c06d}
FLAG
1
flag{a9eef27e-2229-4110-a28f-42f7f007c06d}
随机数之旅 1.3
题目信息
题号:84
类型:LCG 线性同余
FLAG:动态 FLAG
题面
想恢复 flag,但直接给了一个”最旧最冷”的配置:有一个未知的线性同余生成器,把 flag 的值混了进去:
1 2 3 4 5 6 7
m = bytes_to_long(flag.encode()) p = getPrime(m.bit_length()+3) a = getPrime(p.bit_length())
hint = [random.randint(1, p-1)] for i inrange(10): hint.append((a*hint[-1]+m) % p)
分析
序列满足一阶线性递推:
1
h[k+1] = a*h[k] + m (mod p)
两式相减消去 m:
1
h[k+2] - h[k+1] = a*(h[k+1] - h[k]) (mod p)
于是 a = (h[2]-h[1]) * (h[1]-h[0])^{-1} (mod p),再代回第一式得 m = h[1] - a*h[0] (mod p)。m 就是 flag 的长整数形式,long_to_bytes 即还原。
p = 478475545597700801137542329947268027178596565166277501475984783168264336204134464479893480035711325623 hint = [ 249919247565764496968024420668100990050724930264873012553221627994767139138419916559737152956192938786, 341098538517870638403021803297435486563954299904421591195678329627022088404800269966659959073623486227, 20018219100052262465673657639106096626775270934552714906385093540517665089433306304783945869390965352, 477110987927537932362183022083084081803652185884243696031637228688890267574215943741789667631285188517, 316109317526042308856009312339591028959770431193022541894694590723163440242617594274841279773268292931, 288838512929949193288464156452590499193348618769922838206940876596503314942400180385295933551444987426, 181266945000896484248052902194760405660042158622313374086868842724033187572461235292532472052806294610, 363891817161955280083221864938995130581363107122643810787521989924285652140760869565757181912307151144, 176158258425616548246181359314308658522975855113878838400631572536985398273419876407488652665740506588, 226304243444318985869957901105733987782986057182483943969163921743774283862329285859875298207849486395, 235563126973016483026307105002236457145848856279569924823679216801904771557144382780782533443602319128, ]
a = (hint[2] - hint[1]) * pow(hint[1] - hint[0], -1, p) % p m = (hint[1] - a*hint[0]) % p print("a =", a) print("FLAG =", long_to_bytes(m).decode())
输出:
1 2
a = 50284842668591874286962530711840222441575267222168631627346628930023136944986518242285511306089960820 FLAG = flag{3ea753dc-8d46-41f7-b4a6-e828c0253831}
FLAG
1
flag{3ea753dc-8d46-41f7-b4a6-e828c0253831}
随机数之旅 1.9
题目信息
题号:85
类型:LCG(模数未知)
FLAG:动态 FLAG
题面
和 1.3 几乎一样,但这次没有直接给出模数 p:
1 2 3 4 5 6 7
m = bytes_to_long(flag.encode()) p = getPrime(m.bit_length()+3) a = getPrime(p.bit_length())
hint = [random.randint(1, p-1)] for i inrange(15): hint.append((a*hint[-1]+m) % p)
from math import gcd from Crypto.Util.number import long_to_bytes, isPrime
hint = [ 207815833858860472630525746720294722862686098236015762403351705374683468788325370179356514749526876950, 211015979308620411696525425095777275753476560571747569104626146643460892934355111246007348590054728278, # ...(完整数据见题目附件 random_jerni1_9.py 注释) ]
d = [hint[i+1] - hint[i] for i inrange(len(hint) - 1)]
G = 0 for i inrange(len(d) - 2): for j inrange(i + 1, min(len(d) - 1, i + 6)): cross = abs(d[i+1]*d[j] - d[i]*d[j+1]) G = gcd(G, cross)
p = G for pr inrange(2, 10000): while p % pr == 0: p //= pr assert isPrime(p)
a = (hint[2] - hint[1]) * pow(hint[1] - hint[0], -1, p) % p m = (hint[1] - a*hint[0]) % p
for i inrange(15): assert (a*hint[i] + m) % p == hint[i+1]
print("p =", p) print("a =", a) print("FLAG =", long_to_bytes(m).decode())
输出:
1 2 3
p = 280850935843921831854086310440676685065750764735757538361697628591000158614408642674982565414740868673 a = 204196471214096796071122233870504038461030399942935941771930578997515923491755381980140682166507542100 FLAG = flag{513a05ef-ca04-4e94-af25-a893da4221fe}
defextract_number(self): ifself.mti == 0: self.twist() y = self.mt[self.mti] y = y ^ y >> 11 y = y ^ y << 7 & 0x0d000721 self.mti = (self.mti + 1) % 114 return _int32(y)
deftwist(self): for i inrange(0, 114): y = _int32((self.mt[i] & 0x90000000) + (self.mt[(i+1) % 114] & 0x8fffffff)) self.mt[i] = (y >> 1) ^ self.mt[(i + 66) % 114] if y % 2 != 0: self.mt[i] = self.mt[i] ^ 0x0d000721
hint = [task.extract_number() for _ inrange(114)] key = [task.extract_number() for _ inrange(11)] x = 1 for i in key: x *= i print(hint) print(m^x) # m = bytes_to_long(flag)
from functools import reduce from Crypto.Util.number import long_to_bytes
def_int32(x): returnint(0xFFFFFFFF & x)
N, UP, MASK = 114, 66, 0x0d000721
defundo_right_hash(x, shift): res = 0 for i inrange(31, -1, -1): hi = (res >> (i + shift)) & 1if i + shift < 32else0 res |= ((x >> i) & 1 ^ hi) << i return _int32(res)
deftemper(y): y = y ^ y >> 11 y = y ^ ((y << 7) & MASK) return _int32(y)
deftwist_arr(arr): mt = arr[:] for i inrange(N): y = _int32((mt[i] & 0x90000000) + (mt[(i + 1) % N] & 0x8fffffff)) mt[i] = (y >> 1) ^ mt[(i + UP) % N] if y % 2 != 0: mt[i] ^= MASK return [_int32(v) for v in mt]
hint = [...] # 114 个随机数,见题目注释 enc = 174279382333440272527169405563126775575894462244164992062996670946512594329265894481264929021062073725
S1 = [untemper(y) for y in hint] S2 = twist_arr(S1) key = [temper(S2[i]) for i inrange(11)] x = reduce(lambda a, b: a * b, key) m = enc ^ x print("FLAG =", long_to_bytes(m).decode())
输出:
1
FLAG = flag{e9ef408f-feef-4732-b6d0-77d9813b8f9c}
FLAG
1
flag{e9ef408f-feef-4732-b6d0-77d9813b8f9c}
Weil 的噪声与秩序
题目信息
题号:91
类型:Weil 配对比特编码
题面
一个基于 BLS12-381 类型素数 p 上曲线 E: y² = x³ + 4 的 Weil 配对比特编码题。flag 逐字符转 8 位二进制后按位决定密文:
带二元噪声的”类 LWE”线性系统题:500×30 矩阵 A、模数 p=random_prime(2^64)、秘密 x 分量在 [1,2^32],用 b=A·x+e mod p 加密,噪声 e 的每个分量只取两个固定值 ec[0]、ec[1]。m = c ^ prod(x)。
1 2 3 4 5 6 7 8 9
n = 30 m = 500 p = random_prime(2**64) ec = [random.randint(1, p-1) for _ inrange(2)] e = [random.choice(ec) for _ inrange(m)] A = matrix(Zmod(p), m, n, [random.randint(1, p-1) for _ inrange(m*n)]) x = vector([random.randint(1, 2**32) for _ inrange(n)]) b = A*x + e print("A=", list(A)); print("ec=", ec); print("b=", list(b))
分析
噪声只有两个值,而它们已知。做”白化”把它压成 0/1:
d = ec[1]-ec[0] (mod p),dinv = d⁻¹ mod p
Ap = A·dinv mod p,bp = (b - ec[0])·dinv mod p
则 bp ≡ Ap·x + t (mod p),其中 t_i∈{0,1},即每行都是一个”带二进制小噪声的模线性方程”。500 行方程、30 个未知数、32 位秘密 → 标准的”短噪声 CVP/SVP 嵌入”问题。
lines = open('random_jerni3_9.py').read().split('\n') p = int(lines[30].split('p= ')[1]) A = eval(lines[31].split('A= ')[1]) ec = eval(lines[32].split('ec= ')[1]) b = eval(lines[33].split('b= ')[1]) c = int(lines[34].split('c= ')[1]) m, n = len(A), len(A[0]) d = (ec[1]-ec[0]) % p; dinv = pow(d, -1, p) bp = [((b[i]-ec[0]) % p)*dinv % p for i inrange(m)] Ap = [[(A[i][j] % p)*dinv % p for j inrange(n)] for i inrange(m)]
defsym(r): return r if2*r < p else r - p
q, X = 50, 1 bp2 = [sym(bp[i]) for i inrange(q)] Ap2 = [[sym(Ap[i][j]) for j inrange(n)] for i inrange(q)] dim = q + n + 1 rows = [] for j inrange(n): row = [0]*dim for i inrange(q): row[i] = Ap2[i][j] row[q+j] = X; rows.append(row) for i inrange(q): row = [0]*dim; row[i] = p; rows.append(row) last = [0]*dim for i inrange(q): last[i] = bp2[i] last[dim-1] = 1; rows.append(last)
M = IntegerMatrix(len(rows), dim) for i inrange(len(rows)): for j inrange(dim): M[i, j] = rows[i][j] LLL.reduction(M)
defverify(xs): for i inrange(m): lhs = sum(A[i][j]*xs[j] for j inrange(n)) % p if (b[i]-lhs) % p notin (ec[0] % p, ec[1] % p): returnFalse returnTrue
for idx inrange(min(6, dim)): short = [int(M[idx, j]) for j inrange(dim)] for sgn in (1, -1): nx = [sgn*int(round(short[q+j]/X)) for j inrange(n)] ifall(0 < v <= (1 << 32) for v in nx) and verify(nx): key = 1 for xi in nx: key *= xi msg = c ^ key print("FLAG =", msg.to_bytes(128, 'big').lstrip(b'\x00').decode()) sys.exit(0) print("failed")
输出:
1
FLAG = flag{12b2b60e-7783-4bfa-9e58-f77911a211c1_144a045e-0c31-4e5d-b7b5-7e69ac4344ac_19691419-c2f3-4b43-9955-d24adefd7005}
gen_dlp_with_flag(16, 32, flag):取 16 个 32 位随机素数 p_i,各找一个原根 g_i,令 y_i = g_i^x mod p_i(x 是 flag 的字节整数),再用 CRT 组合 y = CRT(primes, ys)。题目给出模数 N = ∏p_i 与 y。
1 2
N = 309188900849282292730996572442105319804517021637303572285568169372827724672013943204807085606291832819055916540180210625660012888515667353984324438526947 y = 260785984269183342143040042876301128691169473526814133757612160538721419207138445246818874092343133346101040420148945804080352598475162932165207050154918
分析
N 是 16 个小素数之积(每个 32 位),逐素数分解后用 BSGS(Baby-step Giant-step,k₁ + 爆破量级每项关于 ∛p 时间,实际 O(∛p)·表+O(∛p))恢复 x mod (p_i-1),最后 CRT 组合 x。
N 分解:32 位素因子,sympy.ntheory.factorint 秒级完成。
逐素数原根:对 p 的素因子集合 facs,对每个候选 g 检查 pow(g,(p-1)//q,p)!=1 即得原根。
BSGS:x_i = log_{g_i}(y mod p_i) mod (p_i-1)(模数为 32 位,表长 isqrt(p)+1 ≤ 65536,极快)。
from math import isqrt from sympy.ntheory import factorint from sympy.ntheory.modular import crt from Crypto.Util.number import long_to_bytes
N = 309188900849282292730996572442105319804517021637303572285568169372827724672013943204807085606291832819055916540180210625660012888515667353984324438526947 y = 260785984269183342143040042876301128691169473526814133757612160538721419207138445246818874092343133346101040420148945804080352598475162932165207050154918
deffind_primitive_root(p): phi = p - 1 facs = set(factorint(phi)) for g inrange(2, p): ifall(pow(g, phi // q, p) != 1for q in facs): return g
defbsgs(g, h, p): m = isqrt(p) + 1 table = {} e = 1 for j inrange(m): table.setdefault(e, j) e = e * g % p c = pow(g, (p - 2) * m, p) gamma = h for i inrange(m + 1): if gamma in table: return (i * m + table[gamma]) % (p - 1) gamma = gamma * c % p
primes = [p for p, e in factorint(N).items() for _ inrange(e)] xs = [bsgs(find_primitive_root(p), y % p, p) for p in primes] xval = int(crt([p - 1for p in primes], xs)[0]) for p in primes: g = find_primitive_root(p) assertpow(g, xval, p) == y % p, p print('FLAG =', long_to_bytes(xval).decode())
输出:
1 2
x = 50937517511022639871703333483128773651462640640207375656073560302191171538567074566455165 FLAG = flag{D0_y0u_lik3_4i5cr3te_1og@rit6m?}
FLAG
1
flag{D0_y0u_lik3_4i5cr3te_1og@rit6m?}
随机数之旅 3.6
题目信息
题号:87
类型:欠定线性系统恢复 ASCII
FLAG:动态 FLAG
题面
n = len(flag)(flag 为 ASCII 可打印),m = n-6,p = random_prime(2^64),随机矩阵 A ∈ (Z/pZ)^{m×n},b = A·x,其中 x 是 flag 各字符的 ASCII 码向量。题目给出 p、A、b。
1 2 3 4
n = len(flag); m = n - 6 p = random_prime(2**64) A = matrix(Zmod(p), m, n, [random.randint(p//2, p-1) for _ inrange(m*n)]) x = vector([ord(i) for i in flag]); b = A*x
分析
m < n,这是欠定线性系统:方程数比未知数少 6 个,解空间维数为 n-m=6 的仿射子空间。但 x 分量全在 32..126(可打印 ASCII),这是一个”短解”约束 → 用**零空间格 + Babai 最近平面(CVP)**恢复。
思路:
对增广矩阵做模 p 行化简(高斯消元)找到主元列,得特解 x0 与零空间基 Ns(6 个向量)。一般解 x = x0 + Σ c_l·N_l (mod p)。
把零空间基拼进格(每行 Ns[l] + 对角 p),LLL 化,得到约减正交基。
目标向量 T = -x0,对格做 Babai 最近平面法求最近格点 g,则 x = x0 + g (mod p)。
from fractions import Fraction from fpylll import IntegerMatrix, LLL from sympy import mod_inverse
data = open('output.txt').read().split('\n') p = int(data[0]) Arows = [list(r) for r ineval(data[1])] bvec = eval(data[2]) R, C = len(Arows), len(Arows[0])
M2 = [Arows[i][:] + [bvec[i]] for i inrange(R)] col = 0; pivots = [] for r inrange(R): while col < C: piv = next((rr for rr inrange(r, R) if M2[rr][col] % p != 0), None) if piv isNone: col += 1; continue M2[r], M2[piv] = M2[piv], M2[r] inv = mod_inverse(M2[r][col] % p, p) M2[r] = [(v * inv) % p for v in M2[r]] for rr inrange(R): if rr != r and M2[rr][col] != 0: f = M2[rr][col] M2[rr] = [(a - f * bb) % p for a, bb inzip(M2[rr], M2[r])] pivots.append(col); col += 1; break
free = [c for c inrange(C) if c notin pivots] x0 = [0] * C for i, c inenumerate(pivots): x0[c] = M2[i][-1] Ns = [] for fv in free: nv = [0] * C; nv[fv] = 1 for i, c inenumerate(pivots): nv[c] = (-M2[i][fv]) % p Ns.append(nv)
n = C; k = len(Ns) rows = [[Ns[l][j] % p for j inrange(n)] for l inrange(k)] for j inrange(n): r = [0] * n; r[j] = p; rows.append(r)
B = IntegerMatrix(len(rows), n) for i inrange(len(rows)): for j inrange(n): B[i, j] = rows[i][j] LLL.reduction(B) red = [[int(B[i, j]) for j inrange(n)] for i inrange(len(rows))]
defdot(a, b): returnsum(x * y for x, y inzip(a, b))
Bv = [[Fraction(x) for x in r] for r in red] NB = len(red); Bstar = [[Fraction(0)] * n for _ inrange(NB)] for i inrange(NB): v = list(Bv[i]) for j inrange(i): d = dot(Bstar[j], Bstar[j]) if d == 0: continue proj = dot(v, Bstar[j]) / d v = [v[t] - proj * Bstar[j][t] for t inrange(n)] Bstar[i] = v
defbabai(target): tgt = [Fraction(x) for x in target] b2 = list(tgt) for i inrange(NB - 1, -1, -1): d = dot(Bstar[i], Bstar[i]) if d == 0: continue c = round(dot(b2, Bstar[i]) / d) b2 = [b2[t] - c * Bv[i][t] for t inrange(n)] return [tgt[t] - b2[t] for t inrange(n)]
for trial inrange(8): T = [-x0[j] - trial for j inrange(n)] g = babai(T) x = [(x0[j] + int(g[j])) % p for j inrange(n)] ifall(32 <= z <= 126for z in x): print('FLAG =', ''.join(chr(z) for z in x)) assertall(sum(Arows[i][c] * x[c] for c inrange(C)) % p == bvec[i] % p for i inrange(R)) break
输出:
1 2
FLAG = flag{0b319110-bdfa-411c-957f-50bdabe1fa1c} equation verified OK
from secret import flag; from functools import reduce; from itertools import accumulate; import operator print((lambda z: (a:=7, b:=0b10000011, c := 59, d := (1 << a) - 1, e := list(accumulate(range(d), lambda r, l: (r << 1) ^ b if (r << 1) & (1 << a) else r << 1, initial=1))[1:], g := e + e, h := [0] * (1 << a), [h.__setitem__(r, l) for l, r inenumerate(e)], j := [g[ord(s) % d] for s in z], k := [(lambda q: h[q] if q else0)(reduce(operator.xor, (g[h[j[l]] + h[j[(p - l) % c]]] if j[l] and j[(p - l) % c] else0for l inrange(c)), 0)) for p inrange(c)], "".join(chr(l) for l in k).encode())[-1])(flag))
a=7; b=0b10000011; c=59; d=(1<<a)-1 e=list(accumulate(range(d), lambda r,l: (r<<1)^b if (r<<1)&(1<<a) else r<<1, initial=1))[1:] h=[0]*128 for l,r inenumerate(e): h[r]=l
defgfmul(x,y): # GF(2^7) 乘法, P=x^7+x+1=131 r=0 while y: if y&1: r^=x y>>=1; x<<=1 if x&0x80: x^=131 return r defgfpown(x,pw): r=1 while pw: if pw&1: r=gfmul(r,x) x=gfmul(x,x); pw>>=1 return r
out = b'MfYGCnO`w%\x07zSzejG#kkb\x01\x01%eS?]GO`?]\x03m?`ab`kbnsS]``][?S`C\x1dB?{m' alpha_inv = gfpown(2,126) q=[e[o] if o else0for o in out] flag=''.join(chr(h[gfpown(gfmul(q[(2*l)%c], alpha_inv),64)]+1) for l inrange(c)) print(flag) # flag{Circu1@r_c0nv01u7i0n_0N_v3c70R==5Qu@Ring_A_p01yn0mia!}
o = list(map(int, f'{r1:0128b}{r2:0128b}')) A, b = [], [] for t inrange(128): A.append(o[t:t + 128]) b.append(o[128 + t])
M = [row[:] + [bi] for row, bi inzip(A, b)] where = [-1] * 128 r = 0 for c inrange(128): sel = next((i for i inrange(r, 128) if M[i][c]), None) if sel isNone: continue M[r], M[sel] = M[sel], M[r] for i inrange(128): if i != r and M[i][c]: for j inrange(129): M[i][j] ^= M[r][j] where[c] = r r += 1
x = [0] * 128 for c inrange(128): if where[c] != -1: x[c] = M[where[c]][-1]
mask = 0 for bit in x: mask = (mask << 1) | bit pt = AES.new(mask.to_bytes(16, 'big'), AES.MODE_ECB).decrypt(ct) print(pt[:-pt[-1]])
FLAG
1
flag{124ab3f1-4c3e-4d2a-8e6f-9b5e6c7d8f90}
baby_next
题目信息
题号:306
类型:RSA:Fermat 分解(近邻素数)
题面
q 是对 p 连续调用 114514 次 next_prime 的结果,两素数相距极近,Fermat 分解秒破。
1 2 3 4 5 6 7 8 9
from Crypto.Util.number import * from gmpy2 import next_prime from functools import reduce
p = getPrime(512) q = int(reduce(lambda res, _: next_prime(res), range(114514), p)) # p 之后第 114514 个素数 n = p * q e = 65537 c = pow(m, e, n)
from Crypto.Util.number import long_to_bytes from gmpy2 import isqrt
n = 96742777571959902478849172116992100058097986518388851527052638944778038830381328778848540098201307724752598903628039482354215330671373992156290837979842156381411957754907190292238010742130674404082688791216045656050228686469536688900043735264177699512562466087275808541376525564145453954694429605944189276397 c = 17445962474813629559693587749061112782648120738023354591681532173123918523200368390246892643206880043853188835375836941118739796280111891950421612990713883817902247767311707918305107969264361136058458670735307702064189010952773013588328843994478490621886896074511809007736368751211179727573924125553940385967 e = 65537
a = int(isqrt(n)) + 1 whileTrue: t = a * a - n b = int(isqrt(t)) if b * b == t: p, q = a - b, a + b break a += 1
交互式题目:服务端生成 p, g, a,给出 A = g^a,并允许我们提供 Bob 的公钥 B,随后用 s = B^a 派生 AES-ECB 密钥加密 flag。密钥交换中 Bob 的公钥完全可控,可强制共享密钥退化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
from Crypto.Util.number import * from secret import flag from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad
p = getPrime(512) g = getRandomRange(2, p) a = getRandomRange(2, p) A = pow(g, a, p) # Alice 的公钥 B = int(input("Bob's Public Key: ")) assert B != A s = pow(B, a, p) # 共享密钥 key = sha256(long_to_bytes(s)).digest() cipher = AES.new(key, AES.MODE_ECB) enc = cipher.encrypt(pad(flag, 16)) # AES-ECB 加密 flag
from hashlib import sha256 from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes from Crypto.Util.Padding import unpad import socket, re
host, port = "<host>", <port> so = socket.create_connection((host, port), timeout=15) so.settimeout(15) buf = b'' whileb"Bob's Public Key"notin buf: buf += so.recv(4096) p = int(re.search(rb'The Prime is (\d+)', buf).group(1)) g = int(re.search(rb'The Generator is (\d+)', buf).group(1)) A = int(re.search(rb"Alice's Public Key is (\d+)", buf).group(1))
so.sendall(b'1\n') # B = 1 -> s = 1 buf = b'' whileTrue: try: c = so.recv(4096) ifnot c: break buf += c except socket.timeout: break enc = re.search(rb'Encrypted Flag: ([0-9a-f]+)', buf).group(1).decode()
key = sha256(long_to_bytes(1)).digest() # s = 1 flag = unpad(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc)), 16) print(flag)
FLAG
1
flag{e2838a75-ccef-4089-8e60-6ebf6ae66d7e}
Ez_RSA
题目信息
题号:490
类型:静态 RSA
题面
1 2 3 4 5 6 7 8 9 10
from Crypto.Util.number import * from secret import flag
p, q = [getPrime(256) for _ inrange(2)] n = p * q e = 65537 m = bytes_to_long(flag) c = pow(m, e, n) print(f"n = {n}") print(f"c = {c}")
1 2
n = 5288062996177288067805240670327919739339874127477405321607402348589147491552053048231920112750216696782518281218048178087877077018108705271341382858124037 c = 2454797328903978848197140611862882439826920912955785083080835692389929572917351093371626343669582289242212514789420568997224614087740388703381025018563979
分析
标准 RSA,n 仅 511 bit(两个 256 bit 素数之积),远低于一般安全下限,可直接查 FactorDB 或本地 ECM 秒级分解,得到 p, q 后按常规求 d 解密。
解题步骤
n 仅 511 bit,两个 256 bit 素数之积,可直接查 FactorDB 或本地 ECM 分解。
得到 p, q 后算 phi=(p-1)*(q-1),d = e^{-1} mod phi。
m = c^d mod n,转字节即 flag。
EXP
1 2 3 4 5 6 7 8 9
from Crypto.Util.number import long_to_bytes, inverse
n = 5288062996177288067805240670327919739339874127477405321607402348589147491552053048231920112750216696782518281218048178087877077018108705271341382858124037 c = 2454797328903978848197140611862882439826920912955785083080835692389929572917351093371626343669582289242212514789420568997224614087740388703381025018563979 e = 65537 p = 60979507724530093051797511853954365018147917052474373616663462193464369184711 q = 86718689499194998339746379891242621495538434539975542252458947218776577824467 d = inverse(e, (p - 1) * (q - 1)) print(long_to_bytes(pow(c, d, n)))
FLAG
1
flag{F4ct0rDB_1s_usefu1_r19ht?}
Vigenere
题目信息
题号:491
类型:静态古典密码
题面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from string import digits, ascii_letters, punctuation from secret import flag
from fpylll import IntegerMatrix, LLL from Crypto.Util.number import long_to_bytes
p = 151240196317566398919874094060690044886978001146739221635377812709640347441550250665168046149125216617951660209690860015625296899030453965800801283336223544189902980591153121592938172963303968803995733283426759581586368403208379337416298836517168491618212440911971420911495272791409112867645195821357346746831 h = 124332746104765845147133491132959579184849644379099440465281812273660434050281263409975356196112560300248343107170084466976976410232928660489912629913525776979726428263975968343564076005019264661696777686114079504603568726429498116488469855127100166072195548037981863885014261706582936943023968781022607949646
B = IntegerMatrix(2, 2) B[0, 0] = 1; B[0, 1] = h B[1, 0] = 0; B[1, 1] = p LLL.reduction(B) print(long_to_bytes(abs(int(B[0, 0]))))
FLAG
1
flag{8dc1f4b8-3f4e-4c3e-9d1a-2b5e6f7a8b9c}
ez_lattice
题目信息
题号:606
类型:静态格密码
题面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
assertlen(flag) % 5 == 0 block_size = len(flag) // 5 m_blocks = [bytes_to_long(flag[i*block_size:(i+1)*block_size]) for i inrange(5)] p = getPrime(128)
Noise = [[randrange(1, p) for _ inrange(5)] for _ inrange(4)] Noise.append(m_blocks) M = matrix(Noise) A = make_mask(5, p) C = A * M # 只给出 C
M 共 5 行:前 4 行是 ~128bit 随机噪声,最后一行是 flag 的 5 个分块(每块约 64bit,远小于 p)。
分析
掩码 A 是单位行列式(上三角 × 下三角,det = 1)的整矩阵,因此整数乘法 C = A·M 不改变行空间:C 的行与 M 的行张成同一个格 L。M 最后一行是 5 个约 64 bit 的 flag 分块,噪声行元素约 128 bit,故 flag 行是格 L 中的显著短向量,直接对 C 的行做 LLL 约减即可约出该短向量。
解题步骤
A 由单位行列式的三角矩阵相乘得到,det(A) = 1。
整数矩阵乘法下,C 的行向量与 M 的行向量张成同一个格 L(相差行列式为 ±1 的整线性变换)。
因此 flag 分块行(5 个约 64bit 的小数)是格 L 中的一个短向量,而噪声行元素约 128bit。
对 C 的行直接做 LLL 约减,约减后的第一行就是 flag 分块,long_to_bytes 拼接还原 flag。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from fpylll import IntegerMatrix, LLL from Crypto.Util.number import long_to_bytes import re
txt = open('output.txt').read() p = int(re.search(r'p=(\d+)', txt).group(1)) C = eval(re.search(r'C=(\[\[.*\]\])', txt, re.S).group(1))
n = 5 B = IntegerMatrix(n, n) for i inrange(n): for j inrange(n): B[i, j] = C[i][j] LLL.reduction(B)
row = [int(B[0, j]) for j inrange(n)] print(b''.join(long_to_bytes(abs(x)) for x in row))
输出:b'moectf{h0w_P0werfu1_7he_latt1ce_1s}'
FLAG
1
flag{h0w_P0werfu1_7he_latt1ce_1s}
Ez_wiener
题目信息
题号:605
类型:静态 Wiener 攻击
题面
1 2 3 4
d = getPrime(nbits // 5) # 约 204 bit assert30 * pow(d, 4) < n # Wiener 条件 e = pow(d, -1, phi) c = pow(m, e, n)
分析
题面断言 30·d⁴ < n,即 d 满足 Wiener 界 d < n^{1/4}/3,说明存在小私钥。对 e/n 做连分数展开,其某个收敛子 k/d 恰为真实的 k/d(ed = 1 + kφ),反解 φ 后检验二次方程 x² - (n - φ + 1)x + n = 0 的判别式为完全平方即确认并得到 p, q。
from Crypto.Util.number import long_to_bytes import gmpy2
n = 84605285758757851828457377667762294175752561129610097048351349279840138483398457225774806927631502994733733589395840262513798535197234231207789297886471069978772805190331670685610247724499942260404337703802384815835647029115023558590369107257177909006753910122009460031921101203824769814404613875312981158627 e = 36007582633238869298665544067678113422327323938964762672901735035127703586926259430077542134592019226503943946361640448762427529212920888008258014995041748515569059310310043800176826513779147205500576568904875173836996771537397098255940072198687847850344965265595497240636679977485413228850326441605991445193 c = 25377227886381037011295005467170637635721288768510629994676412581338590878502600384742518383737721726526909112479581593062708169548345605933735206312240456062728769148181062074615706885490647135341795076119102022317083118693295846052739605264954692456155919893515748429944928104584602929468479102980568366803
defcf(a, b): while b: yield a // b a, b = b, a % b
n0, n1, d0, d1 = 0, 1, 1, 0 for q in cf(e, n): n0, n1 = n1, q * n1 + n0 d0, d1 = d1, q * d1 + d0 k, d = n1, d1 if k == 0or (e * d - 1) % k: continue phi = (e * d - 1) // k s = n - phi + 1 disc = s * s - 4 * n if disc >= 0: t = gmpy2.isqrt(disc) if t * t == disc: print(long_to_bytes(pow(c, int(d), n))) break
FLAG
1
flag{Ez_W1NNer_@AtT@CK!||}
ez_DES
题目信息
题号:305
类型:静态 DES 弱密钥爆破
题面
1 2 3
key = 'ezdes' + ''.join(secrets.choice(characters) for _ inrange(3)) cipher = DES.new(key.encode(), DES.MODE_ECB) # c = b'\xe6\x8b0\xc8m\t?\x1d\xf6\x99sA>\xce \rN\x83z\xa0\xdc{\xbc\xb8X\xb2\xe2q\xa4"\xfc\x07'
import itertools, string from Crypto.Cipher import DES
c = b'\xe6\x8b0\xc8m\t?\x1d\xf6\x99sA>\xce \rN\x83z\xa0\xdc{\xbc\xb8X\xb2\xe2q\xa4"\xfc\x07' chars = string.ascii_letters + string.digits + string.punctuation for t in itertools.product(chars, repeat=3): key = ('ezdes' + ''.join(t)).encode() pt = DES.new(key, DES.MODE_ECB).decrypt(c) if pt.startswith(b'moectf{'): print(pt, key) break
FLAG
1
flag{_Ju5t envmEra+e.!}
lit_elgamal_handshake
题目信息
题号:805
类型:静态 ElGamal,私钥泄露
题面
附件直接给出公钥 (p,g,y)、密文 (c1,c2),以及被误打印的长期私钥 x。
分析
ElGamal 解密需要共享密钥 s = c1^x (mod p),而附件错误地打印了长期私钥 x,等于把私钥直接交出。拿到 s 后 m = c2 · s⁻¹ (mod p) 即得明文。
解题步骤
共享密钥 s = c1^x mod p。
m = c2 * s^{-1} mod p。
long_to_bytes(m)。
EXP
1 2 3 4 5 6 7 8
from Crypto.Util.number import long_to_bytes, inverse
p = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651 c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627 c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654 x = 633366293219022684108628483753423657477324253833657141033762971761747669344649667887002347907882241246119223126492863291886751205505360049793728851371884 s = pow(c1, x, p) print(long_to_bytes(c2 * inverse(s, p) % p))
FLAG
1
flag{elgamal_leak_makes_happy_decrypt}
rsa_neighbor
题目信息
题号:806
类型:静态 Fermat 分解
题面
标准 RSA,n,e,c 直接给出。题名 neighbor 提示 p,q 接近。
分析
题名 neighbor 提示 p, q 取值接近。若 p, q 接近,则满足 Fermat 分解条件:从 a = ⌈√n⌉ 出发逐次加一,检查 a² - n 是否为完全平方数,首个成立的 a ± b 即为 p, q。本组数据 0 次迭代即命中。
解题步骤
a = isqrt(n)+1,检查 a^2-n 是否完全平方。
本题 0 次迭代即分解,q-p = 1135234。
常规 RSA 解密。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from Crypto.Util.number import long_to_bytes, inverse from gmpy2 import isqrt
n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911 c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429 e = 65537 a = int(isqrt(n)) + 1 whileTrue: t = a * a - n b = int(isqrt(t)) if b * b == t: p, q = a - b, a + b break a += 1 print(long_to_bytes(pow(c, inverse(e, (p - 1) * (q - 1)), n)))
key = b'Slightly different from the AES.' enc = b'%\x98\x10\x8b\x93O\xc7\xf02F\xae\xedA\x96\x1b\xf9\x9d\x96\xcb\x8bT\r\xd31P\xe6\x1a\xa1j\x0c\xe6\xc8' expanded = key_expansion(bytearray(key)) pt = b''.join(decrypt_block(enc[i:i+16], expanded) for i inrange(0, 32, 16)) print(pt)
FLAG
1
flag{Th1s_1s_4n_E4ZY_AE5_!@#}
ez_det
题目信息
题号:601
类型:静态矩阵掩码
题面
5×5 矩阵 M 前 4 行是噪声,最后一行 [m,0,0,0,0]。左乘 det=1 的掩码 A 得 C,并给出前 4 行噪声。
分析
C = A·M,M 前 4 行是已知噪声、最后一行是 [m, 0, 0, 0, 0]。C 的第 1~4 列只由 A 的前 4 列与噪声决定,与未知的 m 无关,因此可在有理数域上从 C[:,1:] = A[:,:4]·Noise[:,1:] 反解出整数的 A 前 4 列;再用第 0 列消去噪声贡献,剩余向量为 A[:,4]·m,对其各分量取 gcd 即得 m。
解题步骤
C[:,1:] = A[:,:4] * Noise[:,1:],在有理数上求逆得到 A 的前 4 列(整数)。
C[:,0] - A[:,:4] * Noise[:,0] = A[:,4] * m。
该向量各分量的 gcd 即 m。
EXP
1 2 3 4 5 6 7 8 9 10 11
from math import gcd from sympy import Matrix, Integer from Crypto.Util.number import long_to_bytes
N, C = Matrix(Noise1), Matrix(C) Aleft = (C[:, 1:] * N[:, 1:].inv()).applyfunc(lambda x: Integer(x)) w = C[:, 0] - Aleft * N[:, 0] g = 0 for x in w: g = gcd(g, abs(int(x))) print(long_to_bytes(g))
FLAG
1
flag{D0_Y0u_kn0w_wh@7_4_de7erm1n@n7_1s!}
ezlegendre
题目信息
题号:602
类型:静态勒让德符号
题面
对 flag 的每个比特 b,输出 n = (a + b*d)^e mod p,e 为 16-bit 奇素数,d ∈ [1,10]。
分析
e 是奇素数,故 (n/p) = ((a + b·d)^e/p) = ((a + b·d)/p)(勒让德符号的幂不变性)。比特 b = 0 时恒有 (n/p) = (a/p);比特 b = 1 时 (a + d)/p 与 (a/p) 以大概率不同(d ∈ [1,10] 很小,配合题目构造保证可区分)。逐比特比较符号即可还原二进制串。
解题步骤
e 为奇数,勒让德符号 (n/p) = ((a+b d)/p)。
b=0 时与 (a/p) 相同;b=1 时 d 很小,(a+d/p) 与 (a/p) 不同。
比较 (n/p) 与 (a/p) 还原比特串。
EXP
1 2 3 4 5
p = 258669765135238783146000574794031096183 a = 144901483389896508632771215712413815934 la = pow(a, (p - 1) // 2, p) bits = ['0'ifpow(n, (p - 1) // 2, p) == la else'1'for n in ciphertext] print(int(''.join(bits), 2).to_bytes(len(bits) // 8, 'big'))
FLAG
1
flag{Y0u_h@v3_ju5t_s01v3d_7h1s_pr0b13m!}
ezHalfGCD
题目信息
题号:608
类型:静态相关消息 / 多项式 GCD
题面
e=11,同时给出 d^e、phi^e、m^e(均 mod n)。e d - k phi = 1,k < e。
分析
题面同时给出 d^e、φ^e、m^e (mod n),且 ed = 1 + kφ、k < e = 11。令 f(X) = X^e - φ^e、g(X) = (1 + kX)^e - e^e·d^e,两者在模 n 下共享根 X = φ(因 (ed)^e ≡ (1 + kφ)^e (mod n)),对每个候选 k 求多项式 GCD,GCD 降到一次式时即解出 φ,进而分解 n。
解题步骤
整数上 e d = 1 + k phi,故 (e d)^e ≡ (1 + k phi)^e (mod n)。
令 f(X)=X^e - enc_phi,g(X)=(1+k X)^e - e^e enc_d。
枚举 k=1..10,对 f,g 做模 n 多项式 GCD;k=10 时得到一次式,解出 phi。
p+q = n-phi+1,判别式平方即分解,再解 RSA。
EXP
1 2 3 4 5 6
# k = 10 时 gcd(f, g) 为一次多项式,得 phi s = n - phi + 1 delta = isqrt(s * s - 4 * n) p, q = (s + delta) // 2, (s - delta) // 2 d = inverse(11, (p - 1) * (q - 1)) print(long_to_bytes(pow(enc_flag, d, n)))
c = b"\x0c\xdb'`\xc91\xf7\x05\x91+\x0fM\xed\xbc\x9b\xf1\xd8D\xcd\xfd\x0c\xb9\xb6\xb2J<\x86\x19\x06K\xb3\xa2\xa4\x18\x87<v\xac\x1bbu#\xaa\xb5I\x7f\xd8\xd3" prefix = b'LitCTF2026!!!' for a inrange(256): for b inrange(256): for d inrange(256): pt = AES.new(prefix + bytes([a, b, d]), AES.MODE_ECB).decrypt(c) if pt.startswith(b'litctf{'): print(pt) raise SystemExit
from Crypto.Util.number import inverse from Crypto.Cipher import AES from hashlib import sha256
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff a_ = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc P = (96072097493962089165616681758527365503518618338657020069385515845050052711198, 106207812376588552122608666685749118279489006020794136421111385490430195590894) Q = (100307267283773399335731485631028019332040775774395440323669585624446229655081, 22957963484284064705317349990185223707693957911321089428005116099172185773154) ct = bytes.fromhex('3ae55ed273926b589612b764541a6d9486cd2e842a2d93b5148d999492fa4345' 'bd01263fe10166ef8fe3131396a60fc0')
defto_jac(P1): return (P1[0], P1[1], 1)
defto_aff(P1): X, Y, Z = P1 zi = inverse(Z, p); zi2 = zi * zi % p return (X * zi2 % p, Y * zi2 * zi % p)
defdbl_jac(P1): X1, Y1, Z1 = P1 A = X1 * X1 % p; B = Y1 * Y1 % p; C = B * B % p D = 2 * ((X1 + B) * (X1 + B) - A - C) % p E = (3 * A + a_ * pow(Z1, 4, p)) % p F = E * E % p X3 = (F - 2 * D) % p Y3 = (E * (D - X3) - 8 * C) % p Z3 = 2 * Y1 * Z1 % p return (X3, Y3, Z3)
defadd_jac(P1, P2): if P1 isNone: return P2 if P2 isNone: return P1 X1, Y1, Z1 = P1; X2, Y2, Z2 = P2 Z1Z1 = Z1 * Z1 % p; Z2Z2 = Z2 * Z2 % p U1 = X1 * Z2Z2 % p; U2 = X2 * Z1Z1 % p S1 = Y1 * Z2 * Z2Z2 % p; S2 = Y2 * Z1 * Z1Z1 % p if U1 == U2: return dbl_jac(P1) if S1 == S2 elseNone H = (U2 - U1) % p; I = 4 * H * H % p; J = H * I % p r = 2 * (S2 - S1) % p; V = U1 * I % p X3 = (r * r - J - 2 * V) % p Y3 = (r * (V - X3) - 2 * S1 * J) % p Z3 = 2 * Z1 * Z2 * H % p return (X3, Y3, Z3)
defmul_jac(k, P1): R = None while k: if k & 1: R = add_jac(R, P1) P1 = dbl_jac(P1); k >>= 1 return R
step = 1 << 20 minusP = (P[0], (-P[1]) % p, 1) baby = {} cur = (Q[0], Q[1], 1) for j inrange(step): baby[to_aff(cur)] = j cur = add_jac(cur, minusP)
giant = mul_jac(step, P) cur_j = None s = None for i inrange(0, (1 << 40) // step + 2): affine = to_aff(cur_j) if cur_j isnotNoneelseNone if affine in baby: s = i * step + baby[affine] break cur_j = giant if cur_j isNoneelse add_jac(cur_j, giant)
同一明文 m 用同一模数 n 分别以 e1 = 65537、e2 = 17 加密,且 gcd(e1, e2) = 1,构成经典共模攻击。用扩展欧几里得求 a·e1 + b·e2 = 1,则 m = c1^a · c2^b (mod n)(负指数用模逆处理),即可绕过分解 n 直接恢复明文。
解题步骤
gcd(e1, e2) = 1,用扩展欧几里得求 a*e1 + b*e2 = 1。
m = c1^a * c2^b mod n(指数为负时用模逆)。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from Crypto.Util.number import long_to_bytes, inverse import gmpy2
a, b = gmpy2.gcdext(e1, e2)[1], gmpy2.gcdext(e1, e2)[2] a, b = int(a), int(b) m = 1 if a >= 0: m *= pow(c1, a, n) else: m *= pow(inverse(c1, n), -a, n) m %= n if b >= 0: m *= pow(c2, b, n) else: m *= pow(inverse(c2, n), -b, n) m %= n print(long_to_bytes(m))
FLAG
1
flag{ZeroG_common_modulus_attack}
Lunar LCG
题目信息
题号:824
类型:LCG 状态恢复 + 流密码 XOR
题面
m = 2^127 - 1 素数,LCG state = (a*state + c) mod m。加密前泄露 6 个连续状态,加密时每个明文字节与 state & 0xff 异或。
由相邻三个状态解出 a = (s2-s1)/(s1-s0) mod m、c = s1 - a*s0 mod m。
从最后一个泄露状态继续迭代,取低 8 位与密文异或还原明文。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
from Crypto.Util.number import inverse
mm = 170141183460469231731687303715884105727 leaks = [48077378362307815584689819960136019875, 100310108693164117002347749113390493183, 145646689101109657050476193569066602802, 63949818470656288394594660187785964270, 46314465195318558087862397882705709486, 103138436636073932218183299598776830813] a = (leaks[2] - leaks[1]) * inverse((leaks[1] - leaks[0]) % mm, mm) % mm c = (leaks[1] - a * leaks[0]) % mm state = leaks[-1] ct = bytes.fromhex('39fe07de62fdc9bf74bbbcbd7e202386ca9e40451b46c74968e30fff138a95') out = bytearray() for bl in ct: state = (a * state + c) % mm out.append(bl ^ (state & 0xff)) print(bytes(out))
FLAG
1
flag{ZeroG_lcg_stream_recovery}
Phobos Padding
题目信息
题号:825
类型:RSA 低指数广播攻击(Håstad)
题面
e = 3,同一明文无填充加密到三个不同 n。
分析
e = 3,同一明文 m 无填充加密到三个不同模数,构成 Håstad 广播攻击。由于 m³ < n1·n2·n3,用 CRT 把三个密文合并成 M ≡ m³ (mod n1·n2·n3),再对 M 开三次方根即直接得到整数明文。
解题步骤
CRT 合并三个密文得 M = m^3 mod (n1*n2*n3)。
对 M 取立方根即得明文。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from Crypto.Util.number import long_to_bytes, inverse from math import isqrt
N = n1 * n2 * n3 M = 0 for ni, ci inzip((n1, n2, n3), (c1, c2, c3)): Ni = N // ni M += ci * Ni * inverse(Ni, ni) M %= N m = isqrt(M) while (m + 1) ** 3 <= M: m += 1 while m ** 3 > M: m -= 1 print(long_to_bytes(m))