TSCTF-J2024 密码向WP(5/8)
ezRSA
part 1
#part1
p = getPrime(512)
q = getPrime(512)
n = p * q
phi = (p-1) * (q-1)
d = getPrime(250)
e = inverse(d, phi)
c = pow(bytes_to_long(parts[0]), e, n)
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
# n = 124695452995031270645183837267530422032624508784074534979189655228220709056309638648794696369835930482818980808128467814617220810217534821336503942838791498456112882717378013827550680516551959078234339477401303759763506487676709408813412867364414651706461400252466842182365340612883444737530603407481042520627
# e = 38587713366842463962841747677614707312145479235042165803103947138237921458583509748629042842505955223421671992698976799249425980974893271454942323501453571320592126625468760278770909411858284131095166550317734018153286242950401311537208914820833671566620645882329390747892971373265592565291598852744678229891
# c = 75650426098322108299742769179799276679353245586432433359812352366165787480762021753671012472067826915659840541954505167382050569833945309043431554352347482992118091659840982421987700648585142917347916194810259117115753813661282178558428786374835684668840019166776178494086580424196110694158066034697988927644
容易发现
因此采用 \(\text{Wiener's Attack}\)。
part 2
#part2
p = getPrime(80)
q = getPrime(80)
n = p * q
e = 2
c = pow(bytes_to_long(parts[1]), e, n)
print(f'n = {n}')
print(f'c = {c}')
print(f'p,q = {p,q}')
# n = 649329048322675374317593820985753646586809799201
# c = 283668420132846011615755415362202578109404366325
# p,q = (647260709632957671018359, 1003195526406802286444839)
发现 \(e = 2\) 且 \(p, q\) 较小,采用 \(\text{Rabin}\) 算法。
part 3
#part3
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537
c = pow(bytes_to_long(parts[2]), e, n)
print(f'n = {n}')
print(f'c = {c}')
print(f'p^q = {p^q}')
# n = 85836893651204560884454211125508692415276042143801480450535044733242333318334455339451808653755272841343378345709375676280488068099971805717946097641116078266013229365158341178806480395974457905053516603153056936745020102883744430977333247681929718298626185526512756702624513738698825219177049538628421559753
# c = 75578834548096799626696300881096262997184146142305165096930004492293642496308047534319034721187289042138332386120962890635270628767192988167590315544321566944902760623837249074921079890026503778053466720161607743010204269544291059577848021218234039433294700788160167294093796603060247381385159054508170520277
# p^q = 5068548570505625142069285468439450186210992627026138517591800598908379828953823253346928574075223127205617451260708785889033493211347183583859669806824864
\(p \oplus q\) 已知,考虑从低到高搜索 \(p, q\) 的每一位,对于当前为 \(i\) 满足异或后第 \(i\) 位与 \(p \oplus q\) 相等,且 \(p \times q \le n\),实现剪枝。
part 4
#part4
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 3
m = bytes_to_long(os.urandom(32)+parts[3])
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
print(f'mleak = {m>>128}')
# n = 136909292741753142871542219643510188168311518562065789353531367466023357011784735447560466352669948897633633920102617017949126915203615330337196942328793619163571419292670395849251337340787480297972818664454785647844715189207055430597030032696044932960884594660634280475822683286430432169515120767378120972393
# c = 14763067643592454478187771324072634160297758439803081056226124797870386993054732731754324146768654813122274647054179017048620683751626439261028839186682159969656271385676822426489416369402998625865982105676257668946313225156476831065481561281126267398712822218497384996243578386630794573662378684549962709522
# mleak = 287621732882458207416007037901690948810437972193283953524078189541847647258
明文 \(m\) 的高 \(\text{bit}\) 位已知,低 \(128\) 位未知,根据 \(\text{Coppersmith}\) 定理可求。
ezNumberTheory
part 1
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537
gift = pow(p+q,q,n)-p
m = bytes_to_long(flag[:part_len])
c = pow(m,e,n)
print("n =",n)
print("gift =",gift)
print("c =",c)
已知
考虑二项式展开得到
化简得
不妨考虑拆分模数 \(pq\),得到两个式子。
由于 \(p, q\) 为质数,由费马小定理可得
对 \(gift\) 因式分解得到 \(q\),进而 \(p = \frac{n}{q}\)。
已知 \(e,p,q\),可求出密文 \(m\)。
part 2
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537
m = bytes_to_long(flag[part_len:2*part_len])
gift = pow(n-1,m,n**2)
c = pow(m,e,n)
print("n =",n)
print("gift =",gift)
print("c =",c)
# n = 101825028937892274755066568298675753051915211984336844010001051946487425488926444636462308770518438066690807207349397829434761111870976777194195377994066094978667372161712559828354279213717904934626695765400184018995342438568172381388749973725572499090794995578069624189552411881722996041605959223833931977161
# gift = 10368336518202599155478611962986419931032852827247241544520944151147424100095888793813818133261771295138537638975002709013348196763698154979511238402220870231916534915445557931918512473116396709289462544729254616655147690181935999744990349056920271954901882532521970967307761055252723712876050094383313331140406445004997917905258680326374893836686879404331037481540004243801858344196072242572121743245711481444665694028492376994909442739868671793792049626000877294534619734785195159330183197692177383792865340479971388526007871832995878259364333594949083550807440342049213394072558604884076160696389453987350453566282
# c = 6206002756473719752481539026703107851866702754011241324450943403363532188346664095512595594879972008741307201868011790850666619709952602312346601585431717191460826191100496612662950423560204126590072745286069584415447406165328489464592884522745095517397118876988652114937810284710552486235169949976593170616
已知
不妨先考虑
同上,有
带入 \(gift, n\) 可得
于是 \(m\) 为偶数。
直接展开原式有
化简得
即
求得
根据第一部分 \(m\) 的大小推断 \(m,n\) 大小接近,试除后发现 \(m = \frac{n^2 - gift + 1}{n}\) 恰为整数,故 \(m\) 为所求值。
part 3
p = getPrime(512)
q = next_prime(p)
r = getPrime(512)
n = p * q
e = 65537
m = bytes_to_long(flag[2*part_len:])
gift = (pow(1+m*n,1,n**2)*pow(r,n,n**2))%(n**2)
print("n =",n)
print("gift =",gift)
print("p =",p)
print("q =",q)
# n = 120085278656547434097495160698983440086977117014813250068332400723955573086081539793659925690983763612644098938270646361630753175121916904514302168476109276991065963359696653413330575343172587757112308794397643577457876878076258704713342584783867357600427211623403743168388707834963362860793081706620920977197
# gift = 755365096368274234067764643236574524026724653923904235465391689910808113641191521577417355583242717443863451427293486428108305560959934240539916731737071269870765365812729624798503913486014927177398814766178335408644263304959990001131184599054858122656321624816269434361664736002888774514548687701448375859895656340630928111228145226768972365422917506757004452665088879334351527814068349598913876771933729841797767944898598495453091334481416909244894797229851139025287531257727280929467263018600829848751441007477417666581057493947789435287529403836954674188989033519154270698877075630430517874818318137411379414838
已知
考虑
有
再考虑将 \(n\) 分解有
由费马小定理有
即
考虑欧拉定理 \(a^n \equiv a^{n \text{ mod } \varphi(p)} (\text{mod p})\) 有
记 \(q^{-1}\) 为 \(q\) 模 \(p - 1\) 的逆元,可知
即
由于 \(r\) 与 \(p\) 大小相近,可解出唯一 \(r\)。
回到原式,记 $ ( r^n ) ^ {-1} $ 为 \(r^n\) 模 \(n^2\) 的逆元,有
依旧有 $ m = \frac{(gift \times (r ^ n) ^ {-1} - 1) \text{ mod } n ^ 2 }{n} $,此题完。
ezPwntools
挺过了前面的分P狂魔,单题可以开始轻松了不少。
import random
from Crypto.Util.number import *
from typing import Callable
with open("flag.txt", "r") as f:
flag = f.read().strip()
length = 128
class LCG:
def __init__(self, length=128):
self.length = length
self.setparam()
def setparam(self):
self.m = getPrime(length+1)
self.a = getPrime(length)
self.b = getPrime(length)
self.seed = random.randint(0, self.m - 1)
self.begin = self.seed
def generate(self):
for i in range(10):
self.seed = (self.a * self.seed + self.b) % self.m
seed_list = []
for i in range(5):
self.seed = (self.a * self.seed + self.b) % self.m
seed_list.append(self.seed)
return seed_list
def __call__(self):
return self.begin
def challenge(input:Callable[[str],None], print:Callable[[str],None]):
print("Welcome to TSCTF-J! If you can recover the seed, you will get the flag!")
for i in range(512):
lcg = LCG()
print("Here is your gift:{}".format(lcg.generate()))
i = input(f"Give me the seed: ")
if int(i) != lcg():
print(f"Oh, you are wrong!")
return
print(f"Congurations!{flag}")
观察到 generate
函数每次做 \(15\) 次线性同余变换并返回最后 \(5\) 次结果,可以列出方程。
考虑相邻两项相减消去 \(b\) 有
则对于 \(a\) 有
交叉相乘可得
不难发现
取出上式的最大质因子即可得到 \(m\),接着易于得到 \(a, b\) 的值。
然后解 \(10\) 次一元线性同余方程
对于 \(512\) 次操作,求出 \(m, a, b\),并解出其初值,用 pwntools
与交互库交互即可。
如下是一份代码
from pwn import *
from gmpy2 import *
from sympy import *
con = remote('challenges.hazmat.buptmerak.cn', 21747)
t = con.recvline()
for _ in range(512):
t = con.recvline()
t = t[19:len(t)-1]
ls = t.split()
xi = []
for i in ls:
xi.append(int(i[:len(i)-1]))
x1, x2, x3, x4, x5 = xi
m = gmpy2.gcd((x5-x4)*(x3-x2)-(x4-x3)**2, (x4-x3)*(x2-x1)-(x3-x2)**2)
while is_prime(m) == False:
div = primefactors(m)
m //= div[0]
a = (x5 - x4) * gmpy2.invert(x4 - x3, m) % m
b = (x5 - a * x4) % m
x = x1
for i in range (10, -1, -1):
x = (x - b) * gmpy2.invert(a, m) % m
t = con.recv()
print(_ + 1)
con.sendline(str(x))
t = con.recv()
print(t)
ezECC
#sage
from Crypto.Util.number import *
from sage.all import *
msg = b'???'
flag = b'TSCTF-J{'+msg+b'}'
m = bytes_to_long(flag)
(p,a,b) = (getPrime(len(bin(bytes_to_long(flag)))) for i in range(3))
E = EllipticCurve(GF(p),[a,b])
for i in range(4):
print(E.random_point())
e = 114514
K = E.lift_x(m+1)
eK = e*K
print(f"eK={eK}")
# (121542556343240612458886464797113519174471159973947349739843570016310276547868092596053087152046638137671892191449161589255393370014868931365353180578227117593735, 98840327394835055943257602264613388284774639725847294267402852043945104193135363216749971927532657727021952911622427228151863672295675502233797082353097959401869)
(99873546068894326234875062311058443230105529641172112880280159410919160848811689840010700811925066814781044568587853533466146854172381759747204462070834544581290, 54021704113721739503680080012966661376438637715420865450114718929076757346120207862947548620569435334895396324086781218017455445315421875871714429627637052871264)
(157617948261622464254152314994703598559915540865514420750304511730242939622181332932412360735409968696715108030369878546783001121709970056186086411214753185288604, 172554749510163658517336910991986476106096485848063383199885085041705448141288872247307505628175707155625972718684398442883637022240936050198926361252214588735919 )
(35601692031837010013294196210817440996871532774395053520652383520080490377765688796593595849840043210266473831404618732851470643297944496494254634489110294026906, 83109137089012676168973029782730032714034653700896174331183408691033951856596721848482516578501834370646437639802694578133409539742068584938899820955422945845189 )
# eK=(148943471114336569351357302344843611917995236697008317506292328720097140911033713257363114040728547610446354966023690433690295644571622343830380563979394775174929, 5570936030363753742907439216925560949272269404612411400083981446597152991432704283038752432067099389895408182978262946903003923985122728700021027170168725851130 )
构式题,椭圆曲线密码,做法与 ezNumberTheory
和 ezPwntools
完全一样。
给定域 \(GF(p)\),椭圆曲线 \(E_p: y^2 = x^3 + ax + b\) 上任意 \(4\) 点 \(A_1,A_2,A_3,A_4\),与 \(e\) 倍基点 \(K(m+1, y_{lift\_x})\),\(eK\) 的坐标,求 \(m\)。
对于点 \(A_i(x_i, y_i)\) 有
一样是先消去 \(b\),用 \(a\) 联立两个方程得到 \(p\),然后解出 \(a, b\)。
利用 sage
自带的求椭圆曲线阶的函数 E.order()
,求得阶为 \(P\),这个题就转化为了:
\(K\) 为椭圆曲线 \(E_p\) 上一点,已知 \(eK\),\(e\),求 \(K\)。
求出 \(e\) 关于阶 \(P\) 的逆元即可。
ezFakekey
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from typing import Callable
with open("flag.txt", "r") as f:
flag = f.read().strip()
def pad(data):
pad_len = 16 - len(data) % 16
return data + bytes([pad_len] * pad_len)
def unpad(data):
pad_len = data[-1]
return data[:-pad_len]
def generate_key():
return get_random_bytes(32)
def generate_iv():
return get_random_bytes(16)
def encrypt(message, key, iv):
assert len(key) == 32
assert len(iv) == 16
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(message))
return ciphertext
def decrypt(ciphertext, key, iv):
assert len(key) == 32
assert len(iv) == 16
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext))
return plaintext
def challenge(input:Callable[[str],None], print:Callable[[str],None]):
print("Welcome to TSCTF-J! If you are Administrator, I will give you a flag!")
key = generate_key()
iv = generate_iv()
while True:
print('[E]ncrypt the message')
print('[D]ecrypt the message')
print('[G]et the flag')
print('[Q]uit')
option = input('> ').upper()
if option == 'E':
input_message = input('Plz input your message: ')
m = input_message.encode()
if b'Administrator' in m:
print('Permission denied!')
return
ciphertext = encrypt(m, key, iv)
print(f'Your ciphertext: {ciphertext.hex()}')
elif option == 'D':
ciphertext = bytes.fromhex(input('Plz input the ciphertext: '))
m = ciphertext
plaintext = decrypt(m, key, iv)
print(f'Your plaintext: {plaintext}')
elif option == 'G':
ciphertext = bytes.fromhex(input('Plz input the ciphertext: '))
m = ciphertext
plaintext = decrypt(m, key, iv)
if b'Administrator' in plaintext:
print(f'Congurations!Here is flag:{flag}')
return
else:
print('Permission denied!')
elif option == 'Q':
return
else:
print('Unknown option:', option)
又是我们的交互题,这是一道 AES-CBC
的已知文本攻击题目,我们需要构造一条密文,使得密文经过解密后出现一条子串 Administrator
。
由于 CBC
加密只有相邻的两个块有异或关系,我们可以先通过加密得到含有 administrator
的循环密文,其循环节为 \(16\)(CBC
加密的块大小),找到对应字节所在块的位置,进行遍历搜索解密。
考虑 AES
编码只存在十六进制数,因此找到一个十六进制两位数对其做替换解密,直到出现子串 Administrator
,即是我们所需要的密文,最后发现替换第 \(72,73\) 位可以做到。
代码如下(非预期解)
from pwn import *
from gmpy2 import *
from sympy import *
con = remote('challenges.hazmat.buptmerak.cn', 21815)
ls = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f']
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recv()
con.sendline("E")
t = con.recv()
t = b'administrator' + b'\x03' * 3 + b'administrator'+ b'\x03' * 3 + b'administrator'
con.sendline(str(t))
t = con.recv()
Administ = t[17:]
Administ = Administ[:len(Administ) - 1]
for i in ls:
for j in ls:
A = Administ
ad = A[:72] + i + j + A[74:]
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recv()
con.sendline("D")
t = con.recv()
c = str(ad, "utf-8")
con.sendline(c)
t = con.recvline()
ans = t[20:]
if (b'Administrator' in ans):
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recvline()
t = con.recv()
con.sendline("G")
t = con.recv()
con.sendline(str(c))
t = con.recv()
print(t)
break
Conclusion
对于新生赛而言,题目难度是比较高的,\(8\) 题里面我只做了 \(5\) 题(在此期间和 ACM 队友去集训了一天,不然可能还能做一道题),甚至不乏有 ezECC
这种题目解法与其他题目完全重合的这种题,同时也见识到了各种密码题的解题思路,收益还是比较大的,虽然我想学密码学的原因有一部分是数学,但我希望不要所有的密码题都是数学,ezRSA
中对搜索的考察就是很不错的一点。
成就:在比赛期间快把 Nanachi
和 PYthok
烦死了(不会做题导致的),现在其实也是什么都不会呐,还是任重而道远啊。