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

Encrypted Fields Bundle Laravel Package

dwgebler/encrypted-fields-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the Package Add the Flex recipe endpoint to composer.json (Symfony) and run:

    composer require dwgebler/encrypted-fields-bundle
    

    For Laravel, ensure Doctrine ORM is installed first (e.g., laravel-doctrine/orm).

  2. Generate Master Key Create a 32-byte hex key for AES-256-GCM:

    php -r "file_put_contents('.env.local', 'ENCRYPTED_FIELDS_KEY='.bin2hex(random_bytes(32)).PHP_EOL);"
    

    Store this securely (e.g., .env.local or a secrets manager).

  3. Configure the Bundle Add to config/packages/gebler_encrypted_fields.yaml (Symfony):

    encrypted_fields:
      master_key: '%env(ENCRYPTED_FIELDS_KEY)%'
      cipher: 'aes-256-gcm'
    

    For Laravel, publish the config via a service provider or .env:

    ENCRYPTED_FIELDS_MASTER_KEY=%env(ENCRYPTED_FIELDS_KEY)%
    
  4. Run the Migration Create and execute the encryption_key table migration:

    php bin/console make:migration
    php bin/console doctrine:migrations:migrate
    
  5. Annotate an Entity Add the #[EncryptedField] attribute to sensitive fields in a Doctrine entity:

    use Gebler\EncryptedFieldsBundle\Attribute\EncryptedField;
    
    #[ORM\Entity]
    class User {
        #[EncryptedField]
        private string $ssn;
    
        #[EncryptedField(elements: ['credit_card'])]
        private array $paymentDetails;
    }
    
  6. Test Encryption Persist and retrieve an entity to verify automatic encryption/decryption:

    $user = new User();
    $user->ssn = '123-45-6789';
    $entityManager->persist($user);
    $entityManager->flush();
    
    $fetchedUser = $entityManager->find(User::class, $user->getId());
    // $fetchedUser->ssn is automatically decrypted
    

Implementation Patterns

Usage Patterns

1. Field-Level Encryption

  • Standard Use: Encrypt individual fields with per-record keys (default behavior):
    #[EncryptedField]
    private string $apiKey;
    
  • Array Elements: Encrypt specific nested array elements:
    #[EncryptedField(elements: ['token', 'expiry'])]
    private array $authData;
    

2. Master Key Overrides

  • Global Encryption: Use the master key directly for fields requiring it (e.g., audit logs):
    #[EncryptedField(useMasterKey: true)]
    private string $adminNotes;
    
  • Custom Keys: Specify a unique key for a field (e.g., for key rotation testing):
    #[EncryptedField(key: 'custom_key_for_this_field')]
    private string $legacyData;
    

3. Entity Lifecycle Integration

  • Pre-Persist/Update: Fields are encrypted automatically when flush() is called.
  • Post-Load: Fields are decrypted transparently when entities are fetched.
  • Bulk Operations: Use EntityManager::clear() cautiously—decrypted fields may be lost if entities are detached.

4. Query Filtering

  • Avoid querying encrypted fields directly. Use Doctrine’s DISTINCT or IN clauses for encrypted columns:
    $query->select('DISTINCT u.id')
          ->from(User::class, 'u')
          ->where('u.encryptedField LIKE :pattern'); // ❌ Avoid
    
  • Instead, filter on non-encrypted fields or use database-specific functions (e.g., PostgreSQL’s pgcrypto).

5. Repository Patterns

  • Encrypted Field Accessors: Create repository methods to handle encrypted data safely:
    public function findByEncryptedSsn(string $ssn) {
        // Decrypt manually if needed (rare; bundle handles this automatically)
        return $this->createQueryBuilder('u')
            ->where('u.ssn = :ssn') // Encrypted field
            ->setParameter('ssn', $ssn)
            ->getQuery()
            ->getOneOrNullResult();
    }
    

6. Caching Decrypted Data

  • Cache decrypted values for performance-critical fields (e.g., user profiles):
    private ?string $cachedDecryptedSsn = null;
    
    public function getSsn(): string {
        return $this->cachedDecryptedSsn ?? $this->ssn;
    }
    
  • Invalidate cache on entity updates.

Workflows

1. New Feature Development

  • Step 1: Identify sensitive fields in the entity.
  • Step 2: Add #[EncryptedField] attributes.
  • Step 3: Test with sample data to ensure encryption/decryption works.
  • Step 4: Update unit tests to mock encrypted fields if needed.

2. Legacy System Retrofit

  • Step 1: Audit existing Doctrine entities for PII.
  • Step 2: Add encryption attributes incrementally (e.g., start with User entity).
  • Step 3: Backfill encrypted data using a data migration:
    // Custom migration to re-encrypt existing data
    $users = $entityManager->getRepository(User::class)->findAll();
    foreach ($users as $user) {
        $user->setSsn($user->getSsn()); // Trigger re-encryption
        $entityManager->flush($user);
    }
    
  • Step 4: Validate decrypted data matches original values.

3. Key Rotation

  • Planned Rotation:
    1. Generate a new master key (store securely).
    2. Run the rotation command:
      php bin/console gebler:encryption:rotate-key --generate-new-key
      
    3. Update .env with the new key.
    4. Monitor logs for decryption/encryption errors.
  • Emergency Rotation (e.g., key compromise):
    1. Use the old key to decrypt data:
      php bin/console gebler:encryption:rotate-key --database-key-file=/path/to/old.key
      
    2. Generate a new master key and re-encrypt:
      php bin/console gebler:encryption:rotate-key --generate-new-key
      

4. Multi-Environment Sync

  • Step 1: Export the master key from production:
    php bin/console gebler:encryption:rotate-key --database-key-file=prod.key
    
  • Step 2: Import into staging/dev:
    php bin/console gebler:encryption:rotate-key --database-key-file=prod.key
    
  • Step 3: Update .env with the same key.

Integration Tips

1. Symfony-Specific

  • Dependency Injection: Access the encryption service directly if needed:
    use Gebler\EncryptedFieldsBundle\Service\EncryptionService;
    
    public function __construct(private EncryptionService $encryptionService) {}
    
  • Event Listeners: Subscribe to prePersist/preUpdate to log encryption events:
    # config/services.yaml
    services:
        App\EventListener\EncryptionLogger:
            tags:
                - { name: doctrine.event_listener, event: prePersist }
                - { name: doctrine.event_listener, event: preUpdate }
    

2. Laravel-Specific

  • Service Provider: Bind the bundle’s services in AppServiceProvider:
    public function register() {
        $this->app->bind(
            Gebler\EncryptedFieldsBundle\Service\EncryptionService::class,
            Gebler\EncryptedFieldsBundle\Service\EncryptionService::class
        );
    }
    
  • Artisan Commands: Override Symfony commands with Laravel’s Artisan::call() or create custom commands.

3. Testing

  • Unit Tests: Mock the EncryptionService to test entity behavior:
    $encryptionService = $this->createMock(EncryptionService::class);
    $encryptionService->method('encrypt')->willReturn('encrypted_value');
    $this->entityManager->getService('gebler_encrypted_fields.encryption_service') = $encryptionService;
    
  • Integration Tests: Use transactions to test encryption/decryption:
    $entityManager->beginTransaction();
    try {
        $user = new User();
        $user->ssn = '123-45-6
    
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.
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony
spatie/flare-daemon-runtime