美國大選
程序:
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^回顯
- 然后正常解密即可
參考: