Symmetric block ciphers Summary - DES & AES
前言
最近要進行密碼學宣講,所以就稍微總結了一下對稱分組密碼,畢竟公鑰密碼(RSA)前面總結過一些常見的了,這裡給上鍊接
skysec.top/2018/08/24/RSA之拒絕套路(1) skysec.top/2018/08/25/RSA之拒絕套路(2) skysec.top/2018/09/13/Crypto-RSA多等式攻擊總結 skysec.top/2018/09/15/淺析RSA-Padding-Attack skysec.top/2018/09/17/Crypto-RSA-公鑰攻擊小結
有興趣的可以自己看看,無非是從公鑰、多同餘式、p/q或是公式推匯入手。
而這裡介紹的對稱分組密碼(DES、AES),主要從其本身和分組模式的問題介紹
DES
DES由於金鑰只有64bit,並且有效位只有僅僅56bit,剩餘8bit為校驗位,所以存在比較顯著的爆破攻擊風險
爆破攻擊
以最新的hitcon2018為例:oh-my-raddit
這道題就可以作為一個爆破攻擊的典例:
1.題目給了大量的密文
2.每個密文對應一篇文章的link
雖然本題乍一看是一道唯密文攻擊的題目,但是不難提取出如下特點:
c1=6540f0e9c6cf744d42db7d895ec887fddbe85217dba80ef15a370279d62741526126bef1904e34a9754531903e10c8cce11b58e11007a26a55ba2433615b18871ffcfab2df695eb93ca92540eb2d0a42 c2=71b09e88f72ba8da76e357af02ad9eab1433743fe85e31a2501049e465bca5e92faefdcb3f7dee61ca73fdc9bc3e4913ab5c2badf4c89831efa48ec2c7fb9d7bb488b51c02b43f9c7f006f8cb3b607a893cdc66682fcb18fc9708af28e08eddfa24dac555b4564836bf984a7b842cee2a58346067fff581424db8959accbdd893ca92540eb2d0a42 c3=02f7e5385d69c591728fb03634bffc6894db69c649886bb48b2a80152681b5b7ead5bb13c0a0aed1a415ab01344a59c039790c9d9e7f8e303e88184ca4649bd719b04ad9b75ad670d4cfd0a444e4da7f3ca92540eb2d0a42
我們看一下長度


不難發現:
1.每組密文的長度都是16的倍數,可以據此猜出大概為8bytes一組
2.每組密文結尾都是 3ca92540eb2d0a42
,不難猜出這應該是Padding
0808080808080808:3ca92540eb2d0a42
那麼爆破即可,如果強行寫指令碼爆破,肯定是非常慢的。
這裡可以使用工具hashcat

其中
-m 14000
意思為

-a 3
意思為


?l?l?l?l?l?l?l?l
意思為
數字?d 小寫字母?l 大寫字母?u 特殊字元?s 大小寫字母+特殊字元?a
這裡意思為純小寫字母的key爆破
弱金鑰
之所以叫弱金鑰,是因為使用這樣的初始金鑰會生成16個相同的子金鑰,這肯定不是我們期望發生的
這樣的弱金鑰有
- 0x0101010101010101
- 0xFEFEFEFEFEFEFEFE
- 0xE0E0E0E0F1F1F1F1
- 0x1F1F1F1F0E0E0E0E
同時還有半弱金鑰,即存在情況

