Installation Add the package via Composer:
composer require gregoryduckworth/encryptable:^1.0
No additional configuration is required.
First Use Case Apply the trait to an Eloquent model and define which attributes to encrypt:
use GregoryDuckworth\Encryptable\EncryptableTrait;
class User extends Model
{
use EncryptableTrait;
protected $encryptable = ['ssn', 'credit_card'];
}
Now, any interaction with ssn or credit_card (e.g., User::create(), user->ssn = '123') will automatically encrypt the data before saving and decrypt it upon retrieval.
Where to Look First
$encryptable array to confirm which fields are encrypted.dd($user->ssn) to confirm decryption works during retrieval.Basic Encryption/Decryption
// Automatically encrypts on save, decrypts on retrieval
$user = User::create(['ssn' => '123-45-6789']);
echo $user->ssn; // Decrypted value: '123-45-6789'
Mass Assignment
// Encrypts all encryptable fields in bulk
User::create([
'ssn' => '123-45-6789',
'credit_card' => '4111-1111-1111-1111',
'name' => 'John Doe' // Not encrypted (not in $encryptable)
]);
Manual Encryption/Decryption Use accessors/mutators for custom logic:
public function getSsnAttribute($value)
{
return $this->encryptableDecrypt($value);
}
public function setSsnAttribute($value)
{
$this->attributes['ssn'] = $this->encryptableEncrypt($value);
}
Querying Encrypted Fields
Avoid querying encrypted fields directly. Instead, use whereRaw with decrypted values:
// ❌ Avoid (encrypts the query value)
User::where('ssn', '123-45-6789')->get();
// ✅ Use raw SQL for equality checks
User::whereRaw("ssn = ?", [$this->encryptableEncrypt('123-45-6789')])->get();
API Responses Ensure encrypted fields are decrypted in API responses by default (Laravel’s JSON serialization handles this automatically).
config(['encryptable.key' => 'test-key']) for consistent test data.public function authorize(User $user)
{
return $user->ssn === $this->encryptableEncrypt('123-45-6789');
}
public function rules()
{
return [
'ssn' => ['required', function ($attribute, $value, $fail) {
$decrypted = $this->user()->encryptableDecrypt($value);
if (!preg_match('/^\d{3}-\d{2}-\d{4}$/', $decrypted)) {
$fail('Invalid SSN format.');
}
}]
];
}
Performance Overhead: Encryption/decryption adds latency. Avoid encrypting frequently queried or large fields.
Query Limitations:
LIKE on Encrypted Fields: Encrypted values cannot be searched with LIKE or ILIKE.
ORDER BY: Sorting by encrypted fields is unsupported.
Key Management:
The package uses Laravel’s default encryption key (config('app.key')). If this changes, encrypted data becomes unreadable.
Serialization Issues:
Encrypted fields may cause problems with Laravel’s serialization (e.g., replicate(), fresh()).
protected $encryptable = ['ssn'];
protected $dontReplicate = ['ssn'];
Mass Assignment Risks: Encrypted fields are still vulnerable to mass assignment attacks if not protected.
$guarded or $fillable to restrict which fields can be mass-assigned.Database Indexes: Encrypted fields cannot be indexed effectively. Avoid indexing them.
ssn_hash).Encryption/Decryption Failures:
config('app.key') matches expectations.$encryptable.$encrypted = $user->encryptableEncrypt('test');
$decrypted = $user->encryptableDecrypt($encrypted);
dd($decrypted === 'test'); // Should be true
Silent Failures: The trait may silently fail if encryption keys are mismatched. Log decryption attempts:
protected function encryptableDecrypt($value)
{
try {
return parent::encryptableDecrypt($value);
} catch (\Exception $e) {
\Log::error("Decryption failed for {$this->getKey()}: " . $e->getMessage());
return null;
}
}
Database Corruption: If the database contains corrupted encrypted data (e.g., due to key changes), the trait will throw exceptions.
Configuration: Customize the encryption algorithm or key via config:
// config/encryptable.php
return [
'key' => env('ENCRYPTABLE_KEY', config('app.key')),
'algorithm' => 'AES-256-CBC',
];
Partial Updates:
Use update() carefully—it may re-encrypt all encryptable fields:
// Re-encrypts ALL encryptable fields, even unchanged ones
$user->update(['ssn' => 'new-value']);
// Better: Update only the encrypted field
$user->ssn = 'new-value';
$user->save();
Testing: Use a dedicated test key to avoid polluting production data:
// In tests
config(['encryptable.key' => 'test-key-for-encryptable']);
Migration Safety: If adding encryption to an existing table, ensure backward compatibility:
ssn_encrypted) and migrate data gradually.Audit Logs:
Track changes to encrypted fields by overriding saving():
protected static function bootEncryptable()
{
static::saving(function ($model) {
foreach ($model->encryptable as $field) {
if ($model->wasChanged($field)) {
\Log::audit("Encrypted field {$field} updated for ID {$model->id}");
}
}
});
}
Fallback for Missing Key: Handle cases where the encryption key might be missing:
protected function encryptableEncrypt($value)
{
if (empty(config('encryptable.key'))) {
return $value; // Fallback to plaintext (or throw an exception)
}
return parent::encryptableEncrypt($value);
}
How can I help you explore Laravel packages today?