我想编写一个生成器,以给定大小的块来加密文件并一一返回块。我还想验证有效负载,因此我为此选择了 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 用于该目的的理解几乎是正确的。 但是,当前的实现存在一些缺陷:
-
不应在 GCM 模式下为每个块生成单独的 MAC。 GCM 模式设计为对整个消息进行身份验证,而不是对各个块进行身份验证。 为每个块发送单独的 MAC 会破坏 GCM 提供的安全性,并导致身份验证标记的开销更大。
-
正在更新每个块的加密器,这会重置内部状态并导致不正确的加密。 应该使用相同的加密器对象来加密所有块。
-
不应在 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]
说明:
-
我们使用
os.path.getsize()
获取文件大小,并使用struct.pack()
和struct.unpack()
对其进行编码和解码。 -
我们生成一个唯一的 IV 并将其连同文件大小一起发送到解密器。
-
我们创建一个
AES.new()
对象并使用update()
方法对每个块进行加密。 -
加密完成后,我们使用
hexdigest()
方法检索身份验证标记,并将其发送到解密器。 -
解密器使用相同的密钥和 IV 创建一个
AES.new()
对象,并使用update()
方法对每个块进行解密。 -
一旦所有块都被解密,解密器就会使用
hexverify()
方法来验证身份验证标记。
重要提示:
- 切勿 在不同的消息或加密操作中重复使用相同的 IV。
- 密钥应使用加密安全的随机数生成器生成并安全存储。
- 应该考虑添加错误处理和数据验证机制以确保数据完整性和安全性。
通过使用此修正后的实现,可以利用 AES-GCM 的优势来安全有效地逐块加密和身份验证文件。
标签:python,python-3.x,encryption,cryptography,aes From: 78785635