title: 分组密码CBC加密缺陷

date: 2017-05-15 10:04:47

tags: ["密码学"]

关于密码学的种种漏洞以及利用网上也有不少,但是比较零散,有关介绍比较局限,导致一些东西晦涩难懂不易理解,这里是一个有关于CBC分组加密的一个讲解

CBC加密模式

首先上图

这里文字描述不如看图直观,还是大致描述一下,CBC模式的加密方式是通过一个初始向量(IV)先和明文分组第一组异或后使用秘钥K加密,作为第一组密文,同时又与后一分组的明文异或后进行加密产生下一组密文,依次重复。

其解密和加密是对称的,密文先解密,再异或。

关于这个初始向量IV的完整性要比其保密性更为重要。在CBC模式下,最好是每发一个消息,都改变IV,比如将其值加一。

这里说说有关于CBC的错误传播,有利于之后字节翻转攻击的理解

其特点如下:

  1. 明文有一组中有错,会使以后的密文组都受影响,但经解密后的恢复结果,除原有误的一组外,其后各组明文都正确地恢复。
  2. 解密时,有一组秘钥错误,该错误会影响下一分组相同位置的解密
  3. 若在传送过程中,某组密文组出错时,则该组恢复的明文和下一组恢复数据出错。再后面的组将不会受中错误比特的影响。

字节翻转攻击

概述

有关于这里的攻击,没有下面那种方式刺激,但是效果也还是可以

这里最大的效果就是:在不知道Key(秘钥)的情况下篡改明文

通过上面的错误传播,我们可以想到,解密时通过修改前一个密文分组可以影响后一个的解密后的明文分组

这里修改可以将前一个密文中的任意比特进行修改(0,1进行翻转)

详解

这里举个例子:

明文是"lee-jayy:12345678$,ohh he is very rich!"

这里使用DES算法,秘钥key = "leejleej" 初始向量iv='thisisiv'

加密结果为:04f2e7d245cec18f6c1769c66f0e30ccc30a378c58597b15409a0a8c296df6a66a10679b55ddbabb

第一步将消息分组,其des算法的分组长度是64bit,一个字符是8bit故8个字符一组,如下

第一组:lee-jayy

第二组::1234567

第三组:8$,ohh h

第四组: eis ver

第五组: y rich!

这里我想把第二组的第2个字符“1”改为“9”,这里该如何操作?

由上面的特点我们知道,修改第n分组的秘文,其第n+1分组的明文会被窜改,这里我们待修改的字符在第二分组,我们可以修改第一分组的密文来控制第二分组。

为了方便我把密文也分组一下:

04f2e7d245cec18f

6c1769c66f0e30cc

c30a378c58597b15

409a0a8c296df6a6

6a10679b55ddbabb

由于这里需要修改的字符“1”位于第二分组的第二个字符,所以我们只需修改第一分组相同的位置的密文

,一个字符是16bit两个十六进制位,故我们需要修改第一分组密文的“f2”,这里改如何改呢?

在改之前我们先了解一个基本知识,有关于异或:

异或的规则是相同为0,不同为1

于是有1 xor 1 =0,1 xor 0 =1,0 xor 0 = 0

可以看出这么一个规则,A xor B = C <=> A xor C = B

这时候可以回看一下上面的解密的图

我们知道的东西有:

1、第二组des算法加密后要和第一组的密文异或得到第二组的密文

2、字符1的ascii码16进制是31

3、字符1经过cbc后的密文要和f2进行异或

这里我们并不晓得其秘钥key,这里我们假设第二组des算法加密后16进制是ABCDEFGHIGKLMNOP,故字符1经过DES加密的结果是AB

第一步、我们根据上面的算法知道 AB xor f2 = 31,这里04是字符1的密文,我们需要解出AB,只需要f2 xor 31既可得出1经过des算法加密后是C3

第二步、我们利用上一步计算出的DES对字符1加密的结果35异或待修改的字符“9”(其ascii码为39)。

C3 xor 39 = FA

