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))
问题分析与正确实现
错误原因
你的代码存在两个核心问题:
- 解密逻辑错误:只有最后一块加密数据需要解填充,但你对所有读取到的块都调用了
unpad(),导致非最后一块的无填充解密数据被错误解填充,触发“Padding is incorrect”异常。 - 块处理逻辑混乱:加密时仅最后一块需要填充,中间完整块无需操作,但解密时没有区分中间块和最后块的处理逻辑。
正确实现思路
- 加密阶段:
- 选择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
相关产品推荐
相关产品推荐

