r1ngs's blog

深入研究字节反转攻击

2019-04-05

前言

先知已投, 戳我跳转

现在无论你是google还是百度,”字节反转攻击”的搜索结果整整几页都是”CBC字节反转攻击”,看来字节反转攻击和CBC模式结合的想法已经可以说是深入人心,毕竟它的名字就叫”CBC反转字节攻击”,但是不是这种攻击只有CBC分组模式才有呢?如果不用CBC分组模式,是不是就不存在这种重放攻击呢?笔者就这个问题进行了研究。

CBC

CBC模式下的字节反转攻击想必大家都不陌生了,这种攻击方式和分组加密的加密算法无关,是在处理明文加密后的密文块时出现了漏洞:前一块的密文可以影响后一块的明文。

这种图引用自国外一个大佬的文章,攻击手法是简单明了的:

C1 xor D(C2) = P2​

那么我们令C1=C1 xor P2 xor P发送给服务器,其中P是我们想要篡改的明文,那么服务器会计算:

(C1 xor P2 xor P) xor D(C2) = (P2 xor D(C2)) xor P2 xor P xor D(C2) = P

也就达到了篡改的效果,下面是作者自己写的测试demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor

class AES_CBC:
def __init__(self):
self.key = urandom(16)
self.iv = urandom(16)

def encrypt(self, plain):
aes = AES.new(self.key, AES.MODE_CBC, self.iv)
return aes.encrypt(plain)

def decrypt(self, cipher):
aes = AES.new(self.key, AES.MODE_CBC, self.iv)
return aes.decrypt(cipher)

plain = '1'*32
aes = AES_CBC()
cipher = aes.encrypt(plain)
print aes.decrypt(cipher)

cipher = strxor(strxor(cipher[:16], '1'*16), '2'*16)+cipher[16:]
print aes.decrypt(cipher)

运行结果为:

前16个字节乱码是因为我们篡改了C1后,对应的D(C1)也发生了改变

CFB

CFB模式可以将块密码转换为同步的流密码。流密码通俗点讲就是将明文逐字节的进行加密,它生成密钥流块,然后与明文块进行异或,然后获得密文。

先来看一下CFB的加解密模式吧,图片选自wikipedia:

那么按道理说,我们如果将密文的第一块反转,那么明文的第一块也应该被对应篡改,但事实并非如此,我们看这个测试demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor

class AES_CFB:
def __init__(self):
self.key = urandom(16)
self.iv = urandom(16)

def encrypt(self, plain):
aes = AES.new(self.key, AES.MODE_CFB, self.iv)
return aes.encrypt(plain)

def decrypt(self, cipher):
aes = AES.new(self.key, AES.MODE_CFB, self.iv)
return aes.decrypt(cipher)

plain = '1'*32
aes = AES_CFB()
cipher = aes.encrypt(plain)
print aes.decrypt(cipher)

ct = strxor(strxor(cipher[:16], '1'*16), '2'*16)+cipher[16:]
print aes.decrypt(ct)

运行结果如下:

一片乱码?但是如果你仔细观察的话,会发现其实第一个字符已经被改成了2,那为什么后面的都是乱码呢?

我们修改一下代码,只更改第一个字符:

1
ct = strxor(strxor(cipher[:1], '1'*1), '2'*1)+cipher[1:]

程序运行结果如下:

第一个字符依然是2,但是为什么后面有15个字符没有被篡改呢?

其实,wikipedia的图并不能很好的展示CFB的工作模式 ,真实的CFB模式是这样的:

图中的Shift register代表的是移位寄存器,图中的s一般代表的是8bit也就是1字节,同时,如果我们更改了Ci,那么Ci是要被存放到下一个移位寄存器里的,并且这个Ci会一直保存在寄存器里,直到它慢慢从寄存器移出去,所以这个Ci会影响128/8=16个字节的明文,故最后有32-1-16=15个字节的明文没有受影响,所以,CFB模式也是有字节反转攻击的,只是我们每次只能改动一个字节,完整的攻击demo如下:

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
from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor

class AES_CFB:
def __init__(self):
self.key = urandom(16)
self.iv = urandom(16)

def encrypt(self, plain):
aes = AES.new(self.key, AES.MODE_CFB, self.iv)
return aes.encrypt(plain)

def decrypt(self, cipher):
aes = AES.new(self.key, AES.MODE_CFB, self.iv)
return aes.decrypt(cipher)

plain = '1'*32
aes = AES_CFB()
cipher = aes.encrypt(plain)
print aes.decrypt(cipher)


for i in range(32):
pt = aes.decrypt(cipher)
cipher = cipher[:i]+strxor(strxor(cipher[i], pt[i]), '2')+cipher[i+1:]

print aes.decrypt(cipher)

程序运行结果为:

OFB

OFB和CFB类似,也是将块密码转换为流密码的一种分组模式,加解密图示如下:

可以从解密模式发现,OFB模式和CBC模式比较相似,并且如果其中一块的密文进行了改变的话,并不会影响它后面的密文

所以类似的,OFB模式也存在字节反转攻击,同时由于流密码的性质,我们可以很简单的做到对明文任意长度字符的篡改

demo如下:

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
from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor

class AES_OFB:
def __init__(self):
self.key = urandom(16)
self.iv = urandom(16)

def encrypt(self, plain):
aes = AES.new(self.key, AES.MODE_OFB, self.iv)
return aes.encrypt(plain)

def decrypt(self, cipher):
aes = AES.new(self.key, AES.MODE_OFB, self.iv)
return aes.decrypt(cipher)

plain = '1'*32
aes = AES_OFB()
cipher = aes.encrypt(plain)
print aes.decrypt(cipher)

for i in range(32):

ct = strxor(strxor(cipher[:i+1], '1'*(i+1)), '2'*(i+1))+cipher[i+1:]
print aes.decrypt(ct)

运行结果如下

总结

虽然名字叫做”CBC字节反转攻击”,但并不是只有CBC模式才会有这种攻击手法,CFB和OFB模式都是不能抵御这种攻击的。至于CTR,我初步测试也是有的,和OFB模式基本相同。

Tags: 分析

1000000