2021江西省赛赛后总结(Crypto)
美国大选
程序:
from Crypto.Util.number import *
from secret import p,q
def gcd(a, b):
while b:
a, b = b, a%b
return a
flag='DASCTF{********************************}'
e=3
phi = (p-1)*(q-1)
assert gcd(e,phi)==1
d = inverse(e,phi)
print r"Form of vote:{voter}:{votee}! eg: "
print "Yusa:Trump!"
vote = pow(bytes_to_long("Yusa:Trump!"),d,p*q)
print "vote:",vote
try:
yusa = int(raw_input("Your vote: "))
vote = long_to_bytes(pow(yusa,e,p*q)).split(":")
print vote
if vote[-1] == "Trump!":
print flag[:10]
elif vote[-1] == "Biden!":
print flag[10:]
except Exception as e:
print str(e)
exit()
思路:
可以看出这个交互程序中提供了一个加密的机会,而e是已知的,所以可以通过选择明文来得到n的值:
因为求出的是最大的公约数,所以一定能求出n
实验的程序:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 19 17:36:27 2021
@author: 01am
"""
from Crypto.Util.number import bytes_to_long,long_to_bytes
from gmpy2 import gcd
n=12998510197135204376024977476677066247754836878539929011686148268745119316209020562579171398886840449113325708748228673135311752569187260449619807807903218621065777199171595498055285073001556241300819299111213719824569275786074961700296240844459968460491714678805615009589712788617029938309306389913328704049259197596077475679388746327337019377724684383107233046619031888347065776485290701116620689771500575467431146354734895248116917853679826521686058032549240850391628465710220654255931003952332899029099575800979554751748212159051355321567757716972030886957773638513566062484273620681928826749865617412173047445893
e=3
x=bytes_to_long('当王宝乐醒来的时候,在这梦境迷阵内,已经过去了一天的时间,蛇群的毒没有众人想象中那么剧烈,随行的同学中有人擅长治疗蛇毒,也就使得王宝乐的美好愿望落空。好在随着他的苏醒,名叫周小雅的小白兔对他照顾无微不至,杜敏也罕见的没有与他针锋相对,这就让王宝乐心底舒坦,心里又开始琢磨自己的救人表现,必然会被老师们看到,想来自己这一次考核,应该能加分不少。他唯独郁闷的,就是在之后的几天里,团队众人穿梭丛林,寻找其他同学的路上,柳道斌也不知道吃错了什么药,也许是因为之前的事情愧疚,所以一路上遇到一些小危机,总是抢着带人出手,迅速化解,使得本就虚弱的王宝乐,没有丝毫表现的机会。偏偏又没有出现如蛇群那般大的事件,这就让王宝乐觉得自己一身通天的本领,却没有用武之地,满是郁闷中,只能看着柳道斌在那里不断刷考核分。“这柳道斌再这么下去,说不定隐藏的考核分,就比我高了!”到了最后,王宝乐都焦急了,不过这种情绪没有持续太久,第二天深夜时,在一处一线天的山体下扎营的他们,听到了一声声凄厉的狼嚎。'.encode())
y=bytes_to_long('那声音仿佛可以穿透山石,让所有人都耳骨刺痛,蓦然惊醒,纷纷望去时,立刻看到在他们的前方,那无尽的丛林内亮起了一双双血红色的眼睛。月光下数不尽的凶狼,成扇形包围而来,这些狼群有的在地面飞奔,有的则是跳跃在树枝上,口中发出的狼嚎,目中露出的嗜血,让人望之色变!这一幕,仿佛形成了压抑的狂风,直接就让柳道斌等人面色大变,冷汗淋漓,头皮发麻。“快跑,有狼群!” “是幽骨狼,数不清的幽骨狼!”。一旁的杜敏在经历了蛇群事件后,仿佛一下子就成长了不少,立刻就高呼,让众人进入一线天,利用那里的山堑阻挡狼群。柳道斌脸色变幻不定,最后狠狠一咬牙,面对群狼,并没有立刻撤退,而是召唤同学阻挡拖延时间。小白兔慌乱中扶着王宝乐,身体虽发抖,可却拉着他随人群跑向一线天,只是王宝乐这里,'.encode())
vote = long_to_bytes(pow(x,e,n))
vote2 = long_to_bytes(pow(y,3,n))
c=bytes_to_long(vote)
c2 = bytes_to_long(vote2)
x1=c-pow(x,3)
x2=c2-pow(y,3)
print(gcd(x1,x2))
可以得到n=12998510197135204376024977476677066247754836878539929011686148268745119316209020562579171398886840449113325708748228673135311752569187260449619807807903218621065777199171595498055285073001556241300819299111213719824569275786074961700296240844459968460491714678805615009589712788617029938309306389913328704049259197596077475679388746327337019377724684383107233046619031888347065776485290701116620689771500575467431146354734895248116917853679826521686058032549240850391628465710220654255931003952332899029099575800979554751748212159051355321567757716972030886957773638513566062484273620681928826749865617412173047445893
而下面的过程则是基于n的过大而构建的:
因为n非常大,而e=3又非常的小,所以可以考虑用换一个更小的n1,这样计算出的d满足发送过去的是:
然后发过去后,有:
但是在传过去之后,服务器程序并不会模\(2^{54}\)。
不过无所谓。因为模数n1取\(2^{54}\),所以小于这个数的部分没有影响。而len(bin(bytes_to_long(b':Biden!')))-2=54,这就意味着无论模不模\(2^{54}\),这个数的后54位(二进制)都是不会变的。所以long_to_bytes转过去,最后就会满足条件。
实验过程:
from Crypto.Util.number import bytes_to_long,getPrime,long_to_bytes
#随机制造一个字符串,满足最后是:Biden!
a=bytes_to_long(b'12312312:Biden!')
n=12998510197135204376024977476677066247754836878539929011686148268745119316209020562579171398886840449113325708748228673135311752569187260449619807807903218621065777199171595498055285073001556241300819299111213719824569275786074961700296240844459968460491714678805615009589712788617029938309306389913328704049259197596077475679388746327337019377724684383107233046619031888347065776485290701116620689771500575467431146354734895248116917853679826521686058032549240850391628465710220654255931003952332899029099575800979554751748212159051355321567757716972030886957773638513566062484273620681928826749865617412173047445893
from gmpy2 import invert
#正常的加解密
c=pow(a,3,2**56)
d=invert(3,2**55)
f=pow(c,d,2**56)
print(long_to_bytes(f))
print(long_to_bytes(pow(pow(a,d,2**56),3,n)))#签名中的先用d后用e
>> b':Biden!'
>> b'\x04\x11w`\xb6\xf1\x01\x10\x84q\x98:Biden!'
后面问了一个大佬,他说这个是非预期解,预期解应该是低位逐字节爆破……可惜我不会。
CBC第二课
题目:
from Crypto.Cipher import AES
import os
flag='DASCTF{********************************}'
BLOCKSIZE = 16
def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + chr(pad_len) * pad_len
def unpad(data):
num = ord(data[-1])
return data[:-num]
def _enc(data,key,iv):
cipher = AES.new(key,AES.MODE_CBC,iv)
encrypt = cipher.encrypt(pad(data))
return encrypt
def enc(data,key):
try:
iv = raw_input("Your iv: ").decode('hex')
cipher = AES.new(key,AES.MODE_CBC,iv)
encrypt = cipher.encrypt(pad(data))
return encrypt
except:
exit()
def dec(data,key,iv):
try:
cipher = AES.new(key,AES.MODE_CBC,iv)
encrypt = cipher.decrypt(data)
return unpad(encrypt)
except:
exit()
def task():
try:
key = os.urandom(16)
iv = os.urandom(16)
cipher = _enc(flag,key,iv).encode('hex')
print cipher
paintext = raw_input("Amazing function: ").decode('hex')
print enc(paintext,key).encode('hex')
backdoor = raw_input("Another amazing function: ")
assert backdoor != cipher
if dec(backdoor.decode('hex'),key,iv) == flag:
print flag
else:
print "Wow, amazing results."
except Exception as e:
print str(e)
exit()
if __name__ == "__main__":
task()
思路:
这个题也是一个交互的题,先看解题的要求(服务器输出flag的条件):
- 先用题目给出的偏移向量iv和加密密钥key加密flag并输出结果cipher
- 然后给出一个机会用题目给出的key和自己任选的偏移向量iv加密自定的密文paintext,给出加密后的结果
- 然后要求输入一个与cipher不同的密文,要求该密文解出来的明文(用题目指定的密钥和偏移向量iv)等于flag
- 当以上条件满足后,输出flag
这个题的关键不在于找到一个解密后与flag相同的密文,而是在于pad和unpad函数。
def pad(data):
pad_len = BLOCKSIZE - (len(data) %
BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + chr(pad_len) * pad_len
def unpad(data):
num = ord(data[-1])
return data[:-num]
可以看出,pad函数把最后一块的长度填充到BLOCKSIZE,这样就可以进行和前几块一样的分块加密。但是在加密后还要去除到填充的部分,于是这个pad在填充的时候填充的是填充位数对应的字符,而unpad则读取最后一个字符,转成ASCII码后去掉最后这一填充部分。但这样就会出现一个可以进行长度扩展攻击的机会:对于输出的密文在进行一轮加密,将密文最后一块当做偏移向量(见CBC加密流程图),通过程序开头的flag可以得到最后一块填充了8位,再加上新添加的一块就是16+8=24位。那么用16*chr(24)作为密文,在unpad时可以将新加上的这一块连同之前填充的八位一起去掉,这样就能满足不同的密文解密出来是相同的明文。
所以,从这一道题中,可以学到:不要在pad和unpad中填充有关于明文信息的东西,填入0或者其他什么固定的的东西就好。
CBC第三课
题目:
from Crypto.Cipher import AES
import os
flag='DASCTF{********************************}'
BLOCKSIZE = 16
def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + "=" * pad_len
def unpad(data):
return data.replace("=","")
def enc(data,key):
cipher = AES.new(key,AES.MODE_CBC,key)
encrypt = cipher.encrypt(pad(data))
return encrypt
def dec(data,key):
try:
cipher = AES.new(key,AES.MODE_CBC,key)
encrypt = cipher.decrypt(data)
return unpad(encrypt)
except:
exit()
def s_2_l(data):
s=[]
for i in range(len(data)//BLOCKSIZE):
s.append(data[BLOCKSIZE*i:BLOCKSIZE*(i+1)])
return s
def task():
try:
key = os.urandom(16)
asuy = enc(flag,key)
print asuy.encode('hex')
paintext = raw_input("Amazing function(in hex): ")
paintext = paintext.decode('hex')
print enc(paintext,key).encode('hex')
asuy = raw_input("Another amazing function(in hex): ").decode('hex')
yusa = dec(asuy,key)
flag_l = s_2_l(flag)
yusa_l = s_2_l(yusa)
for each in yusa_l:
if each in flag_l:
print(r"You're not yusa!")
exit()
print yusa.encode('hex')
except Exception as e:
print str(e)
exit()
if __name__ == "__main__":
task()
思路:
这个题跟上一个题差不多,但是把pad和unpad的缺陷弥补了,所以不能利用上一问中的漏洞了。不过这个题的特殊之处在于向量iv和key是一样的,而且还给了一次解密的机会,这样的话,有:
(借了一个大佬博客的图,地址在参考中有)
- 我们把加密的明文设为32个0,这样可以减少随机明文对于过程的干扰。
- 第一轮得到的密文3成为第二轮中的加密向量,而第二轮会得到密文4
- 因为第二轮输入的明文是16个0,这样异或之后,得到的结果还是密文3
- 然后把密文4作为输入进行解密,得到一个返回的回显
- 而这个回显因为长度原因,解密时异或的iv是key,这样的话就会有:密文3^key=回显
- 而密文3已知,回显也已知,那么就有:key=密文3^回显
- 然后正常解密即可
参考: