将内存数据写入加密的 ZIP 或 7Z(不使用磁盘临时文件)

Write in-memory data to an encrypted ZIP or 7Z (without using an on-disk temp file)

我在内存中有 2 GB 的数据(例如 data = b'ab' * 1000000000),我想将其写入加密的 ZIP 或 7Z 文件中。

如何在不将 data 写入临时磁盘文件的情况下执行此操作?

是否可以仅使用 Python 内置工具(+ 可选 7z)?

我已经看过了:

使用 third-party 应用程序(例如 RadeonRAMDisk to emulate disk operations in-memory, but you stated you prefer not to. Another possibility is to extend PyFilesystem 允许在内存文件系统上进行加密 zip-file 操作可能是最简单的。

我不知道 Python,但可以使用 7zip C++ 界面来完成此操作。但是,这是很多工作。这是我用于必须打包 zip 文件的项目的 my implementation 的摘录:

class CArchiveUpdateCallback : public IArchiveUpdateCallback
{
public:
    STDMETHODIMP GetProperty(UInt32 Index, PROPID PropID, PROPVARIANT* PropValue)
    {
        const std::wstring& FilePath = m_FileList[Index].first;
        const std::wstring& ItemPath = m_FileList[Index].second;
        switch (PropID)
        {
        case kpidPath:
            V_VT(PropValue) = VT_BSTR;
            V_BSTR(PropValue) = SysAllocString(ItemPath.c_str());
            break;
        case kpidSize:
            V_VT(PropValue) = VT_UI8;
            PropValue->uhVal.QuadPart = Utils::GetSize(FilePath);
            break;
        }
        return S_OK;
    }
    STDMETHODIMP GetStream(UInt32 ItemIndex, ISequentialInStream** InStream)
    {
        const std::wstring& FilePath = m_FileList[ItemIndex].first;
        HRESULT hr = CInStream::Create(FilePath, IID_ISequentialInStream, (void**)InStream);
        return hr;
    }
protected:
    std::vector<std::pair<std::wstring, std::wstring>> m_FileList;
};

它目前专门用于 on-disk 文件,但可以修改以适应 in-memory 缓冲区。例如,它可以对 (in-memory) IInStream 个对象列表而不是文件路径列表进行操作。

原版 POST 03.19.2022

这是使用 pyzipper

完成您的用例的一种方法
import fs
import pyzipper

# create in-memory file system
mem_fs = fs.open_fs('mem://')
mem_fs.makedir('hidden_dir')

# generate data 
data = b'ab' * 10

secret_password = b'super secret password'

# Create encrypted password protected ZIP file in-memory
with pyzipper.AESZipFile(mem_fs.open('/hidden_dir/password_protected.zip', 'wb'),
                         'w',
                         compression=pyzipper.ZIP_LZMA,
                         encryption=pyzipper.WZ_AES) as zf:
    zf.setpassword(secret_password)
    zf.writestr('data.txt', data)


# Read encrypted password protected ZIP file from memory
with pyzipper.AESZipFile(mem_fs.open('/hidden_dir/password_protected.zip', 'rb')) as zf:
    zf.setpassword(secret_password)
    my_secrets = zf.read('data.txt')
    print(my_secrets)
    # output 
    b'abababababababababab'

2022 年 3 月 21 日更新

通读我们的评论,您继续对模块的加密组件提出担忧,例如 pyzipper,但不是 7Z LIB/SDK。这是一篇关于 7Z LIB/SDK 版本 19 密码学的学术论文

基于您的顾虑,您是否考虑过在将数据写入 zip 文件之前对内存中的数据进行加密?

下面是执行此操作并将加密数据写入内存中文件的示例:

import os
import fs
import base64

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

mem_fs = fs.open_fs('mem://')
mem_fs.makedir('hidden_dir')

password = b"password"
salt = os.urandom(16)
kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,
    salt=salt,
    iterations=390000,
)

key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)

data = b'ab' * 10

encrypted_message = f.encrypt(data)

with mem_fs.open('hidden_dir/encrypted.text', 'wb') as in_file_in_memory:
    in_file_in_memory.write(encrypted_message)
    in_file_in_memory.close()

with mem_fs.open('hidden_dir/encrypted.text', 'rb') as out_file_in_memory:
    raw_data = out_file_in_memory.read()
    decrypted_data = f.decrypt(raw_data)
    print(decrypted_data)
    # output
    b'abababababababababab'

之前在评论中我提到了密钥管理,这类似于为您的 zip 存档维护密码列表。

我不知道你的设置,但你可以提前预生成密钥并以安全的方式存储它们以供在你的代码中使用。

我的Mac上没有安装7z,所以我只能给你伪代码。以下示例未使用 7z。

import os
import fs
import base64
import pyzipper
from zipfile import ZipFile
from cryptography.fernet import Fernet

mem_fs = fs.open_fs('mem://')
mem_fs.makedir('hidden_dir')

# pregenerate key
f = Fernet(b'-6_WO-GLrlXexdSbon_fKJoVOVBh66LdYrEM0Kvcwf0=')

data = b'ab' * 10

encrypted_message = f.encrypt(data)

with mem_fs.open('hidden_dir/encrypted.text', 'wb') as in_file_in_memory:
    in_file_in_memory.write(encrypted_message)
    in_file_in_memory.close()

# This uses standard ZIP with no password, but the data
# is encrypted 
with mem_fs.open('hidden_dir/encrypted.text', 'rb') as out_file_in_memory:
    raw_data = out_file_in_memory.read()
    with ZipFile('archive.zip', mode='w') as zip_file:
        zip_file.writestr('file.txt', raw_data)

