CBC加密流程
(瀏覽了各路大神的博客帖子,終于把這個(gè)研究明白了,所以寫(xiě)出一個(gè)博文當(dāng)做自己的筆記哥遮,把自己的理解加入進(jìn)去,因?yàn)橹皩W(xué)習(xí)時(shí)剛開(kāi)始還是難理解陵究,后面跌跌撞撞的還是明白了眠饮,希望想學(xué)到這個(gè)知識(shí)的朋友可以從這篇文章中得到一些收獲。)
0x1加密:
兩個(gè)單詞:Plain代表明文铜邮。Cipher代表密文
Xor:異或仪召,異或運(yùn)算 例如1 ^ 999 = 998。同樣 998 ^ 999 =1 松蒜,繼續(xù) 999 ^ 1 =998
加密步驟為:
? 1.將明文每16個(gè)字節(jié)分一組扔茅,并設(shè)置一個(gè)初始向量iv,iv為固定的16個(gè)字節(jié)一般是隨機(jī)生成秸苗。(如果最后一組不夠十六個(gè)字節(jié)召娜,則會(huì)通過(guò)補(bǔ)充\x0n (n代表缺少的個(gè)數(shù) )來(lái)填滿,例如第三組目前是10個(gè)字節(jié)难述,那么后面的六個(gè)字節(jié)全部都是\x06萤晴,如果超出10則用16進(jìn)制字符代表例如\x11則表示成\x0b)
? 2.將明文第一組與初始向量iv進(jìn)行xor得出一個(gè)待加密的C。
? 3.將C進(jìn)行一個(gè)函數(shù)加密胁后,這個(gè)函數(shù)是對(duì)稱(chēng)的也是未知的店读,解密的時(shí)候也需要用到這個(gè)函數(shù)。(此時(shí)加密后的C,也就是encrypt(C)我們稱(chēng)他為cipher1)
同理 對(duì)第二組明文進(jìn)行相同操作攀芯,只是此時(shí)的初始向量iv是前一組的密文(也就是cipher1)
? 4.此時(shí)第二組的明文與第一組的密文進(jìn)行xor得到第二組的C屯断。
? 5.將C進(jìn)行同樣的函數(shù)加密生成cipher2。之后的每一組都通過(guò)這樣的方式。
? 6.將所有的cipher組連接到一起殖演,生成了密文Ciphertext氧秘。
下面我們模擬一下cbc加密模式的過(guò)程:
(以下的加密解密過(guò)程都是模擬的,真實(shí)情況是不需要我們?nèi)?xiě)代碼的 拿php舉例趴久。
$ciphertext=openssl($plain,"aes-128-cbc",'***********',OPENSSL_RAW_DATA,$iv)丸相,
這條代碼的功能就是產(chǎn)生一個(gè)Ciphertext,你只需要給它一個(gè)明文彼棍,給他一個(gè)初始iv灭忠,函數(shù)會(huì)自動(dòng)幫助你去生成密文。模擬加密解密過(guò)程是為了幫助我們理解他的加密模式座硕,從而了解此加密模式的漏洞機(jī)制弛作。)
我們還是按照加密過(guò)程來(lái)開(kāi)始:
1.先把明文進(jìn)行文組。
以下的代碼我稱(chēng)未知加密函數(shù)為encrypt()? 解密函數(shù)為decrypt()华匾。Plain1代表的是明文第一組映琳,Plain2代表明文第2組。cipher1表示密文第一組蜘拉,cipher2表示密文第二組萨西。。(每組的密文和明文都是16個(gè)字節(jié)诸尽。密文組和明文組的每一個(gè)字節(jié)都是對(duì)應(yīng)的)也就是說(shuō):
密文組1的第[三]個(gè)字節(jié)=未知函數(shù)加密(明文組1的第[三]個(gè)字節(jié)? XOR 初始IV的第[三]個(gè)字節(jié))
代碼表示為:cipher1[2]=encrypt(plain1[2] Xor IV[2])
建立一個(gè)循環(huán)來(lái)代表可以寫(xiě)成
For($i=0;$i<=15;$i++){
$Cipher1[$i]=encrypt($Plain1[$i] xor $Iv[$i]);
}
這時(shí)分組1的密文已經(jīng)獲取完畢原杂,下面我們模擬一下分組2的密文:
此時(shí)分組2的明文與第一組的密文進(jìn)行xor得到第二組的C,再把C進(jìn)行encrypt函數(shù)加密您机,得到了想要的密文。代碼表示:
For($i=0;$i<=15;$i++){
$Cipher2[$i]=encrypt($Plain2[$i] xor $Cipher1[$i]);
}
同樣分組2的密文已經(jīng)獲取完畢
我們把cipher1和cipher2連接起來(lái)年局,便產(chǎn)生了Ciphertext(這里舉例明文只有32個(gè)字節(jié))
Ciphertext=cipher1.cipher2;
現(xiàn)在我們有了Ciphertext了际看,那么這個(gè)是不是我們從cookie中看到的呢?其實(shí)并不是矢否,因?yàn)榧用芎蟮膬?nèi)容有很多是不可見(jiàn)字符仲闽,如果想讓密文可視化,普遍都采用base64進(jìn)行編碼僵朗,這樣就可以看到了赖欣。但是解密的時(shí)候一定要再解碼回去才可以解密。
0x2:解密
解密的過(guò)程:
? 1.將Ciphertext密文分組验庙。
? 2.用函數(shù)對(duì)第一組的密文解密顶吮,然后和IV進(jìn)行xor得到Plain1。
? 3.用函數(shù)對(duì)第二組密文解密粪薛,然后和第一組的密文xor得到Plain2悴了。
我們來(lái)繼續(xù)模擬一下解密的過(guò)程:
(以下稱(chēng)呼未知解密函數(shù)為decrypt,同樣解密的過(guò)程也是我們模擬出來(lái)的,現(xiàn)實(shí)中是不需要我們?nèi)?xiě)代碼進(jìn)行加密解密的湃交,拿php舉例的函數(shù)是$Plaintext=openssl_decrypt($ciphertext,"aes-128-cbc",'***********',OPENSSL_RAW_DATA,$iv))
1.對(duì)整個(gè)Ciphertext進(jìn)行分組熟空,每16個(gè)字節(jié)為一組。這里還是稱(chēng)呼cipher1是第一組搞莺,cipher2是第二組
2.從第1組開(kāi)始息罗,我們的目的是得到Plain1
Plain1=decrypt(cipher1)XOR? iv
同樣每個(gè)字節(jié)都是
For($i=0;$i<=15;$i++){
$Plain1[$i]=decrypt($Cipher1[$i]) Xor $Iv[$i];
}
這樣循環(huán)之后Plian1明文第一組已經(jīng)出來(lái)了,接下來(lái)看一下Plian2明文第二組如何去做才沧。
原理是通過(guò)前一組的密文和本組函數(shù)解密后的值進(jìn)行異或運(yùn)算得到明文2迈喉。
For($i=0;$i<=15;$i++){
$Plain2[$i]=decrypt($Cipher2[$i]) Xor $Cipher1[$i];
}
這時(shí)我們同樣得到了明文組2,下面我們把Plain1和Plian2連接起來(lái)糜工。得到了原本的明文Plaintext
0x3:攻擊
剛剛我們已經(jīng)了解了加密和解密的過(guò)程弊添,此時(shí)的重點(diǎn)來(lái)了,如果我們改變了第一組的密文捌木,令它去和函數(shù)解密后的第二組密文(decrypt(Cipher2))進(jìn)行異或油坝,這時(shí)我們就會(huì)得到不一樣的第二組明文,我們此時(shí)是可以通過(guò)控制第一組的密文來(lái)得到我們想要的第二組的明文的刨裆。這就是所謂的cbc字節(jié)翻轉(zhuǎn)攻擊澈圈。后面會(huì)詳細(xì)講解攻擊過(guò)程。先想想以下的問(wèn)題帆啃。
如何利用瞬女?
設(shè)有一個(gè)驗(yàn)證輸入內(nèi)容的WEB應(yīng)用程序是先驗(yàn)證內(nèi)容有沒(méi)有非法字符,然后再進(jìn)行aes-128-cbc加密所傳輸?shù)臄?shù)據(jù)努潘,并且解密后的數(shù)據(jù)沒(méi)有經(jīng)過(guò)過(guò)濾器時(shí)诽偷,我們可以通過(guò)cbc字節(jié)翻轉(zhuǎn)攻擊來(lái)繞過(guò)。例如用戶(hù)輸入#號(hào)疯坤,我們先輸入a1报慕,然后在把1翻轉(zhuǎn)成#號(hào),這樣就繞過(guò)了過(guò)濾器压怠。
字節(jié)翻轉(zhuǎn):
剛剛講述了CBC解密的過(guò)程眠冈,核心在于我們控制上一組的密文去改變當(dāng)前組的明文。
也就是Plain2[0]=decrypt(Cipher2[0]) Xor Cipher1[0]
這時(shí)我們希望解密后Plian2[0]的值是'a'菌瘫,只需改變Cipher1[0]的值就可以了蜗顽。
那么具體改成什么呢?
推導(dǎo)過(guò)程:
設(shè) 要改變Cipher1[0]的值為x
則? 'a' ^ x = decrypt(cipher2[0])
而又因?yàn)椋篜lain2[0] ^ Cipher1[0]=decrypt(cipher2[0])
把decrypt(cipher2[0]) 代入第一個(gè)式子得
'a' ^ x = Plain2[0] ^ Cipher1[0]
解得x= Plain2[0] ^ Cipher1[0] ^ 'a'
x是我們要賦值的雨让,也就是Cipher1[0] 雇盖,所以最后寫(xiě)出的語(yǔ)句為:
Cipher1[0]=Plain2[0] ^ Cipher1[0] ^ 'a'
Plain2[0]是原明文組2的第一個(gè)字節(jié)(已知),Cipher1[0]是第一組密文的第一個(gè)字節(jié)(已知)
此時(shí)再把Cipher1和Cipher2連接起來(lái)生成Ciphertext宫患,把C刊懈,就會(huì)成功變成我們想要的明文这弧。
修復(fù)IV:
當(dāng)我們破壞掉密文的第一組時(shí),同樣明文的第一組在解密的時(shí)候就并不是原來(lái)的明文了虚汛,這個(gè)時(shí)候我們需要修復(fù)初始向量IV匾浪,給它一個(gè)新的值,
使NewIv ^ NewCipher1 = Plain1(原)
推導(dǎo)過(guò)程:
Plain1(損壞) ^ iv = decrypt(newCipher1)
Newiv=Plain1(原) ^ decrypt(newCipher1) (目的是要給Newiv賦值)
把第一個(gè)式子代入到第二個(gè)式子中得到:(此時(shí)Plain(原) 已知卷哩,Plain1(損壞)已知)
Newiv=Plain1(原) ^ Plain1(損壞) ^ iv
有了Newiv和翻轉(zhuǎn)好的Ciphertext蛋辈,這時(shí)網(wǎng)站后端解密后的明文就是我們構(gòu)造好的明文了。
0x4:EXP腳本:
下面是本人使用PHP編寫(xiě)的字節(jié)翻轉(zhuǎn)腳本:
<meta charset="utf-8">
<form action='' method="POST">
<input type="text" name='id' value='' required="true">輸入你的明文
<br/>
<input type="text" name='cp' value='' required="true">輸入你的ciphertext
<br/>
<input type="text" name='ks' value='' required="true">輸入你想改變的字符所在的塊數(shù)(大于1)
<br/>
<input type="text" name='wz' value='' required="true">輸入字符在其塊中的位置(從零開(kāi)始)
<br/>
<input type="text" name='ys' value='' required="true">輸入原始字符
<br/>
<input type="text" name='tg' value='' required="true">輸入用于替換原字符的目標(biāo)字符
<br/>
<input type="submit" name='submit' value='獲得翻轉(zhuǎn)后的Ciphertext'>
</form>
<?php
if(isset($_POST['id']) && isset($_POST['cp']) ){
$plain=$_POST['id'];
echo "原始plain為:"."<br/>";
$row=ceil(strlen($plian)/16);
for($i=0;$i<$row;$i++){
? ? echo substr($plian,$i*16,16).'<br/>';
}
if(isset($_POST['ks']) && isset($_POST['wz']) && isset($_POST['tg'])? && isset($_POST['ys'])){
$wz=$_POST['wz'];
$value=intval(($_POST['ks'] -2))*16 + intval($wz);
$cipher=base64_decode(urldecode($_POST['cp']));
$cipher[$value]=chr(ord($cipher[$value]) ^ ord($_POST['ys']) ^ ord($_POST['tg']));
echo "翻轉(zhuǎn)后的cipher:";
echo urlencode(base64_encode($cipher));
}
}
修復(fù)iv的腳本:
<meta charset="utf-8">
<form action='' method="POST">
<input type="text" name='id' value='' required="true">輸入原明文組內(nèi)容(16個(gè)字節(jié))
<br/>
<input type="text" name='iv' value='' required="true">輸入iv
<br/>
<input type="text" name='lmmw' value='' required="true">輸入BASE64后的損壞明文組(BASE64前的長(zhǎng)度為16個(gè)字節(jié)的損壞組)
<br/>
<input type="submit" name='submit' value='獲取NEWiv'>
</form>
<?php
if(isset($_POST['id'])? && isset($_POST['iv']) && isset($_POST['lmmw'])){
$plain=$_POST['id'];
$iv=base64_decode(urldecode($_POST['iv']));
$row=ceil(strlen($plain)/16);
$lmmw=base64_decode($_POST['lmmw']);
$newiv='';
for($i=0;$i<=15;$i++){
$newiv .= chr(ord(substr($plain,$i,1)) ^ ord($iv[$i]) ^ ord(substr($lmmw,$i,1)) );
}
echo "newiv為:".urlencode(base64_encode($newiv));
}
(為了方便做題将谊,輸入cipher和iv時(shí)請(qǐng)輸入經(jīng)過(guò)url編碼后的內(nèi)容冷溶,同樣程序輸出的內(nèi)容也是經(jīng)過(guò)url編碼的。)
小結(jié):初次寫(xiě)文尊浓,如有錯(cuò)誤麻煩指出逞频,感謝各位,多多擔(dān)待栋齿。