CBC加密流程

(浏览了各路大神的博客帖子,终于把这个研究明白了,所以写出一个博文当做自己的笔记,把自己的理解加入进去,因为之前学习时刚开始还是难理解,后面跌跌撞撞的还是明白了,希望想学到这个知识的朋友可以从这篇文章中得到一些收获。)
0x1加密:
两个单词:Plain代表明文。Cipher代表密文
Xor:异或,异或运算 例如1 ^ 999 = 998。同样 998 ^ 999 =1 ,继续 999 ^ 1 =998
加密步骤为:
• 1.将明文每16个字节分一组,并设置一个初始向量iv,iv为固定的16个字节一般是随机生成。(如果最后一组不够十六个字节,则会通过补充\x0n (n代表缺少的个数 )来填满,例如第三组目前是10个字节,那么后面的六个字节全部都是\x06,如果超出10则用16进制字符代表例如\x11则表示成\x0b)
• 2.将明文第一组与初始向量iv进行xor得出一个待加密的C。
• 3.将C进行一个函数加密,这个函数是对称的也是未知的,解密的时候也需要用到这个函数。(此时加密后的C,也就是encrypt(C)我们称他为cipher1)
同理 对第二组明文进行相同操作,只是此时的初始向量iv是前一组的密文(也就是cipher1)
• 4.此时第二组的明文与第一组的密文进行xor得到第二组的C。
• 5.将C进行同样的函数加密生成cipher2。之后的每一组都通过这样的方式。
• 6.将所有的cipher组连接到一起,生成了密文Ciphertext。
下面我们模拟一下cbc加密模式的过程:
(以下的加密解密过程都是模拟的,真实情况是不需要我们去写代码的 拿php举例。
$ciphertext=openssl($plain,"aes-128-cbc",'***********',OPENSSL_RAW_DATA,$iv),
这条代码的功能就是产生一个Ciphertext,你只需要给它一个明文,给他一个初始iv,函数会自动帮助你去生成密文。模拟加密解密过程是为了帮助我们理解他的加密模式,从而了解此加密模式的漏洞机制。)
我们还是按照加密过程来开始:
1.先把明文进行文组。
以下的代码我称未知加密函数为encrypt() 解密函数为decrypt()。Plain1代表的是明文第一组,Plain2代表明文第2组。cipher1表示密文第一组,cipher2表示密文第二组。。(每组的密文和明文都是16个字节。密文组和明文组的每一个字节都是对应的)也就是说:
密文组1的第[三]个字节=未知函数加密(明文组1的第[三]个字节 XOR 初始IV的第[三]个字节)
代码表示为:cipher1[2]=encrypt(plain1[2] Xor IV[2])
建立一个循环来代表可以写成
For($i=0;$i<=15;$i++){
$Cipher1[$i]=encrypt($Plain1[$i] xor $Iv[$i]);
}
这时分组1的密文已经获取完毕,下面我们模拟一下分组2的密文:
此时分组2的明文与第一组的密文进行xor得到第二组的C,再把C进行encrypt函数加密,得到了想要的密文。代码表示:
For($i=0;$i<=15;$i++){
$Cipher2[$i]=encrypt($Plain2[$i] xor $Cipher1[$i]);
}
同样分组2的密文已经获取完毕
我们把cipher1和cipher2连接起来,便产生了Ciphertext(这里举例明文只有32个字节)
Ciphertext=cipher1.cipher2;
现在我们有了Ciphertext了,那么这个是不是我们从cookie中看到的呢?其实并不是,因为加密后的内容有很多是不可见字符,如果想让密文可视化,普遍都采用base64进行编码,这样就可以看到了。但是解密的时候一定要再解码回去才可以解密。
0x2:解密
解密的过程:
• 1.将Ciphertext密文分组。
• 2.用函数对第一组的密文解密,然后和IV进行xor得到Plain1。
• 3.用函数对第二组密文解密,然后和第一组的密文xor得到Plain2。
我们来继续模拟一下解密的过程:
(以下称呼未知解密函数为decrypt,同样解密的过程也是我们模拟出来的,现实中是不需要我们去写代码进行加密解密的,拿php举例的函数是$Plaintext=openssl_decrypt($ciphertext,"aes-128-cbc",'***********',OPENSSL_RAW_DATA,$iv))
1.对整个Ciphertext进行分组,每16个字节为一组。这里还是称呼cipher1是第一组,cipher2是第二组
2.从第1组开始,我们的目的是得到Plain1
Plain1=decrypt(cipher1)XOR iv
同样每个字节都是
For($i=0;$i<=15;$i++){
$Plain1[$i]=decrypt($Cipher1[$i]) Xor $Iv[$i];
}
这样循环之后Plian1明文第一组已经出来了,接下来看一下Plian2明文第二组如何去做。
原理是通过前一组的密文和本组函数解密后的值进行异或运算得到明文2。
For($i=0;$i<=15;$i++){
$Plain2[$i]=decrypt($Cipher2[$i]) Xor $Cipher1[$i];
}
这时我们同样得到了明文组2,下面我们把Plain1和Plian2连接起来。得到了原本的明文Plaintext
0x3:攻击
刚刚我们已经了解了加密和解密的过程,此时的重点来了,如果我们改变了第一组的密文,令它去和函数解密后的第二组密文(decrypt(Cipher2))进行异或,这时我们就会得到不一样的第二组明文,我们此时是可以通过控制第一组的密文来得到我们想要的第二组的明文的。这就是所谓的cbc字节翻转攻击。后面会详细讲解攻击过程。先想想以下的问题。
如何利用?
设有一个验证输入内容的WEB应用程序是先验证内容有没有非法字符,然后再进行aes-128-cbc加密所传输的数据,并且解密后的数据没有经过过滤器时,我们可以通过cbc字节翻转攻击来绕过。例如用户输入#号,我们先输入a1,然后在把1翻转成#号,这样就绕过了过滤器。
字节翻转:
刚刚讲述了CBC解密的过程,核心在于我们控制上一组的密文去改变当前组的明文。
也就是Plain2[0]=decrypt(Cipher2[0]) Xor Cipher1[0]
这时我们希望解密后Plian2[0]的值是'a',只需改变Cipher1[0]的值就可以了。
那么具体改成什么呢?
推导过程:
设 要改变Cipher1[0]的值为x
则 'a' ^ x = decrypt(cipher2[0])
而又因为:Plain2[0] ^ Cipher1[0]=decrypt(cipher2[0])
把decrypt(cipher2[0]) 代入第一个式子得
'a' ^ x = Plain2[0] ^ Cipher1[0]
解得x= Plain2[0] ^ Cipher1[0] ^ 'a'
x是我们要赋值的,也就是Cipher1[0] ,所以最后写出的语句为:
Cipher1[0]=Plain2[0] ^ Cipher1[0] ^ 'a'
Plain2[0]是原明文组2的第一个字节(已知),Cipher1[0]是第一组密文的第一个字节(已知)
此时再把Cipher1和Cipher2连接起来生成Ciphertext,把C,就会成功变成我们想要的明文。
修复IV:
当我们破坏掉密文的第一组时,同样明文的第一组在解密的时候就并不是原来的明文了,这个时候我们需要修复初始向量IV,给它一个新的值,
使NewIv ^ NewCipher1 = Plain1(原)
推导过程:
Plain1(损坏) ^ iv = decrypt(newCipher1)
Newiv=Plain1(原) ^ decrypt(newCipher1) (目的是要给Newiv赋值)
把第一个式子代入到第二个式子中得到:(此时Plain(原) 已知,Plain1(损坏)已知)
Newiv=Plain1(原) ^ Plain1(损坏) ^ iv
有了Newiv和翻转好的Ciphertext,这时网站后端解密后的明文就是我们构造好的明文了。
0x4:EXP脚本:
下面是本人使用PHP编写的字节翻转脚本:
<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">输入你想改变的字符所在的块数(大于1)
<br/>
<input type="text" name='wz' value='' required="true">输入字符在其块中的位置(从零开始)
<br/>
<input type="text" name='ys' value='' required="true">输入原始字符
<br/>
<input type="text" name='tg' value='' required="true">输入用于替换原字符的目标字符
<br/>
<input type="submit" name='submit' value='获得翻转后的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 "翻转后的cipher:";
echo urlencode(base64_encode($cipher));
}
}
修复iv的脚本:
<meta charset="utf-8">
<form action='' method="POST">
<input type="text" name='id' value='' required="true">输入原明文组内容(16个字节)
<br/>
<input type="text" name='iv' value='' required="true">输入iv
<br/>
<input type="text" name='lmmw' value='' required="true">输入BASE64后的损坏明文组(BASE64前的长度为16个字节的损坏组)
<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时请输入经过url编码后的内容,同样程序输出的内容也是经过url编码的。)
小结:初次写文,如有错误麻烦指出,感谢各位,多多担待。