Python中用AES256加密ZIP文件:输出二进制并移除时间戳
解决AES256加密ZIP时的二进制输出与时间戳移除问题
先理清Fernet的局限性
首先得说清楚:Fernet是个封装得很死的对称加密方案,它默认会把加密结果做URL-safe Base64编码(也就是你说的ASCII装甲),这会让数据体积凭空膨胀约33%;另外,Fernet的格式规范里强制包含一个明文的Unix时间戳,用来做过期校验,这个是没法通过Fernet本身移除的。所以要满足你的需求,咱们得绕开Fernet的封装,直接用cryptography库的底层AES加密API。
方案:用AES-GCM实现纯二进制加密(无装甲、无时间戳)
Fernet本质上也是基于AES-GCM的,所以我们直接调用底层API就能完全控制加密流程,完美解决你的两个需求:
1. 生成AES256密钥
AES256需要32字节的密钥,你可以用密码推导(推荐,方便记忆)或者直接生成随机密钥:
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend import os # 用密码生成密钥的方式(需要保存salt用于解密) password = b"your_strong_password_here" salt = os.urandom(16) # 把这个salt存好,解密时必须用 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, backend=default_backend() ) key = kdf.derive(password) # 或者直接生成随机密钥(适合不需要密码的场景) # key = os.urandom(32)
2. 加密ZIP为纯二进制密文
我们用AES-GCM模式(加密+认证双重保障),直接输出二进制数据,完全跳过Base64编码:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes def encrypt_zip(zip_path, key, output_path): # 读取ZIP文件的二进制内容 with open(zip_path, "rb") as f: plaintext = f.read() # 生成随机非ce(GCM推荐用12字节,兼顾安全和性能) nonce = os.urandom(12) # 创建AES-GCM加密器 cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend()) encryptor = cipher.encryptor() # 执行加密,得到密文和认证标签(用于解密时校验数据完整性) ciphertext = encryptor.update(plaintext) + encryptor.finalize() tag = encryptor.tag # 把非ce + 标签 + 密文写入输出文件(顺序自己定,解密时对应即可) with open(output_path, "wb") as f: f.write(nonce) f.write(tag) f.write(ciphertext) # 调用示例 encrypt_zip("my_archive.zip", key, "encrypted_archive.bin")
3. 解密二进制密文
解密时需要按加密时的顺序读取非ce、标签和密文:
def decrypt_zip(encrypted_path, key, salt=None, password=None, output_path): with open(encrypted_path, "rb") as f: nonce = f.read(12) # 读取前12字节非ce tag = f.read(16) # 读取接下来16字节认证标签 ciphertext = f.read() # 剩余部分是密文 # 如果用密码生成密钥,先重新推导密钥 if password and salt: kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, backend=default_backend() ) key = kdf.derive(password) # 创建解密器并验证标签(防止数据被篡改) cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend()) decryptor = cipher.decryptor() # 解密得到原始ZIP数据 plaintext = decryptor.update(ciphertext) + decryptor.finalize() # 写入解密后的ZIP文件 with open(output_path, "wb") as f: f.write(plaintext) # 调用示例(用密钥解密) decrypt_zip("encrypted_archive.bin", key, output_path="decrypted_archive.zip") # 或者用密码+salt解密 # decrypt_zip("encrypted_archive.bin", None, salt=salt, password=b"your_strong_password_here", output_path="decrypted_archive.zip")
关于时间戳的关键说明
用上面的底层AES-GCM方案,我们完全没有加入任何明文时间戳,密文里只有非ce、认证标签和加密后的ZIP数据,完全符合你的需求。
如果非要坚持用Fernet,那移除明文时间戳是不可能的——Fernet的格式定义里,第一个8字节就是明文的Unix时间戳,这是方案的核心部分,根本没法修改。这种情况下,你要么接受时间戳的存在,要么果断放弃Fernet改用上面的底层API。
额外实用建议
- 密钥/盐的存储:如果用密码生成密钥,一定要把salt和密文分开存储(比如存在密钥管理服务或加密的配置文件),别和密文放在一起。如果是随机密钥,务必做好安全备份,丢了就彻底没法解密了。
- 非ce的安全性:每次加密都要生成新的随机非ce,绝对不能重复使用同一个非ce和密钥,否则会直接导致加密方案失效。
- 体积对比:纯二进制输出比Fernet的Base64编码小约33%,比如100MB的ZIP,Fernet加密后约133MB,而我们的方案加密后只比原文件多28字节(非ce+标签),几乎和原文件体积一致。
内容的提问来源于stack exchange,提问作者Zorglub29
相关产品推荐
相关产品推荐

