You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

基于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

火山引擎 最新活动