再回想一下我们介绍CBC块加密时说过,在一个加密块(Block N)中翻转某一位,则会在解密后导致对应的下一个明文块(Block N+1)中相同的位进行翻转。由于这个特性,我们可以在不知道密钥的情况下,使用服务器来猜解出明文数据。
最后一字节
具体怎么做呢?再次仔细思考一下CBC模式的解密流程,若要解密一个块,则需要其本身的密文C2以及前一个块的密文C1,解密的流程如下:
在这种攻击场景下,我们(攻击者)可以控制输入密文块的内容,并且获取服务器的差异化返回,即是否填充错误。假设C2是最后一个块,那么通过变异C1,就可以猜解C2明文。猜解过程如下:
- 将C1前15字节随机设置,第16字节设置为’x00’
- 将修改后的密文块发送给服务器解密
由于我们修改了C1的最后一个字节,那么根据上文介绍,在解密后C2的明文P2最后一个字节也会进行改变,变成什么我们还不知道,但是我们知道:
- P2[15] = I2[15] xor C1[15]
其中I2是解密算法如AES解密后的中间值,我们不关心具体解密算法,但总有这么个值。然后,根据服务器的返回我们知道有两种可能:
- 返回填充不合法。此时P2[15]未知。
- 返回填充合法。此时P2[15]肯定为0x01,因为只有这样才能出现合法的填充。
如果是第一种情况,我们就继续变异C1[15],直到出现合法的填充,即第二种情况。假设我们在变异到C1[15] = 0x42时才出现合法填充,则此时有:
- P2[15] = I2[15] xor C1[15]
- I2[15] = P2[15] xor C1[15] = 0x01 xor 0x26 = 0x27
回顾一下上图,I2的产生与C1无关,只与C2和密钥key相关,但是我们却计算出了I2[15]的值!因此我们可以用I2[15]异或上变异前的C1[15]从而获得原始的明文。
- P2[15] = 0x27 xor C1[15]
这就是Padding Oracle攻击的思路。
五、下一个字节
为了完成攻击,我们继续使用类似方式猜解I2中更多的内容。
- 将C1前14字节设置为随机值
- C1[14]设置为0×00
- C1[15]设置为能令P2[15] = 0x02的值
- P2[15] = I2[15] xor C1[15]
- C1[15] = P2[15] xor I2[15] = 0x02 xor 0x27 = 0x25
即将C1[15]固定为0×25,继续爆破C1[14]知道出现合法的填充,此时P2[14]=0x02,假设出现合法填充时候爆破的C1[14]值为0×68:
- P2[14] = I2[14] xor C1[14] = 0x02
- I2[14] = P2[14] xor C1[14] = 0x02 xor 0x68 = 0x6A
再一次,我们获得了真实的I2[14]值,从何可以算出原始的明文P2[14]。以此类推,最终我们可以计算出完整的明文P2内容。
六、下一个块
根据上述方法,我们已经可以还原最后一个密文块的明文了。而对于CBC模式,每个密文块的解密仅和当前块以及前一个块相关,因此上述攻击可以应用到所有块中,除了第一个。
第一个块的加解密使用初始化向量IV进行,对此没有通用破解方法。但是CBC加密中IV也不是必须保密的,因此在实践中通常会组合到密文的最前面或者最后面,其长度和块大小相同。如果一定要解密第一个块,可以使用这种猜测方法。
七、示例
实践出真知,我们来看一个具体的例子。首先用Flask写一个简单的应用,如下:
- #!/usr/bin/env python3
- import binascii
- import string
- import random
-
- from Crypto.Cipher import AES
- from Crypto.Util.Padding import pad, unpad
- from flask import Flask, request
-
- app = Flask(__name__)
- db = {}
- BSIZE = 16
- secret = b'x26' * BSIZE
-
- def get_iv():
- return b'x00' * BSIZE
-
- def decrypt(data):
- datadata = data.encode()
- data = binascii.unhexlify(data)
- iv = data[:BSIZE]
- engine = AES.new(key=secret, mode=AES.MODE_CBC, iviv=iv)
- datadata = data[BSIZE:]
- data = engine.decrypt(data)
- data = unpad(data, BSIZE)
- return data.decode()
-
- def encrypt(data):
- datadata = data.encode()
- iv = get_iv()
- engine = AES.new(key=secret, mode=AES.MODE_CBC, iviv=iv)
- return binascii.hexlify(iv + engine.encrypt(pad(data, BSIZE))).decode()
-
- @app.route('/dec/<data>')
- def dec(data):
- # print('dec:', data)
- try:
- key = decrypt(data)
- except Exception as e:
- return 'Error: ' + str(e)
- if key not in db:
- return 'Error: invalid key'
- return db[key]
-
- @app.route('/enc/<key>')
- def enc(key):
- db[key] = 'valid'
- return encrypt(key)
-
- app.run(debug=False)
(编辑:济南站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|