当曲高和寡不是一个结果的时候,它往往会成为一种错觉,……,若你的思考真的精妙到了只有你自己能理解,那你岂不是更应该将你精妙的思考分享给他人?

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的值:

\[c1=m1^3+k_1n \\ c2=m2^3+k_2n \\ n=gcd(c1-m_1^3,c_2-m_2^3) \]

因为求出的是最大的公约数,所以一定能求出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满足发送过去的是:

\[a^d\ (mod 2^{54}) \\ \]

然后发过去后,有:

\[a^{ed}\ (mod\ 2^{54} ) \\ \]

但是在传过去之后,服务器程序并不会模\(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^回显
  • 然后正常解密即可

参考:

  1. 【20210908 人在寝室坐题从天上来】Crypto方向WP
posted @ 2021-10-19 23:39  01am  阅读(316)  评论(0编辑  收藏  举报