Laravel - 如何加密和解密大文件块?

问题描述 投票:0回答:1

我最初直接加密文件而不使用块,并在尝试加密大文件时开始遇到错误:

允许的内存大小 134217728 字节已耗尽

经过一些研究,我发现我尝试通过执行以下操作直接加密犯了一个很大的错误:

Storage::put($filePath, $encrypted->encrypt(file_get_contents($file)));

这是一个坏主意,因为它会将整个文件以及加密版本加载到内存中。对于小文件,这工作正常,但对于大文件则不行,因为它会超过 PHP

memory_limit

为了解决这个问题,我决定分块实现加密和解密。 我想使用 Laravel 提供的 Encrypter 类,但遇到了一些问题。这是我尝试过的:

注意:以下路线用于测试目的。

加密

use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;

Route::post('/upload', function (Request $request) {
    $file = $request->file('file');

    if ($file) {
        $key = Config::get('app.file_key'); // Base64 encoded key stored in .env
        $key = str_replace('base64:', '', $key);
        $key = base64_decode($key);
        $encrypted = new Encrypter($key, Config::get('app.cipher'));

        $path = 'my-file.enc';
        $tempFilePath = storage_path('app/public/temp');
        $chunkSize = 1024 * 1024; // 1 MB

        $fpSource = fopen($file->path(), 'rb');
        $fpDest = fopen($tempFilePath, 'wb');

        $firstChunkEnc = null;
        while (!feof($fpSource)) {
            $plaintext = fread($fpSource, $chunkSize);
            $encryptedChunk = $encrypted->encrypt($plaintext);
            $firstChunkEnc = $firstChunkEnc ?? $encryptedChunk;
            fwrite($fpDest, $encryptedChunk);
        }

        fclose($fpSource);
        fclose($fpDest);

        if ($hasUploaded) {
            return response()->json(['success' => true, 'message' => 'File uploaded and encrypted successfully']);
        }
    }

    return response()->json(['success' => false, 'message' => 'File not uploaded']);
})->name('upload');

解密:

Route::get('/decrypt/{file_name}', function ($file_name) {
    $key = Config::get('app.file_key');
    $key = str_replace('base64:', '', $key);
    $key = base64_decode($key);
    $encrypted = new Encrypter($key, Config::get('app.cipher'));

    $sourceFilePath = storage_path("app/public/$file_name");
    $destinationFilePath = storage_path("app/public/decrypted-$file_name");

    $chunkSize = 1024 * 1024; // 1 MB

    $fpEncrypted = fopen($sourceFilePath, 'rb');
    $fpDest = fopen($destinationFilePath, 'wb');

    while (!feof($fpEncrypted)) {
        $encryptedChunk = fread($fpEncrypted, $chunkSize);
        $decryptedChunk = $encrypted->decrypt($encryptedChunk);
        fwrite($fpDest, $decryptedChunk);
    }

    fclose($fpEncrypted);
    fclose($fpDest);

    return response()->download($destinationFilePath, 'decrypted-' . $file_name);
})->name('decrypt');

错误

我在尝试解密块时遇到以下错误:

Illuminate\Contracts\Encryption\DecryptException:有效负载无效。

问题

如何使用 Laravel 的 Encrypter 类正确加密和解密大文件?

php laravel encryption php-openssl laravel-encryption
1个回答
0
投票

您的代码的主要问题是您从

Illuminate\Encryption\Encrypter::encrypt()
得到的不仅仅是加密文本。

你得到的更像是这样的:

base64_encode(
   json_encode([
       'iv' => '...', // initialization vector used for encryption
       'value' => '...', // encrypted text
       'mac' => '...', // HMAC signature
       'tag' => '...', // Tag returned by OpenSSL for some ciphers

   ])
);

因此加密大小为 1MB 的块将导致超过 1MB 的输出。 但正如 Maarten Bodewes 在评论中指出的那样,在尝试解密时您仅读取 1MB 的块。因此,您没有读取整个加密块,这就是

The payload is invalid
错误的原因。

处理文件加密的最佳方法是使用一些已经支持文件加密的库。例如

defuse/php-encryption

如果你不能使用其他库,最好直接使用

openssl_encrypt()
以避免向每个块添加 IV 和签名。如果您这样做,您可能想看看 Laravel 中的加密是如何实现的,以获取灵感。或者你可以看看它在其他库中是如何实现的。 如果您坚持使用

Illuminate\Encryption\Encrypter

,那么按照

Maarten Bodewes
的建议使用一些分隔符可能是一个不错的选择。例如,将每个加密块写入单独的行中。解密时,您将按行读取加密文件,而不是固定的块大小。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.