如何使用 PHP 的 openssl_decrypt 函数分块解密大文件?

Tok*_*ger 5 php encryption bash openssl laravel

我正在开发一个文件存储和共享 Web 应用程序,其中 API 后端使用 Laravel,前端应用程序使用 VueJS。

我必须使用 CRON 例程每分钟启动一个 BASH 脚本来加密上传的文件,但我需要使用 Laravel/PHP 将它们从控制器解密到 StreamDownload 响应中(我需要逐块解密文件,因为大文件我们的服务器使用了太多内存)。

我们决定从外部例程对文件进行加密,以防止用户等待加密,有时在文件上传后等待几分钟。

我在 Debian 4.9 服务器上使用 Laravel 5.7 和 PHP 7.3,但我在 Windows 10 上的本地计算机上使用 WAMP 和 PHP 7.3 进行测试。我正在使用 Git Bash 来运行和测试我的 shell 命令。

我当前的 FileController 包含许多方法,包括“创建”和“下载”。

“创建”只是将文件存储到 Laravel 的存储目录中,并在数据库中创建一个新的“文件”资源,而“下载”则尝试检索加密文件,解密并将其发送到客户端。

这是我的创建方法。它只是创建资源并存储带有“.decrypted”前缀扩展名的文件。(我留了很多空白,因为这个方法的逻辑不是关于加密的)

//App\Http\Controllers\Files\FileController.php

public function create(Request $request)
    {
       ...

       $file = File::create([
            'name' => $name,
            'uuid' => $uuid,
            ...
        ]);

        ...

        $output->move($userPath, $uuid.'.decrypted');

        ...

        return new FileResource($file);
    }
Run Code Online (Sandbox Code Playgroud)

然后,这是我编写的用于每分钟加密后缀文件的 BASH 脚本(我用一些“###”替换了敏感信息,不用担心。)

#encrypt.sh

#!/bin/bash

set -euo pipefail

# PARAMETERS
APP_KEY='######'
FILES_PATH='/###/.../storage/app/files/'
FILES_LIST=$(find "$FILES_PATH" -type f -name '*.decrypted' )
KEY=$(echo "$APP_KEY" | base64 -d -i | xxd -p -c 64)

while read -r file; do
  INPUT_PATH=$file
  OUTPUT_PATH=${file%.decrypted}
  IV=$(openssl rand -hex 16)
  openssl AES-256-CBC -K $KEY -iv $IV -in $INPUT_PATH -out $OUTPUT_PATH
done < <(echo "$FILES_LIST")

echo 'Done'
Run Code Online (Sandbox Code Playgroud)

据我所知,这段代码运行良好。

然后,这是我的最后一段代码:Download 方法。

//App\Http\Controllers\Files\FileController.php

public function download(File $file, Request $request)
    {
        ...

        $dlFile = Storage::disk('files')->path($file->path);
        
        ...

        return response()->streamDownload(
            /* Note: $dlFile is the path, $file is the Laravel resource */
            function () use ($dlFile, $log, $file) {
                $cipher = config('app.cipher'); // AES-256-CBC
                /* Note: the app key is stored in format "base64:#####...", this is why there's a substr() inside a base64() */
                $key = base64_decode(substr(config('app.key'), 7));
                if ($fpIn = fopen($dlFile, 'rb')) {
                    $iv = fread($fpIn, 16);
                    while (!feof($fpIn)) {
                        $ciphertext = fread($fpIn, $this->memoryLimit());
                        $plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
                        print($plaintext);
                    }
                    fclose($fpIn);
                }
            },
            $fileName,
            [
                'Content-Type' => $file->mime,
                'Content-Length' => $file->size
            ],
            'inline'
        );
    }
Run Code Online (Sandbox Code Playgroud)

我从这个页面得到了最后一段代码

我猜我的 PHP 脚本制作得不好,因为解密的输出是错误的。有人有想法可以帮助我吗?

Sco*_*ski 0

由于 Laravel 加密的设计方式,这个问题没有一个简单的答案。

可以重新实现Defuse Security 的 PHP 加密库用于加密/解密低内存设备上的大文件的逻辑。然而,这是一个需要解决的非常复杂的问题,您还必须模拟输入到 HMAC 中的 JSON 序列化。

对于这些文件,您还可以从 Laravel 的内置加密库切换到 Defuse 的库。它具有内置FileAPI,可以满足您的需求。这可能是最省力的解决方案。