商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python如何对文件进行二进制加密与解密操作_基于cryptography库实现AES加密

Python如何对文件进行二进制加密与解密操作_基于cryptography库实现AES加密

  发布于2026-07-20 阅读(0)

扫一扫,手机访问

在Python中处理文件加密,尤其是用到AES这类对称加密算法时,很多开发者都遇到过“ValueError: Invalid key size”这个拦路虎。其实,这背后往往是密钥长度管理不当,或者对加密流程的理解不够清晰。归根结底,AES本身只接受三种固定长度的密钥:16字节、24字节或32字节。如果你传进去一个“HelloWorld”这种长度不对的字符串,或者一个随意生成的字节串,它自然会报错。

解决这个问题的关键,不在于硬编码一个16字节的密钥,而在于使用一个可靠的密钥派生函数(KDF)。换句话说,你可以由用户输入一个“密码”,然后通过算法把这个密码转化成符合AES要求的固定长度密钥。在生产环境中,Scrypt算法是当前对抗GPU暴力破解的强力武器,也是推荐的做法。它的核心逻辑就像下面这样:

from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives import hashes
import os

password = b"my_secret_pass"
salt = os.urandom(16)  # 每次加密必须用新 salt
kdf = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1)
key = kdf.derive(password)
  • 这里n=2**14是CPU/内存成本参数,太低不安全,太高影响性能,这个值算是一个比较稳妥的起点。
  • salt(盐值)必须随密文一起保存,通常放在加密文件的开头。否则,解密时没有盐值,无法重新生成密钥,你就永远打不开文件了。
  • 一个常见的错误是直接用hashlib.sha256(password).digest()来生成密钥。这种做法不加盐、无迭代,容易被彩虹表一锅端,绝对要避免。

如何用AES-GCM安全地加密整个二进制文件

确定了密钥,下一步就是加密方式。AES-GCM是当前的首选方案,因为它不仅提供加密,还自带完整性校验,能有效防止密文被篡改。不过,使用GCM模式有一个绝对不能碰的红线:它的初始化向量(IV,在GCM里也叫nonce)必须是唯一的,绝对不能重复使用。

一个典型的文件加密流程,会包含盐值、nonce、密文以及认证标签。下面这个代码片段展示了这个过程:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os

def encrypt_file(input_path, output_path, key):
    # 生成唯一 nonce(12 字节是 GCM 推荐长度)
    nonce = os.urandom(12)
    # 构建 cipher 并获取加密器
    cipher = Cipher(algorithms.AES(key), modes.GCM(nonce))
    encryptor = cipher.encryptor()

    # 读取原文件并分块加密(避免大文件爆内存)
    with open(input_path, "rb") as f_in, open(output_path, "wb") as f_out:
        # 先写 nonce(实际中这里还应写 salt,若未外置)
        f_out.write(nonce)
        while chunk := f_in.read(64 * 1024):  # 64KB 块
            encrypted_chunk = encryptor.update(chunk)
            f_out.write(encrypted_chunk)
        # 写入认证标签(必须在最后)
        f_out.write(encryptor.finalize())
  • 切记,不要使用时间戳或硬编码作为nonce,os.urandom(12)是唯一可靠的随机来源。
  • 加密后的文件会比原文大一些,需要多出12字节(nonce)+ 16字节(GCM tag)。如果内嵌盐值,还要额外加16字节。
  • 一个常见的陷阱是:encryptor.finalize()只能调用一次,而且必须在所有update()操作之后,绝不能放在循环里。

解密时遇到InvalidTag错误怎么办

解密时遇到InvalidTag错误,是很多新手最头疼的问题。这个错误信息本身就告诉我们:密文不可信了。它意味着数据可能被篡改、损坏,或者解密时使用的密钥、nonce、盐值不匹配。

排查这个问题,需要遵循严格的顺序:

  • 首先确认解密的密钥与加密时完全一致,哪怕是字节级的差异,比如多了个空格或换行,都会导致失败。
  • 其次,确认读取的nonce确实是加密时写入的前12个字节,不多不少。
  • 然后,确认Scrypt的盐值、参数n、r、p与加密时完全相同。
  • 最后,也是容易被忽略的一点:确认文件没有被文本编辑器意外转码。比如,Windows的记事本有时会把二进制文件当作ANSI文本打开并保存,这会破坏文件结构。因此,操作文件时务必使用rbwb模式。

下面是解密流程的关键代码片段:

def decrypt_file(input_path, output_path, password, salt):
    kdf = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1)
    key = kdf.derive(password)

    with open(input_path, "rb") as f_in:
        nonce = f_in.read(12)  # 严格读 12 字节
        cipher = Cipher(algorithms.AES(key), modes.GCM(nonce))
        decryptor = cipher.decryptor()

        # 读剩余全部为密文,注意 tag 在最后 16 字节
        ciphertext = f_in.read()
        tag = ciphertext[-16:]
        ciphertext = ciphertext[:-16]

        try:
            plaintext = decryptor.update(ciphertext) + decryptor.finalize_with_tag(tag)
        except Exception as e:
            raise ValueError("Decryption failed: InvalidTag or bad key") from e

    with open(output_path, "wb") as f_out:
        f_out.write(plaintext)

实际落地时最容易被忽略的三个细节

理论与代码都写好了,但在实际项目中,有几个细节常常会让人抓狂。

第一,盐值和nonce的存储位置必须清晰约定。一个常见的做法是采用“文件头”结构:[salt(16)][nonce(12)][ciphertext][tag(16)]。解密时,必须严格按照这个偏移量来读取,否则拿到的数据全是错的。

第二,cryptography库本身不会自动处理文件权限。加密操作完成后,原始文件依然存在,你需要手动通过os.remove()将其删除,或者采用更安全的擦除方法,比如多次覆写。

第三,在Windows环境下,如果文件路径包含中文或空格,虽然open()函数可能不会立即报错,但有可能会在后续环节静默失败。为了保险起见,建议在操作前先使用pathlib.Path(input_path).resolve()来校验路径是否存在且可读,确保万无一失。

本文转载于:https://www.php.cn/faq/2323661.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注