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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:44:41