Symfony bundle to encrypt Doctrine entity fields at rest using Halite or Defuse—audited libraries, no custom crypto. For Symfony 7 and 8 · PHP 8.1+. Suits GDPR and compliance (e.g. Art. 32); supports key rotation and Nowo\AnonymizedBundle for anonymization and erasure.
⭐ Found this useful? Install from Packagist · Give it a star on GitHub so more developers can find it.
- Quick search terms
- Features
- Installation
- Configuration
- Usage
- Documentation
- Requirements
- Demo
- Development
- License & author
Looking for Doctrine encryption, encrypt entity fields, Halite Symfony, Defuse encryption, field-level encryption, encrypt database column, Symfony encrypt attribute, Doctrine Encrypted? You're in the right place.
- ✅ Encrypt and decrypt entity properties with a single attribute
- ✅ Multiple encryptor configs — e.g.
personal_data(Halite) andfinancial_data(Defuse) in the same app, each with its own key - ✅ Halite and Defuse — audited crypto libraries, no custom algorithms
- ✅ Transparent: encrypt on persist/update, decrypt on load
- ✅ EncryptUtil — programmatic
encrypt()/decrypt()with optional config name (default or e.g.financial_data) - ✅ MaskUtil — mask sensitive values in PHP (e.g. show only last N chars); usable in services
- ✅ Twig filters —
|decrypt(decrypt in templates; optional config:{{ value|decrypt }}or{{ value|decrypt('financial_data') }}) and|mask(mask for display:{{ value|mask(4) }}or{{ value|decrypt|mask(4) }}) - ✅ Works with embedded entities and inheritance
- ✅ Console commands: status, generate secret key, encrypt/decrypt database, rotate keys (backup, decrypt, change keys, re-encrypt with confirmations)
- ✅ Key rotation — one command or manual steps; combinable with Nowo\AnonymizedBundle for GDPR-compliant anonymization and erasure
- ✅ Symfony Flex recipe (register bundle + config; see docs/INSTALLATION.md)
- ✅ Compatible with Symfony 7 and 8 and Doctrine ORM 2.x and 3.x
- ✅ Compatible with FrankenPHP (HTTP runtime and optional worker mode; demos default to
APP_ENV=devwith Caddyfile.dev, i.e. no PHP worker — see docs/DEMO-FRANKENPHP.md and Installation → FrankenPHP)
composer require nowo-tech/doctrine-encrypt-bundleWith Symfony Flex, the recipe (when enabled) registers the bundle and creates the config file automatically. Without Flex, see docs/INSTALLATION.md for manual steps.
Manual registration in config/bundles.php:
<?php
return [
// ...
Nowo\DoctrineEncryptBundle\NowoDoctrineEncryptBundle::class => ['all' => true],
];Create config/packages/nowo_doctrine_encrypt.yaml. You can use one encryptor (legacy) or multiple named configs (recommended).
Use different encryptors and keys per kind of data (e.g. personal vs financial):
nowo_doctrine_encrypt:
default_config: personal_data # used when attribute has no config or uses "default"
configs:
personal_data:
encryptor_class: Halite
secret_directory_path: '%kernel.project_dir%'
financial_data:
encryptor_class: Defuse
secret_directory_path: '%kernel.project_dir%'Defuse: composer require defuse/php-encryption ^2.1
Key files: one per config, e.g. .Halite.personal_data.key, .Defuse.financial_data.key in the config’s secret_directory_path. Add to .gitignore:
.Halite.key
.Defuse.key
.Halite.*.key
.Defuse.*.keyGenerate keys: php bin/console doctrine:encrypt:generate-secret-key (creates missing Halite/Defuse keys for all configs, or pass a config alias). See docs/CONFIGURATION.md and docs/COMMANDS.md.
Use one entry under configs (e.g. default):
nowo_doctrine_encrypt:
default_config: default
configs:
default:
encryptor_class: Halite # or Defuse
secret_directory_path: '%kernel.project_dir%'Key file: .Halite.default.key (or .Defuse.default.key). Full options: docs/CONFIGURATION.md.
Mark entity properties with the Encrypted attribute. Use no argument (or "default") for the default config, or the config name when using multiple configs:
use Nowo\DoctrineEncryptBundle\Configuration\Encrypted;
#[ORM\Entity]
class User
{
#[ORM\Column(type: 'string')]
#[Encrypted] // or #[Encrypted('default')] — uses default_config
private ?string $email = null;
}With multiple configs, pass the config alias per property:
#[ORM\Column(type: 'string')]
#[Encrypted('personal_data')]
private ?string $email = null;
#[ORM\Column(type: 'string')]
#[Encrypted('financial_data')]
private ?string $iban = null;Values are encrypted on persist/update and decrypted on load. For programmatic use: EncryptUtil (encrypt/decrypt) and MaskUtil (mask for display). In Twig use the |decrypt and |mask filters. See docs/USAGE.md for EncryptUtil, MaskUtil, Twig filters, embedded entities, and inheritance.
- Demo with FrankenPHP (development and production)
- Example
- Commands
- Key rotation
- Demo
- Custom encryptor
- PHP >= 8.1
- Symfony 7 or 8 (^7.0 || ^8.0). Current releases of this package require Symfony 7 or 8 (see
composer.json). - Doctrine ORM ^2.15 || ^3.0
- paragonie/halite (included); for Defuse:
defuse/php-encryption ^2.1 - ext-sodium recommended for Halite (or sodium_compat)
See docs/INSTALLATION.md and docs/UPGRADING.md for compatibility notes.
Demos for Symfony 7 and 8 are in demo/symfony7, demo/symfony8. Each runs with FrankenPHP and Caddy (HTTP on port 80 in the container). docker-compose defaults to APP_ENV=dev, so the entrypoint uses Caddyfile.dev (no PHP worker; changes visible on refresh). Worker mode is for a production-style setup — docs/DEMO-FRANKENPHP.md. Default host ports: 8007 (symfony7), 8008 (symfony8) via PORT. Quick start: docs/DEMO.md.
Run tests and QA with Docker: make up && make install && make test (or make test-coverage, make qa). Without Docker: composer install && composer test. See Makefile for all targets.
- Tests: PHPUnit (unit and functional suites)
- PHP: 96.14%
The MIT License (MIT). Please see LICENSE for more information.
Created by Héctor Franco Aceituno at Nowo.tech