首页 > 编程问答 >使用 AES-GCM 分块加密文件

使用 AES-GCM 分块加密文件

时间:2024-07-24 05:58:02浏览次数:12  
标签:python python-3.x encryption cryptography aes

我想编写一个生成器,以给定大小的块来加密文件并一一返回块。我还想验证有效负载,因此我为此选择了 AES-GCM。

为什么我要分块加密而不是一次性加密整个文件?我通过网络发送这些块,因此我不是加密整个(可能很大)文件,将其存储在其他地方,然后在进行网络传输时再次对其进行分块,而是加密要传输的单个块并立即将其发送过来

但是,我非常不确定我实现此方法的方式是否正确,并且我找不到任何适用于我的用例的有用文档。

from Crypto.Cipher import AES
from Crypto import Random
import struct

CHUNK_SIZE = 32
AES_BLOCK_SIZE = 16

def encryptFileInChunks(key: bytes, infile: str, file_size: int):
    iv = Random.new.read(16)
    encryptor = AES.new(key, AES.MODE_GCM, iv, mac_len=16)

    with open(infile, 'r') as infile:
        # write the original file size as little endian 8 byte int
        # so that we can remove the padding after decryption by
        # truncating to this original length
        yield struct.pack("<Q", file_size)
        # iv for decryption
        yield iv

    while True:
        chunk = infile.read(CHUNK_SIZE)
        if len(chunk) == 0:
            break
        if len(chunk) % AES_BLOCK_SIZE != 0:
            # add padding to make a valid block
            chunk += b" " * (AES_BLOCK_SIZE - len(chunk) % AES_BLOCK_SIZE)
        
        c, d = encryptor.encrypt(chunk)

        # only return ciphertext
        yield c, None
    
    # first tuple value being None indicates end of ciphertext,
    # return the mac
    yield None, d

这是否可以按照所述正确计算整个有效负载的 MAC通过 GCM 模式还是我只返回最后一块的 MAC?我怎样才能以类似的方式计算整个密文的 MAC,而不必为每个块单独发送 (密文,mac) 对?

我想另一种方法是只使用 AES-CBC 并使用一些类似于 HMAC 来单独进行身份验证,但这会增加一点开销,并且使用 GCM 会使事情变得更快。


对逐块加密和身份验证文件并将 AES-GCM 用于该目的的理解几乎是正确的。 但是,当前的实现存在一些缺陷:

  1. 不应在 GCM 模式下为每个块生成单独的 MAC。 GCM 模式设计为对整个消息进行身份验证,而不是对各个块进行身份验证。 为每个块发送单独的 MAC 会破坏 GCM 提供的安全性,并导致身份验证标记的开销更大。

  2. 正在更新每个块的加密器,这会重置内部状态并导致不正确的加密。 应该使用相同的加密器对象来加密所有块。

  3. 不应在 GCM 模式下重复使用相同的 IV 进行多次加密。 这会损害安全性。 IV 必须是每个加密唯一的,并且是随机生成的或从加密安全的计数器派生出来的。

以下是使用 AES-GCM 正确逐块加密和身份验证文件的修正版 Python 代码:

from Crypto.Cipher import AES
from Crypto import Random
import struct

CHUNK_SIZE = 32 * 1024  # 使用更大的块大小以提高效率
AES_BLOCK_SIZE = 16

def encrypt_file_in_chunks(key: bytes, infile: str):
    """使用 AES-GCM 逐块加密文件。"""

    # 生成一个唯一的 IV
    iv = Random.new().read(16)

    # 创建一个 AES-GCM 加密器
    encryptor = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=16)

    # 获取文件大小
    file_size = os.path.getsize(infile)

    # 发送文件大小和 IV
    yield struct.pack("<Q", file_size)
    yield iv

    with open(infile, 'rb') as infile:
        while True:
            chunk = infile.read(CHUNK_SIZE)
            if len(chunk) == 0:
                break

            # 加密块
            ciphertext = encryptor.update(chunk)

            # 发送密文
            yield ciphertext

    # 生成并发送身份验证标记
    tag = encryptor.hexdigest()
    yield tag.encode('utf-8')


def decrypt_file_in_chunks(key: bytes, infile: str):
    """使用 AES-GCM 逐块解密文件。"""

    # 读取文件大小
    file_size = struct.unpack("<Q", infile.read(8))[0]

    # 读取 IV
    iv = infile.read(16)

    # 创建一个 AES-GCM 解密器
    decryptor = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=16)

    # 解密块
    decrypted_data = b''
    while True:
        chunk = infile.read(CHUNK_SIZE)
        if len(chunk) == 0:
            break
        decrypted_data += decryptor.update(chunk)

    # 读取并验证身份验证标记
    received_tag = infile.read().decode('utf-8')
    try:
        decryptor.hexverify(received_tag)
    except ValueError:
        raise ValueError("身份验证失败!")

    # 返回解密后的数据
    return decrypted_data[:file_size]

