You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyCryptodome AES-CBC分块加密大文件的正确实现及问题排查

AES-256 CBC分块加密大文件的正确实现问题

我想用Python的PyCryptodome库,采用AES-256 CBC模式加密100GB以上的大文件。为避免将整个文件加载到内存中,计划分块读取输入文件,逐块加密后写入输出文件。

但PyCryptodome的文档里没有多次调用encrypt()加密长明文的示例,全是短明文单次调用的案例。我原本假设只要输入块大小是16字节(AES-CBC的块大小)的整数倍,除最后一块外无需添加填充,结果解密时出现了填充错误。

现在我发现要解密成功,加密时得给每个块都加填充,且解密时要按“输入块大小+16字节”的单位读取,这意味着解密端必须知晓加密时的块大小,感觉这不是正确的实现方式。虽然当前方式能完成加解密,但不确定是否合规。看到关于是否要给每个块加填充的说法不一致,若无需给每个块加填充,希望能得到正确实现多次调用encrypt()和decrypt()完成长明文加解密的方法。

编辑:以下代码在解密第一块时抛出ValueError: Padding is incorrect:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt_file(infile, outfile, aeskey, iv):
    cipher = AES.new(aeskey, AES.MODE_CBC, iv)
    with open(infile, "rb") as fin:
        with open(outfile, "wb") as fout:
            while True:
                data = fin.read(16 * 32)
            
                if len(data) ==0:
                    break
                insize = len(data)
                if insize == (16 * 32):
                    padded_data = data
                else:
                    padded_data = pad(data, AES.block_size)

                fout.write(cipher.encrypt(padded_data))

def decrypt_file(infile, outfile, aeskey, iv):
    cipher = AES.new(aeskey, AES.MODE_CBC, iv)
    with open (infile, "rb") as fin:
         with open(outfile, "wb") as fout:
            while True:
                data = fin.read(16 * 32)
                if len(data) == 0:
                    break
                fout.write(unpad(cipher.decrypt(data), AES.block_size))

问题分析与正确实现

错误原因

你的代码存在两个核心问题:

  1. 解密逻辑错误:只有最后一块加密数据需要解填充,但你对所有读取到的块都调用了unpad(),导致非最后一块的无填充解密数据被错误解填充,触发“Padding is incorrect”异常。
  2. 块处理逻辑混乱:加密时仅最后一块需要填充,中间完整块无需操作,但解密时没有区分中间块和最后块的处理逻辑。

正确实现思路

  • 加密阶段:
    • 选择AES块大小(16字节)的整数倍作为分块大小(比如16MB,可根据内存情况调整),减少IO次数并满足CBC模式的块对齐要求。
    • 仅对最后一块(长度小于设定分块大小的块)执行填充操作,中间完整块直接加密。
  • 解密阶段:
    • 分块读取加密文件,块大小与加密时保持一致。
    • 中间块解密后直接写入输出文件,仅最后一块解密后执行解填充操作。

修正后的代码

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt_file(infile, outfile, aeskey, iv):
    cipher = AES.new(aeskey, AES.MODE_CBC, iv)
    # 设定16MB的分块大小,可根据内存调整,必须是16的整数倍
    chunk_size = AES.block_size * 1024 * 1024
    with open(infile, "rb") as fin, open(outfile, "wb") as fout:
        while True:
            data = fin.read(chunk_size)
            if not data:
                break
            # 仅最后一块需要填充
            if len(data) < chunk_size:
                data = pad(data, AES.block_size)
            fout.write(cipher.encrypt(data))

def decrypt_file(infile, outfile, aeskey, iv):
    cipher = AES.new(aeskey, AES.MODE_CBC, iv)
    chunk_size = AES.block_size * 1024 * 1024  # 和加密时的分块大小一致
    with open(infile, "rb") as fin, open(outfile, "wb") as fout:
        prev_chunk = b""
        while True:
            current_chunk = fin.read(chunk_size)
            if not current_chunk:
                break
            # 前一个块不是最后一块,解密后直接写入
            if prev_chunk:
                fout.write(cipher.decrypt(prev_chunk))
            prev_chunk = current_chunk
        # 处理最后一块,解密后执行解填充
        if prev_chunk:
            decrypted_last = cipher.decrypt(prev_chunk)
            fout.write(unpad(decrypted_last, AES.block_size))

关键说明

  • 分块大小选择:必须是AES块大小(16字节)的整数倍,确保中间块无需填充,同时平衡内存占用与IO效率。
  • 填充逻辑:CBC模式仅要求明文总长度为块大小的整数倍,因此仅需对最后一块不足分块大小的数据进行填充。
  • 解密区分处理:通过保留前一个块的方式,确保最后一块单独执行解填充,避免对中间块的无填充数据误操作。

内容的提问来源于stack exchange,提问作者Nevo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 02:36:19