第三步、修改密文中第一分组开始的f2为FA,修改后的密文是:

04FAe7d245cec18f6c1769c66f0e30ccc30a378c58597b15409a0a8c296df6a66a10679b55ddbabb

可以对比一下输出:

修改前:lee-jayy:12345678$,ohh he is very rich!

修改后:4糉L柖292345678$,ohh he is very rich!

看出对应的1的确变为了9

利用同样方法我把1234567修改为了9999999,秘文为04FAECD848C2CE816c1769c66f0e30ccc30a378c58597b15409a0a8c296df6a66a10679b55ddbabb

结果:d&φΤ5ς:99999998$,ohh he is very rich!

加密脚笨如下,有兴趣可以自己试一试:

<?php

function jiami_DES($input = "",$key = "leejleej",$iv='thisisiv')
{
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($td, $key, $iv); $encrypted_data = mcrypt_generic($td, $input); mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return bin2hex($encrypted_data);
} function jiemi_DES($input = "",$key = "leejleej",$iv='thisisiv')
{
$td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($td, $key, $iv);
$mdecrypted_data = mdecrypt_generic($td,hex2bin($input));//$encrypted_data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $mdecrypted_data;
} echo jiami_DES('lee-jayy:12345678$,ohh he is very rich!');
echo '<br />';
echo jiemi_DES($_GET['key']);
?>

CTF实例

from twisted.internet import reactor, protocol
from Crypto.Cipher import AES
import os
import random
from secret import KEY,KEYSIZE,IV,FLAG PORT = 6666 def pad(instr, length):
if(length == None):
print "Supply a length to pad to"
elif(len(instr) % length == 0):
print "No Padding Needed"
return instr
else:
return instr + '\x04' * (length - (len(instr) % length )) def encrypt_block(key, plaintext):
encobj = AES.new(key, AES.MODE_ECB)
return encobj.encrypt(plaintext).encode('hex') def decrypt_block(key, ctxt):
decobj = AES.new(key, AES.MODE_ECB)
return decobj.decrypt(ctxt).encode('hex') def xor_block(first,second):
if(len(first) != len(second)):
print "Blocks need to be the same length!"
return -1 first = list(first)
second = list(second)
for i in range(0,len(first)):
first[i] = chr(ord(first[i]) ^ ord(second[i]))
return ''.join(first) def encrypt_cbc(key,IV, plaintext):
if(len(plaintext) % len(key) != 0): #加密文本的长度必须能整除Key,否则在后面加x40
plaintext = pad(plaintext,len(key))
blocks = [plaintext[x:x+len(key)] for x in range(0,len(plaintext),len(key))]
for i in range(0,len(blocks)):
if (i == 0):
ctxt = xor_block(blocks[i],IV)
ctxt = encrypt_block(key,ctxt)
else:
tmp = xor_block(blocks[i],ctxt[-1 * (len(key) * 2):].decode('hex'))
ctxt = ctxt + encrypt_block(key,tmp)
return ctxt def decrypt_cbc(key,IV,ctxt):
ctxt = ctxt.decode('hex')
if(len(ctxt) % len(key) != 0):
print "Invalid Key."
return -1
blocks = [ctxt[x:x+len(key)] for x in range(0,len(ctxt),len(key))]
for i in range(0,len(blocks)):
#print blocks[0].encode('hex')
if (i == 0):
ptxt = decrypt_block(key,blocks[i])
ptxt = xor_block(ptxt.decode('hex'),IV)
#print ptxt.encode('hex')
else:
tmp = decrypt_block(key,blocks[i])
tmp = xor_block(tmp.decode('hex'),blocks[i-1])
ptxt = ptxt + tmp
return ptxt def mkprofile(email):
if((";" in email)):
return -1
prefix = "comment1=wowsuch%20CBC;userdata="
suffix = ";coment2=%20suchsafe%20very%20encryptwowww" ptxt = prefix + email + suffix #连接字符串
print ptxt
return encrypt_cbc(KEY,IV,ptxt) def parse_profile(data):
print "DATA:"
print data
ptxt = decrypt_cbc(KEY,IV,data.encode('hex'))
ptxt = ptxt.replace("\x04","")
print ptxt
if ";admin=true" in ptxt:
return 1
return 0 class MyServer(protocol.Protocol):
def dataReceived(self,data):
if(len(data) > 512):
self.transport.write("Data too long.\n")
self.transport.loseConnection()
return if(data.startswith("mkprof:")):
data = data[7:]
resp = mkprofile(data)
if (resp == -1):
self.transport.write("No Cheating!\n")
else:
self.transport.write(resp + '\n')
elif(data.startswith("parse:")):
self.transport.write("Parsing Profile...")
data = data[6:].decode('hex')
if (len(data) % KEYSIZE != 0):
self.transport.write("Invalid Ciphertext <length>\n")
self.transport.loseConnection()
return if(parse_profile(data) == 1):
self.transport.write("Congratulations!\nThe FLAG is: ")
self.transport.write(FLAG)
self.transport.loseConnection() else:
self.transport.write("You are a normal user.\n") else:
self.transport.write("Syntax Error")
self.transport.loseConnection() class MyServerFactory(protocol.Factory):
protocol = MyServer factory = MyServerFactory()
reactor.listenTCP(PORT, factory)
reactor.run()