# This uses pyzipper to create a password word protected 
# encrypted file, which stores the encrypted.text.
# overkill, because the data is already encrypted prior
with mem_fs.open('hidden_dir/encrypted.text', 'rb') as out_file_in_memory:
    raw_data = out_file_in_memory.read()
    secret_password = b'super secret password'
    # Create encrypted password protected ZIP file in-memory
    with pyzipper.AESZipFile('password_protected.zip',
                             'w',
                             compression=pyzipper.ZIP_LZMA,
                             encryption=pyzipper.WZ_AES) as zf:
        zf.setpassword(secret_password)
        zf.writestr('data.txt', raw_data)

我仍在研究如何将此 encrypted.text 传送到子进程 7-zip。

我无法理解为什么您不需要临时 on-disk 文件,因为它会降低复杂性。

是的,我发现很少有只需要内置 python:

模块的解决方案
    • 您可以使用 subprocess 与 powershell 交互并使用 powershell 命令创建 zip 文件。您可以 运行 命令或将命令保存在 .ps1 文件中并执行它。 (此解决方案需要您安装 7zip 软件
def run(self, cmd):
    completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True)
    return completed
  • 电源shell代码为:
# Note this code is not written by me, link is provide to the actual owner
function Write-ZipUsing7Zip([string]$FilesToZip, [string]$ZipOutputFilePath, [string]$Password, [ValidateSet('7z','zip','gzip','bzip2','tar','iso','udf')][string]$CompressionType = 'zip', [switch]$HideWindow)
{
    # Look for the 7zip executable.
    $pathTo32Bit7Zip = "C:\Program Files (x86)-Zipz.exe"
    $pathTo64Bit7Zip = "C:\Program Files-Zipz.exe"
    $THIS_SCRIPTS_DIRECTORY = Split-Path $script:MyInvocation.MyCommand.Path
    $pathToStandAloneExe = Join-Path $THIS_SCRIPTS_DIRECTORY "7za.exe"
    if (Test-Path $pathTo64Bit7Zip) { $pathTo7ZipExe = $pathTo64Bit7Zip }
    elseif (Test-Path $pathTo32Bit7Zip) { $pathTo7ZipExe = $pathTo32Bit7Zip }
    elseif (Test-Path $pathToStandAloneExe) { $pathTo7ZipExe = $pathToStandAloneExe }
    else { throw "Could not find the 7-zip executable." }

    # Delete the destination zip file if it already exists (i.e. overwrite it).
    if (Test-Path $ZipOutputFilePath) { Remove-Item $ZipOutputFilePath -Force }

    $windowStyle = "Normal"
    if ($HideWindow) { $windowStyle = "Hidden" }

    # Create the arguments to use to zip up the files.
    # Command-line argument syntax can be found at: http://www.dotnetperls.com/7-zip-examples
    $arguments = "a -t$CompressionType ""$ZipOutputFilePath"" ""$FilesToZip"" -mx9"
    if (!([string]::IsNullOrEmpty($Password))) { $arguments += " -p$Password" }

    # Zip up the files.
    $p = Start-Process $pathTo7ZipExe -ArgumentList $arguments -Wait -PassThru -WindowStyle $windowStyle

    # If the files were not zipped successfully.
    if (!(($p.HasExited -eq $true) -and ($p.ExitCode -eq 0)))
    {
        throw "There was a problem creating the zip file '$ZipFilePath'."
    }
}
  1. 使用 powershell 依赖 7zip4PowerShell 然后使用 subprocess 与 shell 交互。 (Link提供)
  • 通过管理升级启动 PowerShell。
  • 通过输入下面的 cmdlet 安装 7-zip 模块。它确实查询 PS 库并使用 third-party 存储库下载依赖项。如果您同意安全注意事项,请批准安装以继续:

Install-Module -Name 7zip4PowerShell -Verbose

  • 将目录更改为要保存压缩文件的位置。
  • 通过输入以下 cmdlet,为压缩文件的加密创建安全字符串:

$SecureString = Read-Host -AsSecureString

  • 输入您希望在 PowerShell 中使用的密码。密码将被星号混淆。输入的纯文本将转换为 $SecuresString,您将在下一步中使用它。

  • 输入以下 cmdlet 以加密生成的压缩文件:

Compress-7zip -Path "\path ofiles" -ArchiveFileName "Filename.zip" -Format Zip -SecurePassword $SecureString

  • 命令完成处理后,生成的 ZIP 文件将保存到所选目录。

  • 您可以在 powershell 终端中遵循该过程,或者在使用 subprocess.

    安装依赖项后仅与终端交互

参考文献:

7z.exe 有 -si 标志,它允许它从标准输入读取文件数据。这样,即使没有额外的文件,您仍然可以从 subprocess 使用 7z 的命令行:

from subprocess import Popen, PIPE

# inputs
szip_exe = r"C:\Program Files-Zipz.exe"  # ... get from registry maybe
memfiles = {"data.dat" : b'ab' * 1000000000}
arch_filename = "myarchive.zip"
arch_password = "Swordfish"

for filename, data in memfiles.items():
    args = [szip_exe, "a", "-mem=AES256", "-y", "-p{}".format(arch_password),
        "-si{}".format(filename), output_filename]
    proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    proc.stdin.write(data)
    proc.stdin.close()    # causes 7z to terminate 
    # proc.stdin.flush()  # instead of close() when on Mac, see comments
    proc.communicate()    # wait till it actually has

write() 在我的机器上花费了 40 多秒,这是相当多的。不过,我不能说这是由于通过标准输入传输整个数据的效率低下造成的,还是压缩和加密一个 2GB 文件需要多长时间。 编辑:在我的机器上从 HDD 打包文件需要 47 秒,这代表后者。