ercsctt / laravel-file-encryption
Secure file encryption and decryption for Laravel applications
Installs: 376
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/ercsctt/laravel-file-encryption
Requires
- php: ^8.2
- ext-openssl: *
- illuminate/console: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/filesystem: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- symfony/finder: ^7.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/pint: ^1.27
- orchestra/testbench: ^9.0|^10.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5|^11.0
Suggests
- laravel/prompts: Required for interactive CLI commands (^0.1.0|^0.2.0|^0.3.0)
README
A robust file encryption package for Laravel using AES-256-GCM authenticated encryption. Designed for secure, efficient encryption and decryption of files of any size with streaming support for memory-efficient processing of large files.
Features
- AES-256-GCM encryption - Industry-standard authenticated encryption
- Streaming support - Process files of any size with minimal memory usage
- Key rotation - Seamlessly rotate encryption keys without downtime
- Artisan commands - Encrypt and decrypt files from the command line
- Facade and helper - Convenient ways to use the package
Requirements
- PHP 8.2 or higher
- Laravel 11.0 or higher
- OpenSSL PHP extension
Installation
Install the package via Composer:
composer require ercsctt/laravel-file-encryption
The package will automatically register its service provider.
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=file-encryption-config
This will create a config/file-encryption.php file.
Environment Variables
Add the following to your .env file:
FILE_ENCRYPTION_KEY=base64:your-base64-encoded-32-byte-key
Generate a key using:
php artisan tinker --execute="echo 'base64:'.base64_encode(random_bytes(32));"
Optional configuration:
# Previous keys for key rotation (comma-separated, base64-encoded) FILE_ENCRYPTION_PREVIOUS_KEYS=base64:old-key-1,base64:old-key-2 # Chunk size for streaming (default: 65536 bytes) FILE_ENCRYPTION_CHUNK_SIZE=65536
Usage
Using the Facade
use Ercsctt\FileEncryption\Facades\FileEncrypter; // Encrypt a file FileEncrypter::encryptFile('/path/to/source.txt', '/path/to/encrypted.enc'); // Decrypt a file FileEncrypter::decryptFile('/path/to/encrypted.enc', '/path/to/decrypted.txt'); // Auto-generate destination path (appends .enc for encryption) FileEncrypter::encryptFile('/path/to/source.txt'); // Creates source.txt.enc // Auto-generate destination path (removes .enc for decryption) FileEncrypter::decryptFile('/path/to/source.txt.enc'); // Creates source.txt // Get decrypted contents as string $contents = FileEncrypter::decryptedContents('/path/to/encrypted.enc'); // Check if a file is encrypted if (FileEncrypter::isEncrypted('/path/to/file.enc')) { // File has encryption magic bytes }
Using the Helper Function
// Decrypt and get file contents $contents = decrypt_file('/path/to/encrypted.enc');
Direct Instantiation
use Ercsctt\FileEncryption\FileEncrypter; // Key must be exactly 32 bytes for AES-256 $key = random_bytes(32); $encrypter = new FileEncrypter($key); // With custom chunk size (minimum 1024 bytes) $encrypter = new FileEncrypter($key, 131072); // 128KB chunks // Encrypt and decrypt $encrypter->encryptFile('/path/to/source.txt', '/path/to/encrypted.enc'); $encrypter->decryptFile('/path/to/encrypted.enc', '/path/to/decrypted.txt');
Artisan Commands
Encrypt Command
php artisan file:encrypt {path} [options]
Options:
| Option | Description |
|---|---|
--key= |
Encryption key (base64-encoded). Uses configured key if not provided |
-R, --recursive |
Recursively encrypt all files in a directory |
--force |
Skip confirmation prompts |
--chunk-size=65536 |
Chunk size in bytes for streaming |
--prune |
Delete original files after successful encryption |
Examples:
# Encrypt a single file (creates document.pdf.enc) php artisan file:encrypt storage/app/document.pdf # Encrypt with a specific key php artisan file:encrypt storage/app/secret.txt --key="base64:abc123..." # Encrypt an entire directory recursively php artisan file:encrypt storage/app/private --recursive # Encrypt directory and delete originals after encryption php artisan file:encrypt storage/app/sensitive --recursive --prune # Encrypt without confirmation prompts (useful for scripts/CI) php artisan file:encrypt storage/app/data --recursive --force # Encrypt with larger chunks for better performance on big files php artisan file:encrypt storage/app/large-video.mp4 --chunk-size=1048576 # Combine options: encrypt directory, delete originals, no prompts php artisan file:encrypt storage/app/backup --recursive --prune --force
Decrypt Command
php artisan file:decrypt {path?} [options]
Options:
| Option | Description |
|---|---|
--key= |
Decryption key (base64-encoded) |
-R, --recursive |
Recursively decrypt all .enc files in a directory |
--force |
Skip confirmation prompts |
--keep |
Keep encrypted files after successful decryption |
--output= |
Custom output path for decrypted file |
--scan |
Scan entire project for encrypted (.enc) files |
Examples:
# Decrypt a single file (creates document.pdf from document.pdf.enc) php artisan file:decrypt storage/app/document.pdf.enc # Decrypt with a specific key php artisan file:decrypt storage/app/secret.txt.enc --key="base64:abc123..." # Decrypt to a custom output path php artisan file:decrypt storage/app/config.json.enc --output=config/decrypted.json # Decrypt an entire directory recursively php artisan file:decrypt storage/app/private --recursive # Decrypt but keep the encrypted files (useful for verification) php artisan file:decrypt storage/app/backup --recursive --keep # Decrypt without confirmation prompts php artisan file:decrypt storage/app/data.enc --force # Scan entire project for encrypted files and decrypt them php artisan file:decrypt --scan # Scan and decrypt all found files, keeping encrypted versions php artisan file:decrypt --scan --keep # Scan and decrypt without prompts (CI/scripts) php artisan file:decrypt --scan --force # Combine options: decrypt directory recursively, no prompts, keep originals php artisan file:decrypt storage/app/encrypted --recursive --force --keep
Common Workflows
Encrypt sensitive uploads for storage:
# Encrypt all files in uploads directory, remove originals
php artisan file:encrypt storage/app/uploads --recursive --prune --force
Decrypt files for processing, then re-encrypt:
# Decrypt keeping encrypted versions php artisan file:decrypt storage/app/data --recursive --keep --force # ... process the files ... # Re-encrypt (original .enc files still exist as backup) php artisan file:encrypt storage/app/data --recursive --prune --force
Find and decrypt all encrypted files in a project:
# Scan finds all .enc files in the project and decrypts them
php artisan file:decrypt --scan --force
Migrate to a new encryption key:
# 1. Set new key in .env, add old key to FILE_ENCRYPTION_PREVIOUS_KEYS # 2. Decrypt all files (will use old key automatically) php artisan file:decrypt storage/app/encrypted --recursive --force # 3. Re-encrypt with new key php artisan file:encrypt storage/app/encrypted --recursive --prune --force
Streaming Large Files
For large files, use decryptedStream() for memory-efficient processing:
use Ercsctt\FileEncryption\Facades\FileEncrypter; // Stream decrypted content through a callback FileEncrypter::decryptedStream('/path/to/large-file.enc', function ($chunk) { echo $chunk; // Process each chunk }); // Stream to a file handle $output = fopen('/path/to/output.txt', 'wb'); FileEncrypter::decryptedStream('/path/to/encrypted.enc', function ($chunk) use ($output) { fwrite($output, $chunk); }); fclose($output); // Stream for hashing $context = hash_init('sha256'); FileEncrypter::decryptedStream('/path/to/file.enc', function ($chunk) use ($context) { hash_update($context, $chunk); }); $hash = hash_final($context);
Progress Callbacks
Monitor progress when processing large files:
use Ercsctt\FileEncryption\Facades\FileEncrypter; // Progress callback receives current chunk and total chunks FileEncrypter::encryptFile( '/path/to/large-file.zip', '/path/to/encrypted.enc', function ($currentChunk, $totalChunks) { $percent = round(($currentChunk / $totalChunks) * 100); echo "Encrypting: {$percent}%\n"; } ); FileEncrypter::decryptFile( '/path/to/encrypted.enc', '/path/to/decrypted.zip', function ($currentChunk, $totalChunks) { echo "Decrypting chunk {$currentChunk} of {$totalChunks}\n"; } );
Key Rotation
When you need to rotate your encryption key:
-
Generate a new key:
php artisan tinker --execute="echo 'base64:'.base64_encode(random_bytes(32));" -
Update your
.envfile:FILE_ENCRYPTION_KEY=base64:your-new-key FILE_ENCRYPTION_PREVIOUS_KEYS=base64:your-old-key
-
The package will automatically try previous keys when decrypting files encrypted with old keys.
-
Programmatically configure previous keys:
use Ercsctt\FileEncryption\FileEncrypter; $currentKey = random_bytes(32); $oldKey = '...'; // Your previous 32-byte key $encrypter = (new FileEncrypter($currentKey))->previousKeys([$oldKey]); // Decryption will try current key first, then fall back to old keys $encrypter->decryptFile('/path/to/old-encrypted.enc', '/path/to/decrypted.txt'); // Get all configured keys $allKeys = $encrypter->getAllKeys(); // Returns [currentKey, oldKey]
Security Features
- AES-256-GCM - Provides both confidentiality and authenticity
- Unique nonce per chunk - Each chunk uses a unique 12-byte nonce derived from base nonce XORed with chunk index
- Authentication tag - 16-byte GCM authentication tag per chunk prevents tampering
- Chunk index in AAD - Additional authenticated data includes chunk index to prevent reordering attacks
- Header HMAC - 12-byte truncated HMAC-SHA256 protects header integrity
- Constant-time comparisons - Uses
hash_equals()to prevent timing attacks - Secure key handling - Keys marked with
#[\SensitiveParameter]attribute
File Format
Encrypted files use the following binary format:
HEADER (32 bytes):
[4 bytes: Magic "LENC"]
[1 byte: Version]
[1 byte: Cipher ID (1 = AES-256-GCM)]
[2 bytes: Reserved]
[4 bytes: Chunk size (big-endian)]
[8 bytes: Original file size (big-endian)]
[12 bytes: Header HMAC]
CHUNKS (repeated):
[12 bytes: Nonce]
[N bytes: Ciphertext]
[16 bytes: GCM authentication tag]
Error Handling
The package throws specific exceptions for different error conditions:
use Ercsctt\FileEncryption\Facades\FileEncrypter; use Illuminate\Contracts\Encryption\EncryptException; use Illuminate\Contracts\Encryption\DecryptException; try { FileEncrypter::encryptFile('/path/to/source.txt', '/path/to/encrypted.enc'); } catch (EncryptException $e) { // Handle encryption failure (file not found, not readable, etc.) } try { FileEncrypter::decryptFile('/path/to/encrypted.enc', '/path/to/decrypted.txt'); } catch (DecryptException $e) { // Handle decryption failure (wrong key, corrupted file, invalid format, etc.) }
Key Validation
use Ercsctt\FileEncryption\FileEncrypter; // Keys must be exactly 32 bytes try { new FileEncrypter(str_repeat('a', 16)); // Too short! } catch (RuntimeException $e) { echo $e->getMessage(); // "File encryption requires a 32-byte key for AES-256-GCM." } // Chunk size must be at least 1024 bytes try { new FileEncrypter(random_bytes(32), 512); // Too small! } catch (RuntimeException $e) { echo $e->getMessage(); // "Chunk size must be at least 1024 bytes." }
Testing
Run the test suite:
composer test
Or with PHPUnit directly:
./vendor/bin/phpunit
API Reference
FileEncrypter Methods
| Method | Signature | Description |
|---|---|---|
encryptFile |
(string $source, ?string $dest = null, ?callable $progress = null): void |
Encrypt a file. Destination defaults to $source.enc |
decryptFile |
(string $source, ?string $dest = null, ?callable $progress = null): void |
Decrypt a file. Destination defaults to source without .enc |
decryptedContents |
(string $path): string |
Return entire decrypted file contents as string |
decryptedStream |
(string $path, callable $callback): void |
Stream decrypted chunks through callback |
isEncrypted |
(string $path): bool |
Check if file has encryption magic bytes |
previousKeys |
(array $keys): static |
Set previous keys for key rotation (fluent) |
getKey |
(): string |
Get primary encryption key |
getChunkSize |
(): int |
Get configured chunk size |
getAllKeys |
(): array |
Get all keys (primary + previous) |
License
This package is open-sourced software licensed under the MIT license.