wp:

from pwn import *
sh = remote('133.130.52.128',6666)
target = ";admin=true"
email = '0000000000000000000000'
prefix = "comment1=wowsuch%20CBC;userdata="
suffix = ";coment2=%20suchsafe%20very%20encryptwowww"
ptxt = prefix + email + suffix sh.send('mkprof:' + email)
s = sh.recv(1024)[0:len(ptxt)*2]
s = list(s.decode('hex'))
for i in range(len(target)):
s[32+i] = p8( u8(s[32+i]) ^ u8(target[i]) ^ u8(ptxt[48+i]) )
s = ''.join(s).encode('hex')
sh.send('parse:' + s)
print sh.recv(1024)

Padding Oracle Attack

概述

有关于这种方式的攻击,实在是巧妙!

首先说一下使用条件,以及可以达到的效果。

使用条件:

一、我们可以修改iv(在我的理解其实这里如果数据分组大于1组其实不需要iv也可以进行攻击)

二、我们知道密文

三、我们可以利用服务端进行解密

效果:获取明文!(如果分组大于1组,没有iv的情况这里可以获取到部分明文)

详解

首先介绍一下对于分组加密过程中数据填充常用的方式(PKCS#5)

这种方式其实就是缺少多少字节就补充多少字节,其补充的数值就是填充的字节的数量比如数据差2字节,我们就补充两个0x02即可,就是差几个补几个几,如下图。

下面我们回顾一下关于cbc模式的解密方式

这里对每一分组进行一下细分,

可以看出末尾的数值为四个0×04,即为填充,这里如果填充的数值不对或者没有进行填充,解密过程往往不会进行,同时程序会报异常。

有了这个特性,我们就有了利用点,在这一点有些类似于在SQL注入中的盲注的思想。

下面具体分析一下:

在sql注入中,盲注我们通常是只有两种状态,truefalse

这里又是如何区分的呢?这里先具体看看具体原理分析

之前说到的使用条件是,我们可以修改iv、知道密文同时我们可以利用服务端进行解密

这里构造一个例子,如果服务端如果给用户设置了这样的cookie:key=6d367076036e2239|f851d6cc68fc9537

我们推测出‘|’之前的是iv,其后面的是加密结果,这个时候如果这里我将iv设置为0000000000000000

即为key=0000000000000000|f851d6cc68fc9537,发现这个时候页面出错了

看一下这样子的解密过程

这里之前提到的有关填充,可以知道不管消息多长,最后肯定是有填充的,其中填充数值范围和分组的大小有关,这里如果是DES算法8字节一组的话那么最后一位的范围一定是在0x01~0x08之间的,这里0x3D不是这个范围故出错。

这时候使程序不出错只有最后一位是0x01才可以

我们开始修正iv,发现当iv为000000000000003C的时候程序没有报错了,这个时候分析一下解密过程

这个这个时候0x3D异或0x3C为0x01,程序以为这里的填充位为一位,之前的全是数据,没毛病,开始解密

我们在这里0x3c可以穷举得出,中间值0x3d可以利用0x01异或0x3c计算出,同时我们还知道一个原始的iv=6d367076036e2239。

由于明文 = 中间值 xor IV,我们列两个式子

0x01 = 中间值 xor 0x3c

待求明文 = 中间值 xor 0x39

这里一个二元一次方程,不难解出待求明文。

这时候继续向前一位进行攻击,这时候需要后两位为0x02

第八位已经计算出,这里只需要重复之前的方法计算第七位即可

最开始看到这里会觉得有些疑问,为什么我们不能直接多位一起计算,这里可能会出现这么一个情况,假设最后一位是一个合理的填充(假设八字节分组0x01~0x08)第七位是个错误的值时候,就会出现非预期的错误,比如中间值的后两位为0x00和0x02,我们构造的初始向量的后两位为0x02和0x00,这时候程序并不会报错。

以此类推我们便可以获取到明文。

如果这里我们并不知道初始iv,在这里我们依然可以进行攻击,只不过第一组的数据无法获取到

仔细回顾一下上述的加密方式,我们的初始iv只是加密第一组数据中用得到,加密第二组数据的时候其实就是用的第一组的密文充当iv的角色继续加密,我们这里只需要不断修改分组上一分组的密文即可得到下一组的明文。

实例

有关的例子:

<?php
$type = "aes-128-cbc"; //加密类型,即分组大小为16
$P = "aaaaaaa"; //明文
$Key = "aAbBcCdDeE"; //加密要用到的Key
$IV = "thisivthisivthis"; //初始化向量,因为有一个异或的过程,所以它的大小和分组大小要一样
$C = openssl_encrypt($P, $type, $Key, OPENSSL_RAW_DATA, $IV);
//满足padding oracle attack前提条件1
print "iv: ".bin2hex($IV)."<br>";
print "c: ".bin2hex($C)."<br>"; //可能存在不可显示的字符,加个base64的编码 if(isset($_GET['s']) && isset($_GET['iv'])){
$s = hex2bin($_GET['s']);
$iv = hex2bin($_GET['iv']);
if(($n = openssl_decrypt($s, $type, $Key, OPENSSL_RAW_DATA, $iv)) !== false){ //解密失败会返回false
//bit flipping attack
echo $n;
if($n === "admin"){
print "well done!";
}
}else{
//满足padding oracle attack前提条件2
die("Fail!");
}
}
?>

