paperscissorsandglue/laravel-encryption-at-rest
A Laravel package for encrypting sensitive data at rest and automatically decrypting it when in use. Useful for regulatory compliance requirements like GDPR, HIPAA, and other data protection standards. Compatible with Laravel 10, 11, and 12.
You can install the package via composer:
composer require paperscissorsandglue/laravel-encryption-at-rest
After installation, publish the configuration file:
php artisan vendor:publish --tag=encryption-at-rest-config
In your .env file, you can optionally set a custom encryption key:
ENCRYPTION_AT_REST_KEY=your-secure-key-here
If not set, the package will use your application key for encryption.
Add the Encryptable trait to your model and define which attributes should be encrypted:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Paperscissorsandglue\EncryptionAtRest\Encryptable;
class User extends Model
{
use Encryptable;
/**
* The attributes that should be encrypted.
*
* @var array
*/
protected $encryptable = [
'email',
'phone',
'address',
];
}
That's it! The specified attributes will be automatically encrypted when saved to the database and decrypted when retrieved.
For JSON columns where you only want to encrypt certain fields within the JSON structure, use the EncryptableJson trait:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Paperscissorsandglue\EncryptionAtRest\EncryptableJson;
class UserProfile extends Model
{
use EncryptableJson;
/**
* The attributes that should have encrypted JSON fields.
*
* @var array
*/
protected $encryptableJson = [
'preferences' => ['notification_email', 'backup_phone'],
'settings' => ['api_key', 'personal_token'],
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'preferences' => 'json',
'settings' => 'json',
];
}
With this setup, only the specified fields within your JSON structure will be encrypted while the rest of the JSON remains searchable.
This package uses smart dynamic getters and setters to handle all encryption and decryption transparently. All encrypted fields, including email, are automatically:
This universal approach means you don't need to write any special code to handle encryption - it just works:
// Regular attribute access
$email = $user->email; // Automatically decrypted
// Assignment
$user->email = 'new@example.com'; // Will be encrypted on save
// Laravel notifications work seamlessly
$user->notify(new WelcomeNotification());
// Form requests and API responses work correctly
return response()->json(['user' => $user]);
// Eloquent serialization works properly
$array = $user->toArray();
For JSON attributes with encrypted fields, the package also ensures seamless operation:
// If 'api_key' is encrypted within the preferences JSON
$apiKey = $user->preferences['api_key']; // Automatically decrypted
// Set values that will be encrypted automatically
$user->preferences = [
'api_key' => 'new-secret-key',
'public_setting' => 'not-encrypted'
];
This package provides special support for encrypting the email field while maintaining the ability to authenticate users by email. This is achieved by adding a searchable hash of the email (email_index) that enables efficient lookup.
email_index column to your users table:php artisan vendor:publish --tag=encryption-at-rest-migrations
php artisan migrate
HasEncryptedEmail trait to your User model:<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Paperscissorsandglue\EncryptionAtRest\HasEncryptedEmail;
class User extends Authenticatable
{
use HasApiTokens, HasEncryptedEmail, Notifiable;
// ... existing model code
}
config/auth.php to use the encrypted email user provider:'providers' => [
'users' => [
'driver' => 'encrypted-email',
'model' => App\Models\User::class,
],
],
# Run in dry-run mode first to see what would be changed
php artisan encryption:encrypt-emails "App\Models\User" --dry-run
# When ready, run the actual encryption (use --chunk=XX to set batch size)
php artisan encryption:encrypt-emails "App\Models\User"
Or if you prefer, create a migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use App\Models\User;
return new class extends Migration
{
public function up(): void
{
// Rehash all existing user emails
User::all()->each(function ($user) {
$user->handleEmailEncryption();
$user->save();
});
}
};
With the HasEncryptedEmail trait, you can still find users by their email:
// Find a user by email
$user = User::findByEmail('user@example.com');
// Or use the scope
$user = User::whereEmail('user@example.com')->first();
When a user is created or updated, the email is:
email columnemail_index column for searchingWhen a user is retrieved:
email_index column for lookupsThe authentication provider is modified to:
You can also use the EncryptionService directly for custom encryption needs:
use Paperscissorsandglue\EncryptionAtRest\EncryptionService;
public function __construct(EncryptionService $encryptionService)
{
$this->encryptionService = $encryptionService;
}
public function storeData($data)
{
$encryptedData = $this->encryptionService->encrypt($data);
// Store $encryptedData...
}
public function retrieveData($encryptedData)
{
$decryptedData = $this->encryptionService->decrypt($encryptedData);
// Use $decryptedData...
}
You can use the provided facade for quick access to encryption functionality:
use Paperscissorsandglue\EncryptionAtRest\Facades\EncryptionAtRest;
$encrypted = EncryptionAtRest::encrypt('sensitive data');
$decrypted = EncryptionAtRest::decrypt($encrypted);
This package includes several command-line tools to help you manage encrypted data.
To encrypt data in an existing database table for a model that uses our traits:
php artisan encryption:encrypt-model "App\Models\User"
Options:
--chunk=100 - Process records in chunks (default: 100)--dry-run - Test the process without making changes--backup=true - Create a database backup before processing (default: true)--filter="id > 1000" - Only process records matching SQL where clauseFor models using the HasEncryptedEmail trait, you can use a dedicated command to process emails:
php artisan encryption:encrypt-emails "App\Models\User"
Options:
--chunk=100 - Process records in chunks--dry-run - Test the process without making changesIf you need to decrypt data (for example, when migrating away from encryption):
php artisan encryption:decrypt-model "App\Models\User"
Options:
--chunk=100 - Process records in chunks (default: 100)--dry-run - Test the process without making changes--backup=true - Create a database backup before processing (default: true)--filter="id > 1000" - Only process records matching SQL where clause⚠️ Warning: Decryption permanently removes the encryption protection from your data. Only use this command when absolutely necessary and after creating a backup.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.
How can I help you explore Laravel packages today?