2025第一届轩辕杯--Crypto--WriteUp
2025第一届轩辕杯–Crypto–WriteUp
Crypto
easyrsa
task
e = 65537
n = 1000000000000000000000000000156000000000000000000000000005643
c = 418535905348643941073541505434424306523376401168593325605206
exp
from Crypto.Util.number import inverse, long_to_bytese = 65537
n = 1000000000000000000000000000156000000000000000000000000005643
c = 418535905348643941073541505434424306523376401168593325605206# yafu
p = 1000000000000000000000000000099
q = 1000000000000000000000000000057phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)
m = pow(c, d, n)flag = long_to_bytes(m)
print(b'flag{' + flag + b'}') # b'xuanyuanbei_easy_rsa!'
# flag{xuanyuanbei_easy_rsa!}
简单编码
exp
ciphertext = "ABBAABB ABBABAB ABABAAA ABABAAB ABBBBAA ABBAABA ABABBAA ABBAAAA ABBAAAB ABBABAB ABBBAAA ABAABBB ABABBAA ABABABB ABABBAA ABBABBB ABBABAA ABABABA ABAABAB ABBBAAA ABBBABA ABABBAB ABBBBAA ABABBAB ABBBAAA ABBABAB ABBAABA ABABAAA ABABABA AABBAB ABBBABB ABBAABA ABBABAB AABABA ABBBBAA ABBBAAB ABBAABA AABBAB ABABBAA ABBAAAB ABBBAAA ABBABAB ABBABAA ABABABB ABBBABA ABABABB ABBAABB ABBABAA ABBABAB ABBABAB ABABAAA ABBBABA AABABB ABABBAB AABBAB ABABAAA ABBAAAB ABBBBAB ABBBAAA ABABABA ABBAAAA ABABAAB ABABABB ABBABBA ABBABAB AABABA ABBABAA ABBBABA ABBBABA AABBAA ABBBBAA ABBAAAA ABABBBB ABBABAB ABABABB ABAABBB ABBAAAA ABABAAA ABABABB ABBABAA ABBABBA ABABABA ABAABAB ABABABA AABABB ABABBAB ABBBBAA ABBBBAB ABBBAAA ABABAAB ABBABBB ABABAAB ABBAAAA ABAABAB ABBBABB ABBABAA ABBABAB ABABABA ABAABAB ABBBABA ABBAABA AABBAB ABABBAA ABAABAB ABBBAAA ABBABAB ABBBABA ABAABBB ABABBBA ABABABB ABABBAA ABBABBB ABBABAA ABAABAB ABABABA ABBBAAB ABABBAA ABBAABA ABABBAA ABAABAB ABBBAAA ABBABAB ABBABBB ABBBABB ABBBABA ABABBAA ABBABAB ABABABA ABBAABA ABAABAB ABBAABA ABBABBB ABBBAAA ABBAABA ABBBBAA ABBAAAA ABBABAA ABABBAB ABBABAA ABAABBB ABABABA ABABABB ABABABB AABBAB ABBAAAB ABBBBAB ABABABA ABBBABA AABBAB ABABABA ABBABAB ABBBAAB ABBBAAA ABBAAAB ABBBBAA ABBBBAA ABBABAA ABBAABA AABBAB ABBBABA"
my_dict = {"A":"1", "B":"0"} # 注:因为所有的均为A开头,所以A-->1; B-->0
temp = "".join([" " if c == " " else my_dict[c] for c in ciphertext])
print(temp)
此后,利用随波逐流:
二进制转字符–>base32–>URL–>base64–>栅栏(5)
flag{c04d6e34aab689c5c0e68eb51753c843e032efa7c16427f8642ee07ab946e981}
dp
task
n = 110231451148882079381796143358970452100202953702391108796134950841737642949460527878714265898036116331356438846901198470479054762675790266666921561175879745335346704648242558094026330525194100460497557690574823790674495407503937159099381516207615786485815588440939371996099127648410831094531405905724333332751 dp = 3086447084488829312768217706085402222803155373133262724515307236287352098952292947424429554074367555883852997440538764377662477589192987750154075762783925 c = 59325046548488308883386075244531371583402390744927996480498220618691766045737849650329706821216622090853171635701444247741920578127703036446381752396125610456124290112692914728856924559989383692987222821742728733347723840032917282464481629726528696226995176072605314263644914703785378425284460609365608120126 e = 65537
analysis
dp泄露,RSA攻击基础题型。
exp
from Crypto.Util.number import *n = 110231451148882079381796143358970452100202953702391108796134950841737642949460527878714265898036116331356438846901198470479054762675790266666921561175879745335346704648242558094026330525194100460497557690574823790674495407503937159099381516207615786485815588440939371996099127648410831094531405905724333332751
dp = 3086447084488829312768217706085402222803155373133262724515307236287352098952292947424429554074367555883852997440538764377662477589192987750154075762783925
c = 59325046548488308883386075244531371583402390744927996480498220618691766045737849650329706821216622090853171635701444247741920578127703036446381752396125610456124290112692914728856924559989383692987222821742728733347723840032917282464481629726528696226995176072605314263644914703785378425284460609365608120126
e = 65537for i in range(1, e): if(dp * e - 1) % i == 0:if n % (((dp * e - 1) // i) + 1) == 0: p = ((dp * e - 1) // i) + 1q = n // (((dp * e - 1) // i) + 1)phi = (q - 1) * (p - 1) d = inverse(e, phi) m = pow(c, d, n) print(long_to_bytes(m))
# flag{C5G0_1s_the_8eSt_FPS_G@m3}
babyrsa
task
from Crypto.Util.number import *
from gmpy2 import *
from random import choice
flag = b"flag{****************************}"
m = bytes_to_long(flag)
p = getPrime(256)
q = getPrime(256)
n = p*q
d = getPrime(130)
phi = (p-1)*(q-1)
e = invert(d, phi)
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
# print(f'e = {e}')def gen(bits):while True:p = 2while p.bit_length() < bits:p *= choice(sieve_base)if isPrime(p - 1):return p - 1p1 = gen(256)
q1 = gen(256)
n1 = p1 * q1
c1 = p1 + eprint(f'n1 = {n1}')
print(f'c1 = {c1}')'''n = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363
c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
c1 = 6701513605196718137208327145211106525052740242222174201768345944717813148931922063338128366155730924516887607710111701686062781667128443135522927486682574
'''
analysis
采用了RSA对密文进行了加密,准确给出了n
,c
。接着再使用了RSA对e
进行了隐藏。
我们需要针对于第二次的RSA进行破解恢复e
,之后我们就拿到了第一次RSA的n,e,c
据此进行明文m
的恢复。虽然n
的位数不是很高,但是针对于直接分解,都是有很明确的攻击方向的。
第二次加密的算法利用浏览器搜索可以直接搜到p+1
光滑的攻击方法,此后第一个搜索的话应该可以搜索到winer
攻击。但是根据验证发现并不满足winer
攻击的条件,但是可以顺着winer
攻击的低解密指数的方向寻找,boneh and durfee攻击
找个模板代码进行数据替换即可。
exp
p+1光滑攻击
from itertools import count
from math import gcd, isqrt
from tqdm import trangedef mlucas(v, a, n):v1, v2 = v, (v ** 2 - 2) % nfor bit in bin(a)[3:]: v1, v2 = ((v1 ** 2 - 2) % n, (v1 * v2 - v) % n) if bit == "0" else ((v1 * v2 - v) % n, (v2 ** 2 - 2) % n)return v1def primegen():yield 2yield 3yield 5yield 7yield 11yield 13ps = primegen() # yay recursionp = ps.__next__() and ps.__next__()q, sieve, n = p ** 2, {}, 13while True:if n not in sieve:if n < q:yield nelse:next, step = q + 2 * p, 2 * pwhile next in sieve:next += stepsieve[next] = stepp = ps.__next__()q = p ** 2else:step = sieve.pop(n)next = n + stepwhile next in sieve:next += stepsieve[next] = stepn += 2def ilog(x, b): # greatest integer l such that b**l <= x.l = 0while x >= b:x /= bl += 1return ldef attack(n):for v in count(1):for p in primegen():e = ilog(isqrt(n), p)if e == 0:breakfor _ in trange(e):v = mlucas(v, p, n)g = gcd(v - 2, n)if 1 < g < n:return int(g), int(n // g) # g|nif g == n:break
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
print(attack(n1))
# (647625598040937990477179775340017395831855498212348808173836982264933068647233, 234343806431846981391062476356400447729334179333927516463017977438646752515331973)
boneh and durfee攻击
from Crypto.Util.number import *
import gmpy2n = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363
c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
c1 = 6701513605196718137208327145211106525052740242222174201768345944717813148931922063338128366155730924516887607710111701686062781667128443135522927486682574# print(n1.bit_length()) 526
################################################### p + 1光滑p1 = 647625598040937990477179775340017395831855498212348808173836982264933068647233
q1 = 234343806431846981391062476356400447729334179333927516463017977438646752515331973
assert p1 * q1 == n1 and isPrime(p1) and isPrime(q1)
e1 = c1 - p1
e2 = c1 - q1
print(f"e1 = {e1}")
print(f"e2 = {e2}")"""
e1 = 647625598040937990477179775340017395831855498212348808173836982264933068647233
e2 = 6701513605196718137208327145211106525052740242222174201768345944717813148697578256906281384764668448160487159980777522352135265204110465696876174971350601
"""
# sage
from Crypto.Util.number import *
# from __future__ import print_function
import time############################################
# Config
##########################################"""
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
"""
debug = True"""
Setting strict to true will stop the algorithm (and
return (-1, -1)) if we don't have a correct
upperbound on the determinant. Note that this
doesn't necesseraly mean that no solutions
will be found since the theoretical upperbound is
usualy far away from actual results. That is why
you should probably use `strict = False`
"""
strict = False"""
This is experimental, but has provided remarkable results
so far. It tries to reduce the lattice as much as it can
while keeping its efficiency. I see no reason not to use
this option, but if things don't work, you should try
disabling it
"""
helpful_only = True
dimension_min = 7 # stop removing if lattice reaches that dimension############################################
# Functions
########################################### display stats on helpful vectors
def helpful_vectors(BB, modulus):nothelpful = 0for ii in range(BB.dimensions()[0]):if BB[ii,ii] >= modulus:nothelpful += 1print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful")# display matrix picture with 0 and X
def matrix_overview(BB, bound):for ii in range(BB.dimensions()[0]):a = ('%02d ' % ii)for jj in range(BB.dimensions()[1]):a += '0' if BB[ii,jj] == 0 else 'X'if BB.dimensions()[0] < 60:a += ' 'if BB[ii, ii] >= bound:a += '~'print(a)# tries to remove unhelpful vectors
# we start at current = n-1 (last vector)
def remove_unhelpful(BB, monomials, bound, current):# end of our recursive functionif current == -1 or BB.dimensions()[0] <= dimension_min:return BB# we start by checking from the endfor ii in range(current, -1, -1):# if it is unhelpful:if BB[ii, ii] >= bound:affected_vectors = 0affected_vector_index = 0# let's check if it affects other vectorsfor jj in range(ii + 1, BB.dimensions()[0]):# if another vector is affected:# we increase the countif BB[jj, ii] != 0:affected_vectors += 1affected_vector_index = jj# level:0# if no other vectors end up affected# we remove itif affected_vectors == 0:print("* removing unhelpful vector", ii)BB = BB.delete_columns([ii])BB = BB.delete_rows([ii])monomials.pop(ii)BB = remove_unhelpful(BB, monomials, bound, ii-1)return BB# level:1# if just one was affected we check# if it is affecting someone elseelif affected_vectors == 1:affected_deeper = Truefor kk in range(affected_vector_index + 1, BB.dimensions()[0]):# if it is affecting even one vector# we give up on this oneif BB[kk, affected_vector_index] != 0:affected_deeper = False# remove both it if no other vector was affected and# this helpful vector is not helpful enough# compared to our unhelpful oneif affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):print("* removing unhelpful vectors", ii, "and", affected_vector_index)BB = BB.delete_columns([affected_vector_index, ii])BB = BB.delete_rows([affected_vector_index, ii])monomials.pop(affected_vector_index)monomials.pop(ii)BB = remove_unhelpful(BB, monomials, bound, ii-1)return BB# nothing happenedreturn BB"""
Returns:
* 0,0 if it fails
* -1,-1 if `strict=true`, and determinant doesn't bound
* x0,y0 the solutions of `pol`
"""
def boneh_durfee(pol, modulus, mm, tt, XX, YY):"""Boneh and Durfee revisited by Herrmann and Mayfinds a solution if:* d < N^delta* |x| < e^delta* |y| < e^0.5whenever delta < 1 - sqrt(2)/2 ~ 0.292"""# substitution (Herrman and May)PR.<u, x, y> = PolynomialRing(ZZ)Q = PR.quotient(x*y + 1 - u) # u = xy + 1polZ = Q(pol).lift()UU = XX*YY + 1# x-shiftsgg = []for kk in range(mm + 1):for ii in range(mm - kk + 1):xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kkgg.append(xshift)gg.sort()# x-shifts list of monomialsmonomials = []for polynomial in gg:for monomial in polynomial.monomials():if monomial not in monomials:monomials.append(monomial)monomials.sort()# y-shifts (selected by Herrman and May)for jj in range(1, tt + 1):for kk in range(floor(mm/tt) * jj, mm + 1):yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)yshift = Q(yshift).lift()gg.append(yshift) # substitution# y-shifts list of monomialsfor jj in range(1, tt + 1):for kk in range(floor(mm/tt) * jj, mm + 1):monomials.append(u^kk * y^jj)# construct lattice Bnn = len(monomials)BB = Matrix(ZZ, nn)for ii in range(nn):BB[ii, 0] = gg[ii](0, 0, 0)for jj in range(1, ii + 1):if monomials[jj] in gg[ii].monomials():BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)# Prototype to reduce the latticeif helpful_only:# automatically removeBB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)# reset dimensionnn = BB.dimensions()[0]if nn == 0:print("failure")return 0,0# check if vectors are helpfulif debug:helpful_vectors(BB, modulus^mm)# check if determinant is correctly boundeddet = BB.det()bound = modulus^(mm*nn)if det >= bound:print("We do not have det < bound. Solutions might not be found.")print("Try with highers m and t.")if debug:diff = (log(det) - log(bound)) / log(2)print("size det(L) - size e^(m*n) = ", floor(diff))if strict:return -1, -1else:print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")# display the lattice basisif debug:matrix_overview(BB, modulus^mm)# LLLif debug:print("optimizing basis of the lattice via LLL, this can take a long time")BB = BB.LLL()if debug:print("LLL is done!")# transform vector i & j -> polynomials 1 & 2if debug:print("looking for independent vectors in the lattice")found_polynomials = Falsefor pol1_idx in range(nn - 1):for pol2_idx in range(pol1_idx + 1, nn):# for i and j, create the two polynomialsPR.<w,z> = PolynomialRing(ZZ)pol1 = pol2 = 0for jj in range(nn):pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY)pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY)# resultantPR.<q> = PolynomialRing(ZZ)rr = pol1.resultant(pol2)# are these good polynomials?if rr.is_zero() or rr.monomials() == [1]:continueelse:print("found them, using vectors", pol1_idx, "and", pol2_idx)found_polynomials = Truebreakif found_polynomials:breakif not found_polynomials:print("no independant vectors could be found. This should very rarely happen...")return 0, 0rr = rr(q, q)# solutionssoly = rr.roots()if len(soly) == 0:print("Your prediction (delta) is too small")return 0, 0soly = soly[0][0]ss = pol1(q, soly)solx = ss.roots()[0][0]#return solx, solydef example():############################################# How To Use This Script############################################ The problem to solve (edit the following values)## the modulusN = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363# the public exponent# e = 0x19441f679c9609f2484eb9b2658d7138252b847b2ed8ad182be7976ed57a3e441af14897ce041f3e07916445b88181c22f510150584eee4b0f776a5a487a4472a99f2ddc95efdd2b380ab4480533808b8c92e63ace57fb42bac8315fa487d03bec86d854314bc2ec4f99b192bb98710be151599d60f224114f6b33f47e357517e = 6701513605196718137208327145211106525052740242222174201768345944717813148931274437740087428165253744741547590314279846187850432858954606153257994418035341e2 = 6701513605196718137208327145211106525052740242222174201768345944717813148697578256906281384764668448160487159980777522352135265204110465696876174971350601# the hypothesis on the private exponent (the theoretical maximum is 0.292)delta = .26 # this means that d < N^delta## Lattice (tweak those values)## you should tweak this (after a first run), (e.g. increment it until a solution is found)m = 5 # size of the lattice (bigger the better/slower)# you need to be a lattice master to tweak theset = int((1-2*delta) * m) # optimization from Herrmann and MayX = 2*floor(N^delta) # this _might_ be too muchY = floor(N^(1/2)) # correct if p, q are ~ same size## Don't touch anything below## Problem put in equationP.<x,y> = PolynomialRing(ZZ)A = int((N+1)/2)pol = 1 + x * (A + y)## Find the solutions!## Checking boundsif debug:print("=== checking values ===")print("* delta:", delta)print("* delta < 0.292", delta < 0.292)print("* size of e:", int(log(e)/log(2)))print("* size of N:", int(log(N)/log(2)))print("* m:", m, ", t:", t)# boneh_durfeeif debug:print("=== running algorithm ===")start_time = time.time()solx, soly = boneh_durfee(pol, e, m, t, X, Y)# found a solution?if solx > 0:print("=== solution found ===")if False:print("x:", solx)print("y:", soly)d = int(pol(solx, soly) / e)print("private key found:", d)c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835m = pow(c,d,N)print(long_to_bytes(int(m)))else:print("=== no solution was found ===")if debug:print(("=== %s seconds ===" % (time.time() - start_time)))if __name__ == "__main__":example()
# flag{39693fd4a45b386c28c63100cc930238259891a2}
这道题不知道有没有人尝试硬分解。
告白2009-01-23
- 首先,我在比赛第一天就进行了附件的下载,由于我先进行了
"babyrsa"
的解题,针对于winer
攻击,看起来就有莫名的熟悉感。之后进行winer
攻击即可获得RSA加密的私钥d
.之后我以为这道出在新生赛的题应该就到这里了,然后直接进行了long_to_bytes
发现结果是乱码。
from Crypto.Util.number import *n= 106907120255411141276638612258492580223206670508697860345280705552076099016030935898699700187523599766269485047282325650117035914628760419926410817774570995043643433455055595591107437470658308764074450729921003648782408533657438504280874574703167028727399770901329675528708585142713643443248769642817712218371
e= 92066298664485065396027178362270794902621018857568310802765263839921592653297188141639082907410773099588833460614099675385786190965706296547920850855064908555902716021514756109564555466796584126969045436871844375174789134742417250605776973188216013735765092101366990049447374275811804264794446656219369440535
c= 72413193823586193683552385578931939035012872670413497855056244201691512354415666469936125548748032982020958495114951719066245650644060153838816623502095911253320142088319318206119073607336497914311058118174988818658610257295726356030260769061712429926392969618604615189351858925626182197332313954336604548074############################################################# winer
import gmpy2
import libnumdef continuedFra(x, y):"""计算连分数:param x: 分子:param y: 分母:return: 连分数列表"""cf = []while y:cf.append(x // y)x, y = y, x % yreturn cf
def gradualFra(cf):"""计算传入列表最后的渐进分数:param cf: 连分数列表:return: 该列表最后的渐近分数"""numerator = 0denominator = 1for x in cf[::-1]:# 这里的渐进分数分子分母要分开numerator, denominator = denominator, x * denominator + numeratorreturn numerator, denominator
def solve_pq(a, b, c):"""使用韦达定理解出pq,x^2−(p+q)∗x+pq=0:param a:x^2的系数:param b:x的系数:param c:pq:return:p,q"""par = gmpy2.isqrt(b * b - 4 * a * c)return (-b + par) // (2 * a), (-b - par) // (2 * a)def getGradualFra(cf):"""计算列表所有的渐近分数:param cf: 连分数列表:return: 该列表所有的渐近分数"""gf = []for i in range(1, len(cf) + 1):gf.append(gradualFra(cf[:i]))return gfdef wienerAttack(e, n):""":param e::param n::return: 私钥d"""cf = continuedFra(e, n)gf = getGradualFra(cf)for d, k in gf:if k == 0: continueif (e * d - 1) % k != 0:continuephi = (e * d - 1) // kp, q = solve_pq(1, n - phi + 1, n)if p * q == n:return d
d = wienerAttack(e, n)
-
但是根据
task.py
前面的注释,(起初以为是英文,尝试读了一下,后面发现看不懂,豆包翻译,才发现是德语,注意这里的德语,划重点)。 -
之后我就把我解出的乱码以及提示,翻译丢给了deepseek,发现他给出的解决方法,但是都没能提交成功。之后我去通过群主大大联系出题人大大,被告知题目名称就是hint.在此告一段落……
-
比赛重新开始的第一天,由于附件进行了更新,我在题主不会说一句废话的基础上一个字一个字地扣,首先,
“打不出来给你们挂孙吧”
,我就去搜孙吧,后面越看越发懵,这孙吧是啥东西呀……
然后翻译了一下今天的德语内容:
-
之后我就用过浏览器搜索2009-1-23日表白,在这里我找到了这道题的关键点:
摩斯电码里的爱情 - analyzer - 博客园 -
我醍醐灌顶,昨天
task.py
中的“除了摩斯别的都用了”,以及今天的看着对信中的数字困惑不解。我就明白了,利用winer
攻击获得的私钥d
进行解密m
不要转字节就是原文中经过摩斯之后的内容,之后9键解密内容为:OISTTSEEOWOI
,之后利用单表替换密码进行解密内容为IHLEELCCIBIH
。然后吧,我就想着继续跟着文章走,应该马上就出来了,但是发现根本不是有意义的明文,并且提交不对。 -
这个时候我很懊恼这个脑洞题,注意到我们还有一段提示没有使用,那就是栅栏,我将最后的密文和提示丢给deepseek,发现还是不行,不论是脚本还是想法,都不是正确答案,但是deepseek给我提醒了一下,其中提示中所说的碰倒了第9根和第12根板条,但是栅栏完好无损,deepseek说这两个位置可以是任意的英文字母,这个时候,我明白了,然后我就去爆破。发现还是没有找到有意义的明文。最后我就先不爆破了,我直接用
_
进行占位,看看能不能找到有意义的明文。(为什么是有意义的明文,因为原文章中的I love you too;我觉得出题人不会大费周章地利用随机数的)。 -
之后我还是没有发现什么端倪,之后,我想了想,我要不要直接给deepseek,让他适当地加几个空格获取,毕竟他不会累,但是还是没有收获,这个时候,我想到了,
!!!题目注释是德语写的
会不会,这道题的答案也是德语,所以再次求救deepseek。这次有了新发现"ICH_LIEBE_DICH"是“我爱你”
-
但是我比对这个答案跟我原来求解的字母个数(因为原文是栅栏最后一步),发现我求解的字符个数中多个
L
少个D
.这个时候我要去看看是不是真的通过栅栏解密的,因为原来不知道德语,也看不明吧,这次带着这个相似的答案进行找,就看到了。
-
这里我不知道是出题人故意少个
D
多个L
。最后拿着flag{xxxx}去提交,发现还是不对,这个时候我就去请教群主大大,之后我尝试了如下答案之后,得到最后的flag{ich liebe dich}
.
单表替换的代码:
def decrypt_substitution(ciphertext: str) -> str:"""使用QWE=ABC顺序的单表替换进行解密参数:ciphertext: 密文字符串返回:解密后的明文字符串"""# 构建解密映射表 (QWE对应ABC,依次类推)# 获取字母表alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'# 构建替换表 (Q对应A, W对应B, E对应C, ...)# 这里使用Q作为起始点,按字母表顺序映射key = 'QWERTYUIOPASDFGHJKLZXCVBNM'# 确保key包含所有26个字母且不重复if len(set(key)) != 26 or len(key) != 26:raise ValueError("替换密钥必须包含所有26个字母且不重复")# 构建解密字典 (密文字母 -> 明文字母)decrypt_dict = {k: v for k, v in zip(key, alphabet)}# 执行解密plaintext = []for char in ciphertext.upper():if char in decrypt_dict:plaintext.append(decrypt_dict[char])else:# 非字母字符保持不变plaintext.append(char)return ''.join(plaintext)# 示例使用
if __name__ == "__main__":ciphertext = "Q" # 对应明文"HELL"plaintext = decrypt_substitution(ciphertext)print(f"密文: {ciphertext}")print(f"明文: {plaintext}")
进行flag格式爆破的代码:
s1 = "ICH_LIEBE_LICH"
s2 = "ICH_LIEBE_DICH"
s1 = s1.lower()
s2 = s2.lower()
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")
s1 = s1.replace("_", " ", 2)
s2 = s2.replace("_", " ", 2)
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")
s1 = s1.replace(" ", "", 2)
s2 = s2.replace(" ", "", 2)
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")
前面尝试了六种形式的大写,发现都不对。
注:这个时候有人问为啥要用_
占位,因为随波逐粒进行栅栏的时候有时候会忽略空格,为了少些脚本,我利用_
占位。
DIladila
analysis
给出的分组密码没有采用CBC等分组密码的工作模式,因此我们可以采用逐四字符的穷举攻击,这对于正常的密码学而言是最有效的方式.
至于这个是不是预期解,等出题人大大放出wp吧。
不过经过不断修改代码逻辑,可以根据二分算法的思想,缩小需要爆破的范围,最后可以很会地爆破出来flag。
exp
from Crypto.Util.number import *def ror(val, r_bits, max_bits=16):return (val >> r_bits) | ((val << (max_bits - r_bits)) & (2**max_bits - 1))def decrypt(xys,key):may_xys = []for xy in xys:x,y = xytmp_y = bin(x^y)[2:].zfill(16)may_y = int(tmp_y[-2:]+tmp_y[:14],2)tmp_x = x^keymay_xs = []for xh in range(2 ** 9):for xl in range(2 ** 7):may_x = int(bin(xh)[2:].zfill(9)+bin(xl)[2:].zfill(7),2)if (ror(may_x, 7) + may_y) & 0xFFFF == tmp_x:may_xys.append((may_x, may_y))return may_xysciphertext = [
(57912, 19067),
(38342, 34089),
(16842, 41652),
(30292, 50979),
(9137, 57458),
(29822, 64285),
(33379, 14140),
(16514, 4653)
]
flag=b''
for c in ciphertext:keys = [0x1234, 0x5678, 0x9abc, 0xdef0]keys = keys[::-1]xys=[c]for _ in range(4):xys=decrypt(xys, keys[_])for xy in xys:x,y=xyflag+=(long_to_bytes(x)[::-1]+long_to_bytes(y)[::-1])
print(flag)
# flag{You_DIladila_Crypto_Matser}
古典密码
anlysis
- 给了四种加密,但是说五种,应该另一种就是凯撒。因为凯撒是仿射的特殊情况。单表替换、维吉尼亚、仿射、凯撒的顺序会影响最后的结果。但是栅栏只改变顺序。所以放在第几次解密都可以。
- 其中单表替换的密码表比价容易看出来,同时维吉尼亚的密钥也很容易看出来。接着就是5、8以及4、5.由于仿射加密需要两个密钥,即a,b同时需要满足
gcd(a, 26) = 1
这就表示5、8是仿射加密的密钥,而4、5是栅栏加密的密钥,在经过尝试后发现先5后4进行栅栏解密可以得到flag{}的形式。 - 我只需要爆破4!=24种解密顺序,每次凯撒解密的时候都进行25次密钥的爆破,之后进行先5后4的栅栏。最终寻找有没有
flag
就可以了。
exp
import itertools
from string import ascii_lowercase, ascii_uppercasedef caesar_decrypt(ciphertext, shift):decrypted = []for char in ciphertext:if char.islower():decrypted.append(chr((ord(char) - ord('a') - shift) % 26 + ord('a')))elif char.isupper():decrypted.append(chr((ord(char) - ord('A') - shift) % 26 + ord('A')))else:decrypted.append(char)return ''.join(decrypted)def vigenere_decrypt(ciphertext, key):decrypted = []key_index = 0for char in ciphertext:if char.isalpha():key_char = key[key_index % len(key)]key_shift = ord(key_char.lower()) - ord('a')if char.islower():decrypted.append(chr((ord(char) - ord('a') - key_shift) % 26 + ord('a')))else:decrypted.append(chr((ord(char) - ord('A') - key_shift) % 26 + ord('A')))key_index += 1else:decrypted.append(char)return ''.join(decrypted)def affine_decrypt(ciphertext, a, b):a_inv = 0for i in range(26):if (a * i) % 26 == 1:a_inv = ibreakdecrypted = []for char in ciphertext:if char.islower():decrypted.append(chr((a_inv * (ord(char) - ord('a') - b)) % 26 + ord('a')))elif char.isupper():decrypted.append(chr((a_inv * (ord(char) - ord('A') - b)) % 26 + ord('A')))else:decrypted.append(char)return ''.join(decrypted)def qwe_decrypt(ciphertext):qwe_mapping = {'q': 'a', 'w': 'b', 'e': 'c', 'r': 'd', 't': 'e', 'y': 'f', 'u': 'g', 'i': 'h', 'o': 'i', 'p': 'j','a': 'k', 's': 'l', 'd': 'm', 'f': 'n', 'g': 'o', 'h': 'p', 'j': 'q', 'k': 'r', 'l': 's', 'z': 't','x': 'u', 'c': 'v', 'v': 'w', 'b': 'x', 'n': 'y', 'm': 'z'}decrypted = []for char in ciphertext:if char.isalpha():if char.islower():decrypted.append(qwe_mapping.get(char, char))else:decrypted.append(qwe_mapping.get(char.lower(), char).upper())else:decrypted.append(char)return ''.join(decrypted)def main():ciphertext = "njih{ddolYScoikOWrlctrcc}"vigenere_key = "nxtcctf"affine_a = 5affine_b = 8transformations = [('caesar', lambda x, s: caesar_decrypt(x, s)),('vigenere', lambda x, k: vigenere_decrypt(x, k)),('affine', lambda x, a, b: affine_decrypt(x, a, b)),('qwe', lambda x: qwe_decrypt(x))]# 尝试所有可能的变换顺序for order in itertools.permutations(transformations, 4):# 尝试所有可能的凯撒偏移for caesar_shift in range(26):# 应用变换顺序current_text = ciphertextfor name, func in order:if name == 'caesar':current_text = func(current_text, caesar_shift)elif name == 'vigenere':current_text = func(current_text, vigenere_key)elif name == 'affine':current_text = func(current_text, affine_a, affine_b)elif name == 'qwe':current_text = func(current_text)# 检查是否包含flag格式if 'flag{' in current_text:print(f"找到可能的flag!")print(f"变换顺序: {[t[0] for t in order]}")print(f"凯撒偏移: {caesar_shift}")print(f"明文: {current_text}")print()if __name__ == "__main__":main()
"""
找到可能的flag!
变换顺序: ['vigenere', 'affine', 'qwe', 'caesar']
凯撒偏移: 3
明文: flag{nxtcNBflagNBctfflag}
"""