dwgebler/encrypted-fields-bundle
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).
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).
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)%
Run the Migration
Create and execute the encryption_key table migration:
php bin/console make:migration
php bin/console doctrine:migrations:migrate
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;
}
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
#[EncryptedField]
private string $apiKey;
#[EncryptedField(elements: ['token', 'expiry'])]
private array $authData;
#[EncryptedField(useMasterKey: true)]
private string $adminNotes;
#[EncryptedField(key: 'custom_key_for_this_field')]
private string $legacyData;
flush() is called.EntityManager::clear() cautiously—decrypted fields may be lost if entities are detached.DISTINCT or IN clauses for encrypted columns:
$query->select('DISTINCT u.id')
->from(User::class, 'u')
->where('u.encryptedField LIKE :pattern'); // ❌ Avoid
pgcrypto).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();
}
private ?string $cachedDecryptedSsn = null;
public function getSsn(): string {
return $this->cachedDecryptedSsn ?? $this->ssn;
}
#[EncryptedField] attributes.User entity).// 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);
}
php bin/console gebler:encryption:rotate-key --generate-new-key
.env with the new key.php bin/console gebler:encryption:rotate-key --database-key-file=/path/to/old.key
php bin/console gebler:encryption:rotate-key --generate-new-key
php bin/console gebler:encryption:rotate-key --database-key-file=prod.key
php bin/console gebler:encryption:rotate-key --database-key-file=prod.key
.env with the same key.use Gebler\EncryptedFieldsBundle\Service\EncryptionService;
public function __construct(private EncryptionService $encryptionService) {}
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 }
AppServiceProvider:
public function register() {
$this->app->bind(
Gebler\EncryptedFieldsBundle\Service\EncryptionService::class,
Gebler\EncryptedFieldsBundle\Service\EncryptionService::class
);
}
Artisan::call() or create custom commands.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;
$entityManager->beginTransaction();
try {
$user = new User();
$user->ssn = '123-45-6
How can I help you explore Laravel packages today?