基于Python 3的AES加解密问题:不同文件密钥致解密文件损坏
问题分析与解决方案
我来帮你排查下这个问题——按文件名生成密钥后解密文件损坏,大概率是密钥生成、存储或者IV处理环节出了一致性问题,咱们一步步理清楚:
1. 密钥生成的一致性是核心
你提到用文件名生成密钥,但加密和解密时如果对文件名的处理逻辑不一样,生成的密钥就会完全不同,这是最常见的坑:
- 比如加密时用的是带路径的文件名(
./docs/test.txt),解密时却只用了文件名(test.txt),SHA256哈希结果肯定不一样; - 或者文件名的大小写不一致(加密用
Test.txt,解密用test.txt),也会导致密钥差异; - 还有编码问题:如果文件名包含非ASCII字符,加密时用
filename.encode('gbk'),解密时用utf-8编码,哈希也会出错。
正确的文件名处理方式:统一标准化文件名,比如只取文件basename、统一转小写、固定用utf-8编码:
import hashlib import os def get_key_from_filename(filename): # 标准化:取文件名(不含路径)、转小写、utf-8编码 normalized_name = os.path.basename(filename).lower().encode('utf-8') # SHA256生成32字节密钥(适配AES-256,若要AES-128取前16字节即可) return hashlib.sha256(normalized_name).digest()
加密和解密时必须调用同一个get_key_from_filename函数,传入完全对应的文件名(比如加密用原文件名,解密时要从加密后的文件名还原原文件名,比如test.txt.enc还原成test.txt)。
2. 密钥必须用二进制模式存储/读取
密钥是SHA256生成的二进制字节串,不是文本字符串!如果加密时用文本模式写密钥,解密时读出来的就不是原始密钥了:
- 错误示例:用
open('key.txt', 'w').write(key),二进制里的不可打印字符会被转成乱码; - 正确做法:始终用二进制模式(
wb/rb)处理密钥文件。
加密时写密钥:
key = get_key_from_filename('test.txt') # 二进制写入密钥文件 with open('test_key.bin', 'wb') as f: f.write(key)
解密时读密钥:
with open('test_key.bin', 'rb') as f: key = f.read() # 验证密钥长度:AES支持16/24/32字节(对应128/192/256位) if len(key) not in (16, 24, 32): raise ValueError("密钥长度不符合AES要求")
3. 随机IV必须和密文一起保存
你用了随机IV,这是正确的,但解密时必须用和加密时完全相同的IV才能正常解密!如果加密时只保存了密文,没把IV存进去,解密时即使密钥正确,结果也会损坏。
正确的加密流程(以CBC模式为例):
from Crypto.Cipher import AES from Crypto.Random import get_random_bytes def encrypt_file(plain_path, enc_path): key = get_key_from_filename(plain_path) # 生成16字节随机IV(AES块大小固定为16字节) iv = get_random_bytes(16) cipher = AES.new(key, AES.MODE_CBC, iv) # 读取明文并做PKCS7填充(AES要求明文长度是块大小的倍数) with open(plain_path, 'rb') as f: plain_data = f.read() # PKCS7填充:计算需要填充的字节数,填充对应数值的字节 pad_len = 16 - (len(plain_data) % 16) padded_data = plain_data + bytes([pad_len]) * pad_len # 加密并保存IV+密文(IV放在密文开头) cipher_data = cipher.encrypt(padded_data) with open(enc_path, 'wb') as f: f.write(iv + cipher_data) # 保存密钥(可选,其实也可以解密时重新用文件名生成,避免密钥文件丢失) with open(f'{os.path.basename(plain_path)}_key.bin', 'wb') as f: f.write(key)
对应的解密流程:
def decrypt_file(enc_path, plain_path): # 从加密文件名还原原文件名(比如test.txt.enc → test.txt) original_filename = os.path.splitext(os.path.basename(enc_path))[0] key = get_key_from_filename(original_filename) # 读取IV和密文:前16字节是IV,剩下的是密文 with open(enc_path, 'rb') as f: iv = f.read(16) cipher_data = f.read() cipher = AES.new(key, AES.MODE_CBC, iv) padded_plain = cipher.decrypt(cipher_data) # 去除PKCS7填充 pad_len = padded_plain[-1] plain_data = padded_plain[:-pad_len] # 写入解密后的文件 with open(plain_path, 'wb') as f: f.write(plain_data)
4. 验证文件完整性(可选但推荐)
为了确保解密后的文件和原文件一致,可以在加密时计算原文件的SHA256哈希,和密文/密钥一起保存,解密后再计算哈希对比:
# 加密时计算哈希 import hashlib with open(plain_path, 'rb') as f: file_hash = hashlib.sha256(f.read()).hexdigest() # 把哈希写入密钥文件或者单独的哈希文件 # 解密后验证 with open(plain_path, 'rb') as f: decrypted_hash = hashlib.sha256(f.read()).hexdigest() if decrypted_hash != saved_hash: print("文件损坏,可能密钥或IV错误!")
总结下常见的出错点
- 文件名处理不一致导致密钥不同;
- 密钥用文本模式读写导致二进制损坏;
- 随机IV未随密文保存,解密时IV不匹配;
- 加密和解密的填充方式、AES模式不一致。
按照上面的步骤调整你的代码,应该就能解决解密文件损坏的问题了!
内容的提问来源于stack exchange,提问作者Mritunjay Kumar




