前幾天我跟我隊友寫了一道逆向,說是逆向題其實是個密碼學的題目
是這樣的:
出題人構造了一個 AES cipher
給出了密鑰(key),明文(plainText),密文(cipherText),使用的是 密碼分組鏈接 CBC(Chiper Block Chaining) 模式。要求出 初始化向量 IV(Initalization Vector)
其實要是熟悉 CBC 模式的話很快就能求出來。
下面是 CBC 的加密流程
加密:
- 先把明文(PlainText)填充(pad)成長度為 16 的倍數
- 根據設定的塊大小(block size)來分組
- 第一組明文(p1)先與初始化向量 IV 進行異或運算得到一個二進制序列(enc_msg)
- 然后使用密鑰(key) 去加密 enc1_msg,得到密文(CipherText)
- 把當前的密文當成下一個明文組(p2)的初始化向量重復上面的流程直到加密完成
PlainText = pad(PlainText)
enc_msg = xor(PlainText, IV)
CipherText = encrypt(key, enc_msg)
解密:
解密是加密的逆過程
- 也是把密文(CipherText)按照每組長度為 16 的倍數來分組
- 然后使用 密鑰(key)解密最后一個分組密文(CipherTextN),得到一個序列(fake_msgN)
- 再把前一個分組(CipherTextN-1)的當成初始化向量 IV,解密 fake_msgN 得到明文 PlainTextN。重復以上流程直到解密完成
fake_msgN = encrypt(key, CipherTextN)
PlainTextN = xor(key, CipherTextN-1)
現在看題目
之前我寫的題目是道逆向題,.net 寫的,為了方便,這個題目是我用 python 復現的
from Crypto.Cipher import AES
key = "09e6855d293a1b86ff44f18948b19bac".decode("hex")
cipherText1 = "ed64978b91ef5b62561a44c8f529b91f".decode("hex")
cipherText = "fd6dd5e0f9ab258b2bc9c813177e3ad677116d2f08c69517d0e7796c1f5e06ba95c3de5a139bb687bf3e779a0730e47c".decode(
"hex")
plainText = "CBC_Cr4cked_succ"
iv = raw_input("give me iv :> ")
aes = AES.new(key, AES.MODE_CBC, iv)
aes1 = AES.new(key, AES.MODE_CBC, iv)
if aes.decrypt(cipherText1) == plainText:
flag = input("give me flag :> ")
if aes1.encrypt(flag) == cipherText:
print("you get it")
else:
print("nonono")
else:
print("nonono")
上面的可以得到的信息
key = "09e6855d293a1b86ff44f18948b19bac".decode("hex")
cipherText1 = "ed64978b91ef5b62561a44c8f529b91f".decode("hex")
cipherText = "fd6dd5e0f9ab258b2bc9c813177e3ad677116d2f08c69517d0e7796c1f5e06ba95c3de5a139bb687bf3e779a0730e47c".decode("hex")
plainText = "CBC_Cr4cked_succ"
iv = ''
現在我們有了明文密文和密鑰,直接逆向 CBC
步驟是這樣的:
- 偽造一個 fakeIV = "aaaaaaaaaaaaaaaa"
- 使用 fakeIV 和 key 去構造 Cipher -- fakeIVAes
- 使用這個 fakeIVAes 去解密 cipherText1,得到一個假的明文 fakePlainText
- 然后把 cipherText1 和 fakeIV 作異或運算得到 enc_msg
- 把 enc_msg 和 plainText 作異或運算就能得到真正的 IV
這里我要講一下第 4,5 步 為什么使用偽造的 IV -- fakeIV 異或 再和 明文 異或就能得到 真的 IV 了呢
我們現在有了 key 和密文和明文,只要再構造一個 假的 IV -- fakeIV 就能構造起一個 Cipher,enc_msg(使用 key 加密后得到的) 異或 fakeIV 得到錯誤的明文(fakePlainText),只要把 fakePlainText 和 fakeIV 異或自然能得到 enc_msg。
像是:
1 ^ 11110 = 11111
11111 ^ 1 = 11110
其實仔細觀察的話會發現 IV 和用 key 加密后的密文(enc_msg)和 明文(cipherText1)是異或(xor)關系。這樣的話只要把 enc_msg 和 cipherText1 作異或運算就能得到 IV,因為 cipherText1 是使用正確 IV 加密過的。
這個是猜解 IV 的 demo 腳本:
iv 是隨機的,運行后會發現 crackIV 和 iv 一樣
import os
from Crypto.Cipher import AES
iv = os.urandom(16)
key = os.urandom(16)
def pad(plainText):
return plainText + (chr(len(plainText)) * (16 - (len(plainText) % 16)))
aes = AES.new(key, AES.MODE_CBC, iv)
plainText = raw_input(">")
print("plainText : " + pad(plainText).encode('hex'))
cipherText = aes.encrypt(pad(plainText))
print("cipherText : " + cipherText.encode("hex"))
iv1 = "a" * 16
aes2 = AES.new(key, AES.MODE_CBC, iv1)
fakePlainText = aes2.decrypt(cipherText)
crackIV = ''
for i in range(16):
crackIV += chr(ord(fakePlainText[i]) ^ ord(iv1[i]) ^ ord(pad(plainText)[i]))
print("True iv : " + iv.encode("hex"))
print("Crack iv : " + crackIV.encode("hex"))
回到上面的題目
題目的解:
from Crypto.Cipher import AES
def xor(p1, p2):
tmp = ''
for i in range(len(p2)):
tmp += chr(ord(p1[i]) ^ ord(p2[i]))
return tmp
key = "\t\xe6\x85]):\x1b\x86\xffD\xf1\x89H\xb1\x9b\xac"
cipherText1 = "ed64978b91ef5b62561a44c8f529b91f".decode("hex")
cipherText = "fd6dd5e0f9ab258b2bc9c813177e3ad677116d2f08c69517d0e7796c1f5e06ba95c3de5a139bb687bf3e779a0730e47c".decode("hex")
plainText = "CBC_Cr4cked_succ"
fakeIV = "aaaaaaaaaaaaaaaa"
fakeIVAes = AES.new(key, AES.MODE_CBC, fakeIV)
fakePlainText = fakeIVAes.decrypt(cipherText1)
enc_msg = xor(fakePlainText, fakeIV)
iv = xor(enc_msg, plainText)
print len(iv)
print "iv is : " + iv
aes = AES.new(key, AES.MODE_CBC, iv)
flag = aes.decrypt(cipherText)
print flag
flag:we_ax{cr4ck_43s_CBC_Cr4cked_succ3ssfu11y!_asdfg}