如何使用PHP的openssl_decrypt功能按块解密大文件?



我正在开发一个文件存储并与 API 后端的 Laravel 共享 Web 应用程序,VueJS 作为前端应用程序。

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

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

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

我当前的文件控制器包含许多方法,包括"创建"和"下载"。

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

这是我的创建方法。它只是创建资源并使用".decrypted"前缀扩展名存储文件。(我放了很多空白,因为这种方法的逻辑与加密无关)

//AppHttpControllersFilesFileController.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'

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

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

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

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

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

这个问题没有一个简单的答案,因为Laravel的加密是如何设计的。

您可以重新实现化解安全性的 PHP 加密库使用的逻辑,用于加密/解密低内存设备上的大文件。但是,这是一个非常复杂的问题,您还必须模拟馈送到 HMAC 的 JSON 序列化。

您也可以从 Laravel 的内置加密库切换到 Defuse 的库来存储这些文件。它具有内置的FileAPI,可以执行您想要的操作。这可能是最省力的解决方案。

最新更新