完整性检查导致 AES 解密在 Python 3 中失败

Integrity check causes AES decryption to fail in Python 3


我目前正在使用 pycryptodome 及其 AES 密码开发一个简单的加密和解密工具。但是将明文填充到块大小时出现问题。
通常人们通过添加零来做到这一点。但是当明文的最后一个字节也为零时会发生什么?我尝试了一些我在网上找到的其他 padunpad 函数,但没有任何效果。

def _pad(self, s):
    rest = (AES.block_size - len(s)) % AES.block_size
    if not s:
        rest = AES.block_size
    return s + (rest * bytes([rest]))
def _unpad(self, s):
    return s[:-ord(s[len(s) - 1:])]

这是我多次看到的另一个功能,但它不起作用。

加密

def encrypt(self, plaintext, check_integrity=False):
    if check_integrity is True:
        plaintext += struct.pack('L', zlib.crc32(plaintext))

    plaintext = self._pad(plaintext)
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(self.key, AES.MODE_CBC, iv)
    ciphertext = iv + cipher.encrypt(plaintext)
    return ciphertext

我正在使用 zlib.crc32() 创建校验和,以便在解密后检查完整性。我只是将它附加在明文的末尾。可能跟这个有关,我不确定。

解密

def decrypt(self, ciphertext, check_integrity=False):
    iv = ciphertext[:AES.block_size]
    cipher = AES.new(self.key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext[AES.block_size:])
    plaintext = self._unpad(plaintext)

    if check_integrity is True:
        crc, plaintext = (plaintext[-4:], plaintext[:-4])
        if not crc == struct.pack('L', zlib.crc32(plaintext)):
            return False
    return plaintext

ciphertext 解密并取消填充后,最后 4 个字节应等于 zlib.crc36(plaintext)。但这种情况并非如此。我的猜测是 pad and/or unpad 函数弄乱了一些字节,导致完整性检查失败。

感谢阅读!

我终于找到了问题的解决方案:

如果不需要填充 (rest = 0),_unpad() 方法会采用明文并对其进行取消填充。这就是我添加的原因:

if rest == 0:
    rest = AES.block_size

_pad()方法。
这样一切正常!

我不会更正您的代码,而是要告诉您您做事的方式不对。这是错误加密 API 给开发人员带来问题的典型示例。 Crypto APIs 应该为你做填充,不需要你自己实现它。

填充的正确方法是使用像 pkcs #7.

这样的标准

进行完整性检查的正确方法是使用为您完成完整性检查的操作模式(例如 GCM),或者使用加密完整性检查,例如 HMAC。完整性检查应该在密文上进行,并且应该在解密之前完成。否则,您将面临填充 oracle 攻击。我敢打赌您的代码很容易受到此类攻击。