Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Encryptable Laravel Package

sagalbot/encryptable

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require sagalbot/encryptable
    
  2. Ensure Laravel encryption is configured: Run php artisan key:generate if APP_KEY isn’t already set in .env. Verify config/app.php includes the key under 'key' => env('APP_KEY').

  3. Apply the trait to a model:

    use Sagalbot\Encryptable\Encryptable;
    
    class User extends Model
    {
        use Encryptable;
    
        protected $encryptable = ['ssn', 'credit_card'];
    }
    
  4. Test immediately:

    $user = new User;
    $user->ssn = '123-45-6789'; // Automatically encrypted on save
    $user->save();
    echo $user->ssn; // Automatically decrypted on access
    

First Use Case

Secure PII in a User Model:

class User extends Model
{
    use Encryptable;

    protected $encryptable = [
        'ssn',
        'driver_license_number',
        'medical_history'
    ];

    protected $fillable = ['name', 'email', 'ssn']; // ssn is encrypted in DB
}
  • Why? Protects sensitive data at rest without manual encryption/decryption in every query.

Implementation Patterns

Core Workflow

  1. Define Encryptable Fields:

    protected $encryptable = ['field1', 'field2'];
    
    • Only these fields are encrypted when saved to the database.
    • Decrypted automatically when accessed via $model->field1.
  2. Mass Assignment Safety:

    • Use $fillable or $guarded as usual. Encryption happens after validation.
    • Example:
      $user = User::create([
          'name' => 'John',
          'ssn'  => '123-45-6789' // Encrypted before DB insert
      ]);
      
  3. Querying Encrypted Fields:

    • Works with comparisons:
      $users = User::where('ssn', 'like', '123%')->get(); // Decrypts during query
      
    • Avoid raw SQL with encrypted fields (e.g., DB::select()). Use Eloquent.
  4. Serialization/JSON:

    • Encrypted fields are decrypted when converting to array/JSON:
      $user->toArray(); // ssn is decrypted
      
  5. API Responses:

    • Use hidden() or visible() to control exposure:
      protected $hidden = ['ssn']; // Exclude from API responses
      

Advanced Patterns

  1. Dynamic Encryption: Dynamically set $encryptable based on logic (e.g., tenant-specific fields):

    public function initializeEncryptable()
    {
        $this->encryptable = array_merge(
            $this->encryptable,
            ['tenant_specific_field']
        );
    }
    
  2. Custom Encryption Key: Override the default Laravel key per-model:

    use Sagalbot\Encryptable\Encryptable;
    
    class User extends Model
    {
        use Encryptable;
    
        protected $encryptableKey = 'custom-encryption-key';
    }
    
  3. Partial Updates: Use update() or fill():

    $user->update(['ssn' => '987-65-4321']); // Encrypts only the updated field
    
  4. Events: Listen to retrieved (after decryption) or storing (before encryption):

    protected static function bootEncryptable()
    {
        static::retrieved(function ($model) {
            // Log decrypted sensitive data
        });
    }
    
  5. Relationships: Encrypted fields work seamlessly with relationships:

    class Order extends Model
    {
        use Encryptable;
    
        protected $encryptable = ['billing_address'];
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Raw Queries Fail:

    • Issue: DB::select() or DB::table() bypasses Eloquent’s decryption.
    • Fix: Use Eloquent queries or manually decrypt:
      $rawData = DB::table('users')->select('ssn')->first();
      $decrypted = decrypt($rawData->ssn);
      
  2. Caching Issues:

    • Issue: Cached models may retain encrypted values if not re-fetched.
    • Fix: Clear cache or use fresh():
      $user = User::find(1)->fresh(); // Forces re-fetch and decryption
      
  3. Migration Conflicts:

    • Issue: Adding encrypted fields to existing tables may require downtime.
    • Fix: Use nullable and backfill:
      Schema::table('users', function (Blueprint $table) {
          $table->string('ssn')->nullable()->after('email');
      });
      User::all()->each->fill(['ssn' => null])->save();
      
  4. Performance Overhead:

    • Issue: Every access to an encrypted field triggers decryption.
    • Fix: Cache decrypted values in memory:
      public function getSsnAttribute($value)
      {
          return $value ?? $this->attributes['ssn'];
      }
      
  5. Key Rotation:

    • Issue: Changing APP_KEY won’t decrypt existing data.
    • Fix: Use a key derivation strategy (e.g., config(['app.cipher' => 'AES-256-CBC'])).

Debugging Tips

  1. Verify Encryption: Check raw DB values:

    $user->fresh()->getRawOriginal('ssn'); // Returns encrypted string
    
  2. Log Decryption Errors: Wrap access in a try-catch:

    try {
        $ssn = $user->ssn;
    } catch (\Exception $e) {
        Log::error("Decryption failed for user {$user->id}: " . $e->getMessage());
    }
    
  3. Test with encrypt()/decrypt(): Manually test encryption:

    $encrypted = encrypt($user->ssn);
    $decrypted = decrypt($encrypted);
    

Extension Points

  1. Custom Encryption Logic: Override the encryptAttribute() method:

    protected function encryptAttribute($value, $key)
    {
        return bcrypt($value); // Use bcrypt instead of Laravel's encrypt()
    }
    
  2. Conditional Encryption: Dynamically exclude fields from encryption:

    public function getEncryptable()
    {
        if ($this->isAdmin()) {
            return array_diff($this->encryptable, ['admin_notes']);
        }
        return $this->encryptable;
    }
    
  3. Add to Existing Models: Use use Encryptable; in existing models and add $encryptable incrementally.

  4. Integration with Policies: Restrict access to decrypted fields in policies:

    public function viewAny(User $user)
    {
        return $user->can('view_ssn');
    }
    
  5. Database-Level Encryption: Combine with Laravel’s encrypt column type for extra security:

    Schema::table('users', function (Blueprint $table) {
        $table->string('ssn')->encrypt()->nullable();
    });
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle
dmstr/api-platform-utils-bundle
dmstr/api-configuration-bundle
chrisdev/ux-components
baks-dev/finances
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle