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

时间:2019-06-20 17:01:00

标签: php laravel bash encryption openssl

我正在使用API​​后端上的Laravel和VueJS作为前端应用程序来开发文件存储和共享Web应用程序。 我必须使用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的存储目录中,并在数据库中创建一个新的“文件”资源,而“下载”则尝试取回加密的文件,然后解密并将其发送给客户端。

这是我的Create方法。它只是创建资源并以“ .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);
    }

然后,这是我编写的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'

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

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

//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'
        );
    }

我从this page获得了最后一段代码。

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

1 个答案:

答案 0 :(得分:0)

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

可以重新实现Defuse Security's PHP Encryption library用于在低内存设备上加密/解密大文件的逻辑。但是,这是一个非常复杂的问题,必须模拟提供给HMAC的JSON序列化。

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