密码分组链接模式

什么是CBC

1976年,IBM发明了密码分组链接(CBC,Cipher-block chaining)模式。在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。
mark
mark
若第一个块的下标为1,则CBC模式的加密过程为
$$C_i = E_K(P_i ⊕ C_{i-1})$$
$$C_0 = IV$$
解密过程为
$$P_i = D_K(C_i) ⊕ C_{i-1}$$
$$C_0 = IV$$
这里还需要解释两个东西 初始化向量(IV) 还有一个 填充

初始化向量(IV)

初始化向量(IV,Initialization Vector)是许多工作模式中用于将加密随机化的一个位块,由此即使同样的明文被多次加密也会产生不同的密文,避免了较慢的重新产生密钥的过程。
初始化向量与密钥相比有不同的安全性需求,因此IV通常无须保密,然而在大多数情况中,不应当在使用同一密钥的情况下两次使用同一个IV。对于CBC和CFB,重用IV会导致泄露明文首个块的某些信息,亦包括两个不同消息中相同的前缀。对于OFB和CTR而言,重用IV会导致完全失去安全性。

填充

块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的。因此部分模式(即ECB和CBC)需要最后一块在加密前进行填充。CBC模式的做法是在最后一个分组后填充一个固定的值,这个值的大小为填充的字节总数。即假如最后还差3个字符,则填充0x03。
这种Padding原则遵循的是常见的PKCS#5标准
mark

针对CBC模式的攻击

这里提及两个攻击模式 CBC字节翻转攻击 和 Padding Oracle Attack

CBC字节翻转攻击

原理

CBC字节翻转攻击的精髓在于:通过损坏密文字节来改变明文字节。
通过观察上面CBC解密的例图 我们可以发现:改变前一块密文块会影响后一块密文块的解密结果,并且前后之间使用异或,这样的话我就可以修改前面的密文块来操控后面的解密结果。
mark

例子

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
error_reporting(0);

define("key", "dfhehrhrh3429hrtg934");
define("method", "aes-128-cbc");

function getIV()
{
$iv = '';
for ($i = 0; $i < 16; $i++)
{
$iv .= chr(rand(1, 255));
}
return $iv;
}

function enc($data)
{
$iv = getIV();
$c = openssl_encrypt((string)$data, method, key, OPENSSL_RAW_DATA, $iv);
return bin2hex($iv . $c);
}

function dec($data)
{
$data = hex2bin($data);
if ($iv = substr($data, 0, 16))
{
if ($c = substr($data, 16))
{
if ($m = openssl_decrypt((string)$c, method, key, OPENSSL_RAW_DATA, $iv))
{
return $m;
}
}
}
}

if ($_GET['m'])
{
echo enc($_GET['m']) . "<br/>";
}
if ($_GET['c'])
{
echo dec($_GET['c']) . "<br/>";
}

测试数据:m=1234 目标:解密结果为1235

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding:utf-8 -*-
from ctfutils import *

#m = 1234
c = "eac88303c5f788c4988a71bbbec2fd6e6911740587a51db4335731d45977a45c"

strc = hex2str(c)
iv = strc[:16] #提取IV
c = strc[16:]

#IV ^ DC = 4 也就是说 IV ^ 4 = DC 我们的目的是 DC ^ IV' = 5
#将上面的式子整合就能得到 IV' = 5 ^ DC = 5 ^ IV ^ 4
iv = list(iv)
iv[3] = chr(ord(iv[3]) ^ 4 ^ 5)
iv = ''.join(iv)

print str2hex(iv + c)
#new c=eac88302c5f788c4988a71bbbec2fd6e6911740587a51db4335731d45977a45c

mark
mark

Padding Oracle Attack

原理

Padding Oracle Attack 其实就是针对填充方式的一种攻击手段 引用维基百科的话

In cryptography, a padding oracle attack is an attack which is performed using the padding of a cryptographic message. In cryptography, variable-length plaintext messages often have to be padded (expanded) to be compatible with the underlying cryptographic primitive. The attack relies on having a “padding oracle” who freely responds to queries about whether a message is correctly padded or not. Padding oracle attacks are mostly associated with CBC mode decryption used within block ciphers.

mark
Padding Oracle Attack最关键的就是填充。如果最后的Padding不正确(值和数量不一致),则解密程序往往会抛出异常(Padding Error)。而利用应用的错误回显,我们就可以判断出Paddig是否正确。在Padding Oracle Attack攻击中,攻击者输入的参数是IV+Cipher,我们要通过对IV的”穷举”来请求服务器端对我们指定的Cipher进行解密,并对返回的结果进行判断。

前提条件

攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量)。
攻击者能够触发密文的解密过程,且能够知道密文的解密结果。

例子

测试代码需要稍微修改一下 主要是为了方便测试着想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
error_reporting(0);

define("key", "dfhehrhrh3429hrtg934");
define("method", "aes-128-cbc");

function enc($data)
{
$iv = "0123456789abcedf";
$c = openssl_encrypt((string)$data, method, key, OPENSSL_RAW_DATA, $iv);
return bin2hex($iv . $c);
}

function dec($data)
{
$data = hex2bin($data);
if ($iv = substr($data, 0, 16))
{
if ($c = substr($data, 16))
{
if ($m = openssl_decrypt((string)$c, method, key, OPENSSL_RAW_DATA, $iv))
{
return $m;
}
else
{
return "dec error";
}
}
}
}

echo enc('1') . "<br/>";
if ($_GET['c'])
{
echo dec($_GET['c']) . "<br/>";
}

爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#coding:utf-8
import requests
import re

#指定位置加一
def add1(data, pos):
data = list(data)
data[pos] = chr(ord(data[pos]) + 1)
return ''.join(data)

#增加iv的值
def addiv(iv, l):
return add1(iv.decode('hex'), l).encode('hex')

#逐位增加mid
def addmid(iv, l, mid):
mid = mid.decode('hex')
iv = list(iv.decode('hex'))
mid = chr(ord(iv[l]) ^ (16 - l)) + mid
return mid.encode('hex')

#通过mid计算新的iv
def newiv(mid, l):
mid = list(mid.decode('hex'))
iv = '0' * l * 2
tmp = 17 - l
for i in mid:
iv = iv + chr(ord(i) ^ tmp).encode('hex')
return iv

#初始化 mid是中间值 c是部分的密文
iv = '0' * 32
mid = ''
l = 15
c = ""

url = "http://localhost:8080/poa.php?c="
while l > 0: #一共15位最开始一位要爆破
#print url + iv + c
res = requests.get(url + iv + c)
if "dec error" not in res.content:
#print url + iv + c
mid = addmid(iv, l, mid)
iv = newiv(mid, l)
l = l - 1
print mid
continue
iv = addiv(iv, l)