说明:

  1. 我们使用 os.path.getsize() 获取文件大小,并使用 struct.pack() struct.unpack() 对其进行编码和解码。

  2. 我们生成一个唯一的 IV 并将其连同文件大小一起发送到解密器。

  3. 我们创建一个 AES.new() 对象并使用 update() 方法对每个块进行加密。

  4. 加密完成后,我们使用 hexdigest() 方法检索身份验证标记,并将其发送到解密器。

  5. 解密器使用相同的密钥和 IV 创建一个 AES.new() 对象,并使用 update() 方法对每个块进行解密。

  6. 一旦所有块都被解密,解密器就会使用 hexverify() 方法来验证身份验证标记。

重要提示:

  • 切勿 在不同的消息或加密操作中重复使用相同的 IV。
  • 密钥应使用加密安全的随机数生成器生成并安全存储。
  • 应该考虑添加错误处理和数据验证机制以确保数据完整性和安全性。

通过使用此修正后的实现,可以利用 AES-GCM 的优势来安全有效地逐块加密和身份验证文件。

标签:python,python-3.x,encryption,cryptography,aes
From: 78785635

相关文章

  • 使用 Python-PlexAPI 获取 plex 上所有好友的关注列表
    有关如何接收我的plex服务器上所有用户的监视列表的任何提示。我正在根据一些规则创建自动删除,其中一个规则是,如果电影位于用户观看列表中,则不应删除该电影。我遇到了麻烦,因为所有与观看列表相关的内容都在MyPlexAccount上。lexapi.myplex.MyPlexAccount具有我的用......
  • 如何在 Python 中查看与 Azure OpenAI 助手关联的所有上传文件?
    我正在使用Python对文档中的问题进行基准测试,并在jupyter笔记本中实例化了我的助手。我想确认助手是否有我上传的文件,但似乎找不到有关此功能将使用什么功能的文档。使用适用于AzureOpenAI的最新版本的PythonAPI。目前,无法使用AzureOpenAI的PythonAPI直接查看......
  • 如何在Python中计算小数?
    我正在创建一个计算器来用python计算企业的利润,但到目前为止我只能使用整数。这是我的代码示例:Gross=int(input("PleaseentertotalGrossRevenuefortheFiscalYear"))NetTaxes=int(Gross)*0.1所以我将会计年度的总收入乘以按“税率”计算,但我只能使用......
  • 如何使用 Python 打开 Google Firestore 上的特定数据库?
    我正在使用Firebase并使用以下代码从Firestore设置/检索文档:importfirebase_adminfromfirebase_adminimportcredentials,firestorecred=credentials.ApplicationDefault()firebase_admin.initialize_app(cred,options={"projectId":"huq-jimbo"})fires......
  • 如何使用 Python 和 Numpy 重现 Matlab 文件读取以解码 .dat 文件?
    我有一个Matlab脚本,可以读取编码的.dat文件,对其进行解码并保存。我试图使用numpy将其转换为Python。我发现对于同一个文件,我得到不同的输出结果(python数字没有意义)。该代码最初作为从串行端口读取的脚本的一部分运行,因此是数据的结构。我首先认为位移是问题所在,因为......
  • 在Python中调整pdf页面大小
    我正在使用python裁剪pdf页面。一切正常,但如何更改页面大小(宽度)?这是我的裁剪代码:input=PdfFileReader(file('my.pdf','rb'))p=input.getPage(1)(w,h)=p.mediaBox.upperRightp.mediaBox.upperRight=(w/4,h)output.addPage(p)当我裁剪页面时,我也需要......
  • 如何使用 python 更改资源管理器窗口中的路径?
    没有人知道如何在不使用python打开新实例的情况下更改资源管理器窗口中的当前路径吗?例如,如果用户使用C:\Users\User打开资源管理器窗口。然后我必须将该路径更改为C:\Windows\System32例如。提前致谢。很遗憾,无法直接使用Python更改现有文件资源管理器窗口的......
  • python 以及将数组传递给函数的问题
    我需要求解一些常微分方程$\frac{dy}{dx}=f(x)=x^2ln(x)$并继续在限制0之间创建数组xpt。<=xpt<=2因为我必须小心xpt=0,所以我将函数定义如下deff(x):ifx<=1.e-6:return0.else:returnnp.square(x)*np.log(x)我的调用程序读取Np......
  • 如果 Python 脚本正在使用文件夹,如何在文件资源管理器中进行更改时防止 Windows 的“
    我有一个简单的脚本,显示在QTreeView中的QListView中选择的目录的内容,我想添加打开文件资源管理器的功能,以让用户编辑目录内的内容。但是,添加新的文件夹和文件可以,但删除或移动文件夹或文件会提示“文件夹正在使用”错误:此操作无法完成,因为该文件已在另一个程......
  • 如何使用 Python API 获取每个模型的活跃用户列表、最后登录信息
    我想通过PythonAPI获取我的dbt项目的所有模型中的活动或非活动用户列表。这可能吗?我尝试列出模型,但无法获取用户信息,如用户名、项目、以及上次活动或上次登录。不幸的是,dbt本身并不跟踪你所寻找的用户活动数据(最后登录、活跃用户等)。dbt的主要功能是转换数据,而不......