1

我正在尝试加密大于 1GB 的文件。我不想把它全部读到记忆中。我为这项任务选择了 Fernet (cryptography.fernet),因为它是最受推荐的(比不对称解决方案更快)。

我生成了密钥。然后我创建了一个脚本来加密:

    key = Fernet(read_key())

    with open(source, "rb") as src, open(destination, "wb") as dest:
        for chunk in iter(lambda: src.read(4096), b""):
            encrypted = key.encrypt(chunk)
            dest.write(encrypted)

和解密:

    key = Fernet(read_key())

    with open(source, "rb") as src, open(destination, "wb") as dest:
        for chunk in iter(lambda: src.read(4096), b""):
            decrypted = key.decrypt(chunk)
            dest.write(decrypted)

加密有效 - 不足为奇,但解密不是。首先,我认为它可能会起作用,但事实并非如此。我猜块大小在加密时会增加,然后当我读取 4096 字节时,它不是一个完整的加密块。我尝试解密时出错:

Traceback (most recent call last):
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/fernet.py", line 119, in _verify_signature
    h.verify(data[-32:])
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/hazmat/primitives/hmac.py", line 74, in verify
    ctx.verify(signature)
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/hazmat/backends/openssl/hmac.py", line 75, in verify
    raise InvalidSignature("Signature did not match digest.")
cryptography.exceptions.InvalidSignature: Signature did not match digest.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/redacted/path/main.py", line 63, in <module>
    decrypted = key.decrypt(chunk)
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/fernet.py", line 80, in decrypt
    return self._decrypt_data(data, timestamp, time_info)
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/fernet.py", line 137, in _decrypt_data
    self._verify_signature(data)
  File "/redacted/path/venv/lib/python3.7/site-packages/cryptography/fernet.py", line 121, in _verify_signature
    raise InvalidToken
cryptography.fernet.InvalidToken

有没有办法解决这个问题?也许有比 fernet 更好(更简单)的解决方案?

4

4 回答 4

3

Fernet 不应该以流媒体方式使用。他们在文档中解释说:

从文档(最后一部分):

限制

Fernet 非常适合加密容易放入内存的数据。作为一项设计功能,它不会暴露未经身份验证的字节。这意味着完整的消息内容必须在内存中可用,这使得 Fernet 目前通常不适合非常大的文件。

于 2021-09-24T10:34:25.830 回答
1

我刚刚遇到了同样的问题 - 我感觉到你的痛苦兄弟。

Fernet 存在一些问题,使其与您的方法不兼容:

  1. Fernet 吐出 urlsafe_base64 编码数据。这意味着每消耗 3 个字节的未加密数据,Fernet 就会吐出​​ 4 个字节的加密数据。

这可以防止您在加密和解密时使用相同的“块大小”,因为解密块大小必须更大。不幸的是,用urlsafe_b64decode/处理数据urlsafe_b64encode也不起作用,因为:

  1. Fernet 似乎在加密数据的某处添加了某种摘要/校验和/元数据。

可能有一种直接的方法可以计算出这个摘要的大小并调整解密块的大小以适应这个 - 但我想避免使用“魔法常数”做一些事情,因为这感觉很恶心。

我最终确定的解决方案实际上非常优雅。它的工作原理如下:

加密:

  1. 读取n数据字节 ( raw_chunk)
  2. 使用 Fernet加密n字节以创建m字节块 ( enc_chunk)。
  3. 用于len(enc_chunk).to_bytes(4, "big")将加密块的大小写入文件
  4. 将加密的块写入文件
  5. 当我读到一个b""

解密:

  1. 读取4数据字节 ( size)
  2. 如果数据是一个中断b""
  3. int.from_bytes(size, "big")使用( num_bytes)将这 4 个字节转换为整数
  4. 读取num_bytes加密数据
  5. 使用 Fernet 毫无问题地解密此数据
于 2022-02-10T16:03:58.393 回答
0

由于内存的限制,我们可以使用块来加密和解密。

#
# encrypt
#
key = b'Ke0Ft_85-bXQ8GLOOsEI6JeT2mD-GeI8pkcP_re8wio='
in_file_name = 'plain.txt'
out_file_name = 'encypted.txt'
with open(in_file_name, "rb") as fin, open(out_file_name, "wb") as fout:
    while True:
        block = fin.read(524288)  # 2^19
        if not block:
            break
        f = Fernet(key)
        output = f.encrypt(block)
        print('encrypted block size: ' + str(len(block)))  # returns 699148
        fout.write(output)

#
# decrypt
#
in_file_name = 'encrypted.txt'
out_file_name = 'plain2.txt'
with open(in_file_name, "rb") as fin, open(out_file_name, "wb") as fout:
    while True:
        block = fin.read(699148)
        if not block:
            break
        f = Fernet(key)
        output = f.decrypt(block)
        fout.write(output)

块大小值确定如下:

从 4096 作为加密块大小开始,它产生了恒定字节数的一致输出,除了 <4096 字节的最终块。最后,它被提升到 524288,它再次返回了一致的字节数 - 699148,除了 <699148 字节的最终块。

使用524288加密和699148字节解密,35GB以上的大文件已经成功加解密。

block = fin.read(524288) # 2^19
print('encrypted block size: ' + str(len(block)))  # returns 699148
于 2021-11-12T01:00:34.813 回答
0

您可以轻松地将任何非流式算法(如 Fernet)转换为流式算法,只需将输入数据切成块并将块长度存储在加密文件中,@tlonny已经建议了这一点。这只有在您负担得起任何格式的加密数据文件的情况下才有可能。

可以通过不同的方式将块大小转换为字节。一种是使用struct.pack()struct.unpack() ,就像我在下面的代码中所做的那样。另一种方法是使用int(size).to_bytes(4, 'little')and size = int().from_bytes(size_bytes, 'little')

下面的代码有完整的实现encrypt()decrypt()使用示例(加密 2 MB 的随机数据切成 64 KB 块)。

在线尝试!

def encrypt(key, fin, fout, *, block = 1 << 16):
    import cryptography.fernet, struct
    fernet = cryptography.fernet.Fernet(key)
    with open(fin, 'rb') as fi, open(fout, 'wb') as fo:
        while True:
            chunk = fi.read(block)
            if len(chunk) == 0:
                break
            enc = fernet.encrypt(chunk)
            fo.write(struct.pack('<I', len(enc)))
            fo.write(enc)
            if len(chunk) < block:
                break

def decrypt(key, fin, fout):
    import cryptography.fernet, struct
    fernet = cryptography.fernet.Fernet(key)
    with open(fin, 'rb') as fi, open(fout, 'wb') as fo:
        while True:
            size_data = fi.read(4)
            if len(size_data) == 0:
                break
            chunk = fi.read(struct.unpack('<I', size_data)[0])
            dec = fernet.decrypt(chunk)
            fo.write(dec)

def test():
    import cryptography.fernet, secrets
    key = cryptography.fernet.Fernet.generate_key()
    with open('data.in', 'wb') as f:
        data = secrets.token_bytes(1 << 21)
        f.write(data)
    encrypt(key, 'data.in', 'data.enc')
    decrypt(key, 'data.enc', 'data.out')
    with open('data.out', 'rb') as f:
        assert data == f.read()

if __name__ == '__main__':
    test()
于 2022-02-23T19:48:01.153 回答