我最初直接加密文件而不使用块,并在尝试加密大文件时开始遇到错误:
允许的内存大小 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 类正确加密和解密大文件?
您的代码的主要问题是您从
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的建议使用一些分隔符可能是一个不错的选择。例如,将每个加密块写入单独的行中。解密时,您将按行读取加密文件,而不是固定的块大小。