| 
                         再回想一下我们介绍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) 
 
                          (编辑:泰州站长网) 
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! 
                     |