即用K2加密明文,可以用K1解密,這種半弱金鑰有:
- 0x011F011F010E010E:0x1F011F010E010E01
- 0x01E001E001F101F1:0xE001E001F101F101
- 0x01FE01FE01FE01FE:0xFE01FE01FE01FE01
- 0x1FE01FE00EF10EF1:0xE01FE01FF10EF10E
- 0x1FFE1FFE0EFE0EFE:0xFE1FFE1FFE0EFE0E
- 0xE0FEE0FEF1FEF1FE:0xFEE0FEE0FEF1FEF1
參考連結:
ofollow,noindex">https://en.wikipedia.org/wiki/Weak_key#Weak_keys_in_DES子金鑰逆推
如果子金鑰洩露,可以幾乎成功逆推初始金鑰,但由於有效位僅56bit,所以子金鑰只能恢復56bit的子金鑰,還有8bit需要爆破,但 2^8
並不是很大,所以可以容易破解
下面從一道某春秋的例題去看,考察點主要還是在金鑰編排和DES流程分析
注:程式碼非常冗餘,因為是1年前做的題,把指令碼直接扒出來了
.......(程式碼省略) deskey="imnotkey" DES = des(deskey) DES.Kn =[ ........(子金鑰省略) ] DES.setMode(ECB) correct=[ ......(密文省略) ] // DES.encrypt(code)==correct from Crypto.Cipher import Blowfish import base64 key= deskey+code cipher = Blowfish.new(key, Blowfish.MODE_ECB) print cipher.decrypt(base64.b64decode("fxd+VFDXF6lksUAwcB1CMco6fnKqrQcO5nxS/hv3FtN7ngETu95BkjDn/ar+KD+RbmTHximw03g="))
我們首先來看一下程式碼主流程看了什麼:
- 設定了一個未知的deskey
- 然後用這個未知的deskey加密了code
- 然後用deskey+code作為key,呼叫blowfish密碼,加密了flag
然後我們有
- deskey的金鑰編排後的子金鑰
- code加密後的密文correct
- blowfish加密後的密文
所以思路還算清晰:
- 用deskey的子金鑰反推deskey
- 用deskey的子金鑰解密correct得到code
- 用得到的deskey和code作為金鑰解密blowfish密文得到flag
然後我們容易知道DES的金鑰編排過程為:
- 首先輸入64bit金鑰
- 將64bit金鑰經過PC-1盒變成56bit
- 將56bit分為C0和D0,分別28bit
- 將C0,D0分別迴圈左移位,得到C1,D1
- 將C1,D1拼接,經過PC-2盒變成48bit子金鑰key1
- 重複步驟4
- 生成16組子金鑰
所以這裡我有想法:
- 由子金鑰key1經過逆PC-2盒推出C1,D1(得到48位已知和8位未知)
- 由C1,D1分別迴圈右移1位,得到C0,D0
- 由C0,D0經過逆PC-1盒得到deskey(已知48位,未知16位)
- 然後將deskey的16個未知量設定成a,b,c,d……
- 用帶有未知引數的deskey生成16個子金鑰
- 用16個帶未知引數的子金鑰和16個已知子金鑰建立方程組
- 可以解出其中8個bit的未知量,剩餘8個bit不重要,因為deskey實際加密只用了56位金鑰
- 隨機給剩下8bit賦值,作為一個deskey,解密correct
- 爆破剩餘8bit的deskey變數,根據題目特性,應該會有一個可以是明文的字串,即deskey
- 用deskey+code作為key解密blowfish密文,得到flag
那麼我們有子金鑰
DES.Kn =[ ........(子金鑰省略) ]
由子金鑰key1開始逆推:
首先是逆PC-2盒:
key1 = [1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1] __pc2 = [ 13, 16, 10, 23,0,4, 2, 27, 14,5, 20,9, 22, 18, 11,3, 25,7, 15,6, 26, 19, 12,1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 ] C1D1 = ['*']*56 for i in range(0,len(key1)): C1D1[__pc2[i]] = key1[i] print C1D1
可以得到C1D1的值為:
[1, 1, 0, 1, 0, 1, 1, 0, '*', 1, 1, 0, 0, 1, 1, 0, 0, '*', 1, 0, 1, '*', 0, 1, '*', 0, 1, 1, 1, 1, 1, 1, 1, 1, '*', 1, 1, '*', 1, 1, 1, 1, '*', 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, '*', 0, 1]
然後我們迴圈右移動1位逆推出C0D0
C1:11010110*11001100*101*01*011 D1:111111*11*1111*1000000010*01
迴圈右移一位:
C0:111010110*11001100*101*01*01 D0:1111111*11*1111*1000000010*0
然後可以逆PC-1盒得到deskey
C0='111010110*11001100*101*01*01' D0='1111111*11*1111*1000000010*0' __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 ] C0D0 = C0+D0 res = ['*']*64 deskey = "" for i in range(0,len(__pc1)): res[__pc1[i]] = C0D0[i] for i in res: deskey += i print deskey
得到deskey
11000***11**011*0010011*1001011*0111011*11*00*1*1*0*011*1001111*
然後我們給每個未知量替換為變數a,b,c……
得到
11000abc11de011f0010011g1001011h0111011i11j00k1L1m0n011o1001111p
然後我們用這個帶未知量的deskey生成16個子金鑰:
def zuoyiwei(str,num): my = str[num:len(str)] my = my+str[0:num] return my def key_change_1(str): key1_list = [57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4] res = "" for i in key1_list: res+=str[i-1] return res def key_change_2(str): key2_list = [14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32] res = "" for i in key2_list: res+=str[i-1] return res def key_gen(str): key_list = [] key_change_res = key_change_1(str) key_c = key_change_res[0:28] key_d = key_change_res[28:] for i in range(1,17): if (i==1) or (i==2) or (i==9) or (i==16): key_c = zuoyiwei(key_c,1) key_d = zuoyiwei(key_d,1) else: key_c = zuoyiwei(key_c,2) key_d = zuoyiwei(key_d,2) key_yiwei = key_c+key_d key_res = key_change_2(key_yiwei) key_list.append(key_res) return key_list deskey = "11000abc11de011f0010011g1001011h0111011i11j00k1L1m0n011o1001111p" print key_gen(deskey)
得到結果
['101110011111010100011001111100110010101110010111', '1j0n111101d110001m001110101k011110100011be0a0111', '00111010jm100d11111110001011011ae01001111100011b', '1d0111000101110m00101nj1011111b010k00e1111000111', '11j00011d01010110101110m01k1e1101110010b11001a11', '0000110m1111111010n001d1011011101e110101a10010k1', 'n1d1001100111101m11j10101b101k10111101010110101a', '111m1j00111001n011100001e11011a0110111110k10b010', '10n111011011m00j0d1101100k00111e11011b01011110a0', '101001100dm011101110101j1100ba01110111010111k100', '1111101j01110m1d001n0100110010011b0k11101a111e00', '1m001n0010011111j101100d11011001a1011110e0k11101', '010j01ndm111001001111111b00110110101ka101011110e', '1010n11111011101d10000m01001a0e1011110b1101k0101', '01md1010011010111110nj11101b001k0a10101e10110101', '00101111100mdj10n111010110110e110110a0k011010b11']
和題目中的Kn比對:
['101110011111010100011001111100110010101110010111', '110011110111100010001110101001111010001111000111', '001110101010011111111000101101101010011111000111', '110111000101110000101011011111101000011111000111', '111000111010101101011100010111101110010111001011', '000011001111111010000111011011101111010101001001', '011100110011110101111010111010101111010101101010', '111011001110010011100001111011001101111100101010', '100111011011000101110110000011111101110101111000', '101001100100111011101011110010011101110101110100', '111110110111001100100100110010011100111010111100', '100010001001111111011001110110010101111010011101', '010101010111001001111111100110110101001010111101', '101001111101110111000000100100110111101110100101', '010110100110101111100111101100100010101110110101', '001011111000111001110101101101110110000011010111']
我們容易得到8個變數的值,然後得到帶有8個未知數的deskey
"1100001"+c+"1111011"+f+"0010011"+g+"1001011"+h+"0111011"+i+"1110001"+l+"1000011"+o+"1001111"+p
然而這個deskey大家會發現怎麼都不對,我們閱讀題目中給的程式
發現他對deskey的處理:
key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
我們跟進這個__String_to_BitList()
def __String_to_BitList(self, data): """Turn the string data, into a list of bits (1, 0)'s""" if _pythonMajorVersion < 3: data = [ord(c) for c in data] l = len(data) * 8 result = [0] * l pos = 0 for ch in data: for i in range(0,8): result[(pos<<3)+i]=(ch>>i)&1 pos+=1 return result
可以發現這個根本不是原版的pydes庫的函式,我們來看看原版函式:
def __String_to_BitList(self, data): """Turn the string data, into a list of bits (1, 0)'s""" if _pythonMajorVersion < 3: # Turn the strings into integers. Python 3 uses a bytes # class, which already has this behaviour. data = [ord(c) for c in data] l = len(data) * 8 result = [0] * l pos = 0 for ch in data: i = 7 while i >= 0: if ch & (1 << i) != 0: result[pos] = 1 else: result[pos] = 0 pos += 1 i -= 1 return result
容易發現,我們題目中的處理deskey的函式:
- 會先把deskey轉換成64bit的二進位制
- 然後將64bit的2進位制,8個一組進行分組
- 再對每一組倒敘輸出
- 然後再把8組拼接回來
什麼意思呢?
比如
abcdefjhABCDEFJH
他處理後會變成
hjfedcbaHJFEDCBA
所以我們的deskey要進行處理:
deskey_old = '1100001"+c+"1111011"+f+"0010011"+g+"1001011"+h+"0111011"+i+"1110001"+L+"1000011"+o+"1001111"+p'.replace('"+','').replace('+"','') deskey_new = "" for i in range(0,len(deskey_old),8): deskey_new += deskey_old[i:i+8][::-1] print deskey_new
得到
c1000011f1101111g1100100h1101001i1101110L1000111o1100001p1111001
然後我們就可以爆破8bit尋找可讀明文字串了
def bintostr(str): res = "" for i in range(0,len(str),8): res += chr(int(str[i:i+8],2)) return res for c in "01": for f in "01": for g in "01": for h in "01": for i in "01": for L in "01": for o in "01": for p in "01": str = c+"1000011"+f+"1101111"+g+"1100100"+h+"1101001"+i+"1101110"+L+"1000111"+o+"1100001"+p+"1111001" str = bintostr(str) print str
執行程式容易發現,只有一個可見字串:
CodinGay
所以這一定是我們的deskey,後續的步驟就不再寫出了,和這篇文章標題不符,也比較容易了,畢竟有Key,直接解就行了
分組問題
ECB-Replay Attack
ECB模式相對來說,是最簡單的一個模式,如圖