脚本:

import requests
import base64
url = 'http://192.168.248.1/test/demo.php'
N = 16
l = [0] * N
iv = '74686973697674686973697674686973'.decode('hex')
tmp_iv = ''
out = [0] * N
s = ''
for i in range(1, N+1):
for c in range(0,256):
l[N-i] = c
tmp_iv = ''
for m in l:
tmp_iv += chr(m)
print tmp_iv.encode('hex')
payload = "?s=e211ffa0baa91627a5827f3867a0cff1&iv=" + tmp_iv.encode('hex')
#print payload
data = requests.get(url+payload).content
if 'Fail!' not in data:
out[N-i] = c ^ i
for y in range(i):
l[N-y-1] = out[N-y-1] ^ (i+1)
break
for i in range(N):
out[i] = out[i] ^ ord(iv[i])
for c in out:
s += chr(c)
print s

python有一个关于此方式利用的库,项目地址

参考

http://k1n9.me/2017/03/16/attack-in-cbc/

分组密码CBC加密缺陷的更多相关文章

  1. AES加密解密 助手类 CBC加密模式

    "; string result1 = AESHelper.AesEncrypt(str); string result2 = AESHelper.AesDecrypt(result1); ...

  2. CBC加密原理及攻击

    原理基于分组加密加密过程 Plaintext:明文,待加密的数据.IV:用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文,初始向量,用来与第一块的明文异或运算.Key:被一些如 ...

  3. 在java项目中使用AES256 CBC加密

    首先要注意一点,默认的JDK是不支持256位加密的,需要到Oracle官网下载加密增强文件(Java Cryptography Extension (JCE) Unlimited Strength J ...

  4. Aes CBC加密

    <?php namespace app\components; use yii; class Aes { /** * This was AES-128 / CBC / PKCS5Padding ...

  5. JAVA AES CBC 加密 解密

    AES 256 , KEY 的长度为 32字节(32*8=256bit). AES 128 , KEY 的长度为 16字节(16*8=128bit) CBC 模式需要IV, IV的值是固定写死,还是当 ...

  6. 【java工具】AES CBC加密

    一.定义 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准.这个标准用来替代原先 ...

  7. 各加密模式的演示(ECB,CBC)

    对于较长的明文进行加密需要进行分块加密,但是直接加密(ecb)不容易隐藏模式,用OpenCV写了个程序论证了一下 ECB 优点就是简单,可以并行计算,不会迭代误差 缺点就是隐藏不了模式 CBC 需要初 ...

  8. aes 128、192、256位,cbc、cfb、ecb、ofb、pcbc加密解密

    AES加解密总共有以下这些 算法/模式/填充 字节加密后数据长度 不满16字节加密后长度 AES/CBC/NoPadding 16 不支持 AES/CBC/PKCS5Padding 32 16 AES ...

  9. C++调用openssl实现DES加密解密cbc模式 zeropadding填充方式 pkcs5padding填充方式 pkcs7padding填充方式

    ============================================== des   cbc  加密 zeropadding填充方式 ======================= ...

随机推荐

  1. 【转】ArcGIS 10.1 for Server 架构

    前一段时间在博客中公布了我们的计划,我们采用博客的形式将对ArcGIS10.1 for Server进行全面介绍.但这种形式有一定的遗憾:缺少互动的空间,所以我们希望广大爱好者能将自己感兴趣的话题在博 ...

  2. Navicat10.1.11使用记录

    设计表的时候有个允许空值(null),如果不勾选,则无法插入null(但是可以插入‘null’),且默认值不能为null: 如果某个字段没有设置默认值,而插入时又没有给此字段赋值,则会提示warnin ...

  3. map set vector用法小总结

    1.Map 定义 #include<map> map<string,bool> mp; 插入 mp[s]=; mp.insert(make_pair(s,)); 输出 cout ...

  4. fill 的用法

    博客 : http://blog.csdn.net/liuchuo/article/details/52296646 fill函数的作用是:将一个区间的元素都赋予val值.函数参数:fill(vec. ...

  5. <密码学系列>—信息安全威胁

    懒惰等于将一个人活埋.--泰勒 本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 点关注,不迷路! ...

  6. 「扫盲」Elasticsearch

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 不知道大家的公司用Elasticsearch多不 ...

  7. eclipse android ndk开发遇到的问题.

    1. error:parameter name omitted 用javah生成的.h文件中,方法是没有指定形参的,实现的时候需要我们在实现的方法定义中加上形参. 2. 'NewStringUTF' ...

  8. HBase二次开发之搭建HBase调试环境,如何远程debug HBase源代码

    版本 HDP:3.0.1.0 HBase:2.0.0 一.前言 之前的文章也提到过,最近工作中需要对HBase进行二次开发(参照HBase的AES加密方法,为HBase增加SMS4数据加密类型).研究 ...

  9. mybatis in查询

    原文地址:https://blog.csdn.net/u012427355/article/details/79580561 foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集 ...

  10. 异想家Win10系统安装的软件与配置

    1.C盘推荐一个硬盘,256G,安装好驱动,显卡配置好高性能,激活Win10,屏蔽WIn10驱动更新(Show or hide updates.diagcab),改电脑名称为Sandeepin-PC. ...