其就是將明文分成多塊,然後加密,那麼這樣就一定會存在重放攻擊的危險,例如

- 銀行A給銀行B傳輸資訊使用ECB模式
- 竊賊C中間攔截下了該訊息
- 竊賊C將密文中的一組替換成了自己的資訊
- 導致銀行B得到的訊息被篡改
我們可以看到,該過程中,C對該密碼演算法的金鑰是完全未知的,他可以向銀行A或者銀行B打錢,這樣就能擁有密文,可以將其中的賬號的密文組摳出來,用來替換
注:當然如果有簽名校驗就是另一回事了,這裡我們單看這個模式的安全性
CBC-Padding Oracle Attack
CBC加密模式:

CBC解密模式:

這裡主要關注一下解密過程
密文cipher首先進行一系列處理,如圖中的Block Cipher Decryption
我們將處理後的值稱為middle中間值
然後middle與我們輸入的iv進行異或操作
得到的即為明文
但這裡有一個規則叫做Padding填充:
因為加密是按照16位一組分組進行的
而如果不足16位,就需要進行填充

比如我們的明文為admin
則需要被填充為admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b
一共11個
\x0b
如果我們輸入一個錯誤的iv,依舊是可以解密的,但是middle和我們輸入的iv經過異或後得到的填充值可能出現錯誤
比如本來應該是admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b
而我們錯誤的得到
admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x3b\x2c
這樣解密程式往往會丟擲異常
(Padding Error)
應用在web裡的時候,往往是302或是500報錯
而正常解密的時候是200
所以這時,我們可以根據伺服器的反應來判斷我們輸入的iv
我們假設middle中間值為(為了方便,這裡按8位分組來闡述)
0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d
正確的解密iv應該為
0x6d 0x36 0x70 0x76 0x03 0x6e 0x22 0x39
解密後正確的明文為:
T E S T 0x04 0x04 0x04 0x04
但是關鍵點在於,我們可以知道iv的值,卻不能得到中間值和解密後明文的值
而我們的目標是隻根據我們輸入的iv值和伺服器的狀態去判斷出解密後明文的值
這裡的攻擊即叫做Padding Oracle Attack
這時候我們選擇進行爆破攻擊
首先輸入iv
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
這時候和中間值middle進行異或得到:
0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d
而此時程式會校驗最後一位padding位元組是否正確
我們知道正確的padding的值應該只有 0x01~0x08
,這裡是 0x3d
,顯然是錯誤的
所以程式會丟擲500
知道這一點後,我們可以通過遍歷最後一位iv,從而使這個iv和middle值異或後的最後一位是我們需要0x01
這時候有256種可能,不難遍歷出
Iv:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3c
Middle:
0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d
兩者異或後得到的是:
0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x01
這時候程式校驗最後一位,發現是0x01,即可通過校驗,伺服器返回200
我們根據這個200就可以判斷出,這個iv正確了
然後我們有公式:
Middle[8]^原來的iv[8] = plain[8] Middle[8]^現在的iv[8] = 0x01
故此,我們可以算出
middle[8] = 0x01^現在的iv[8]
然後帶入式1:
Plain[8] = 0x01^現在的iv[8]^原來的iv
plain[8]= 0x01^0x3c^0x39=0x04
和我們之前解密成功的明文一致(最後4位為填充)
下面我們需要獲取plain[7]
方法還是如出一轍
但是這裡需要將iv更新,因為這次我們需要的是2個0x02,而非之前的一個0x01
所以我們需要將現在的iv[8] = middle[8]^0x02
注:為什麼是 現在iv[8] = middle[8]^0x02
?
現在的iv[8]^middle[8]=伺服器校驗的值
而我們遍歷倒數第二位,應該是2個0x02,所以伺服器希望得到的是0x02,所以
現在的iv[8]^middle[8]=0x02 故此iv[8] = middle[8]^0x02
iv[7]
方法還是和上面一樣,遍歷後可以得到
Iv:
0x00 0x00 0x00 0x00 0x00 0x00 0x24 0x3f
Middle:
0x39 0x73 0x23 0x22 0x07 0x6a 0x26 0x3d
兩者異或後得到的是:
0x39 0x73 0x23 0x22 0x07 0x6a 0x02 0x02
然後此時的明文值:
Plain[7]=現在的iv[7]^原來的iv[7]^0x02
Plain[7] = 0x02^0x24^0x22=0x04
和我們之前解密成功的明文一致(最後4位為填充)
最後遍歷迴圈,即可得到完整的plain
CBC-Byte Flip Attack
這個實際上和padding oracle攻擊差不多

還是關注這個解密過程
但這時,我們是已知明文,想利用iv去改變解密後的明文
比如我們知道明文解密後是1dmin
我們想構造一個iv,讓他解密後變成
admin
還是原來的思路
原來的Iv[1]^middle[1]=plain[1]
而此時
我們想要
構造的iv[1]^mddle[1]=’a’
所以我們可以得到
構造的iv[1] = middle[1]^’a’
而
middle[1]=原來的iv[1]^plain[1]
所以最後可以得到公式
構造的iv[1]= 原來的iv[1]^plain[1]^’a’
所以即可造成資料的偽造
我們可以用這個式子,遍歷明文,構造出iv,讓程式解密出我們想要的明文
CFB-Replay Attack
CFB模式的加解密方式:

這裡有一道例題寫的比較詳細,我就不再贅述:
http://www.ifuryst.com/archives/AES_CFB_Attack.html
後記
後面應該還會繼續做一些補充和探索,因為目前寫的頭疼,所以先寫到這裡。
因為Crypto純屬興趣,畢竟我是個web狗,若文章中有錯誤,還請指出:)