Search by

nowo-tech / doctrine-encrypt-bundle

HecFranco

Symfony 7.4+|8 bundle to encrypt Doctrine entity fields at rest with Halite, Defuse, or MySQL-compatible MysqlAes (GDPR-friendly, multiple keys, key rotation).

Package info

github.com/nowo-tech/DoctrineEncryptBundle

Documentation

Type:symfony-bundle

pkg:composer/nowo-tech/doctrine-encrypt-bundle

Fund package maintenance!

HecFranco

Statistics

Installs: 11 480

Dependents: 5

Suggesters: 1

Stars: 6

Open Issues: 0

v2.4.2 2026-10-01 08:01 UTC

README

CI Packagist Version Packagist Downloads License PHP Symfony GitHub stars Coverage

⭐ Found this useful? Install from Packagist · Give it a star on GitHub so more developers can find it.

Symfony bundle to encrypt Doctrine entity fields at rest using Halite or Defuse—audited libraries, no custom crypto. For Symfony 7.4+ and 8 · PHP 8.2+. Suits GDPR and compliance (e.g. Art. 32); supports key rotation and AnonymizeBundle for anonymization and erasure.

FrankenPHP Friendly Worker Mode

This bundle is FrankenPHP worker mode friendly (including when the kernel is not reset between requests; see docs/FRANKENPHP-WORKER-AUDIT.md).

Table of contents

Quick search terms

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.

Features

  • ✅ Encrypt and decrypt entity properties with a single attribute
  • ✅ Multiple encryptor profiles — e.g. personal_data (Halite) and financial_data (Defuse) in the same app, each with its own key
  • ✅ Halite, Defuse, and MysqlAes (MySQL AES_ENCRYPT / AES_DECRYPT compatible) — audited or interoperable crypto; see MYSQL_AES.md
  • ✅ 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 AnonymizeBundle for GDPR-compliant anonymization and erasure
  • ✅ Symfony Flex recipe stub — see docs/INSTALLATION.md (recipes-contrib merge is still pending; Flex applies it when the recipe is enabled)
  • ✅ Compatible with Symfony 7.4+ and 8 and Doctrine ORM 2.x and 3.x
  • ✅ Compatible with FrankenPHP (HTTP runtime and optional worker mode; demos default to APP_ENV=dev with Caddyfile.dev, i.e. no PHP worker — see docs/DEMO-FRANKENPHP.md and Installation → FrankenPHP)

Installation

composer require nowo-tech/doctrine-encrypt-bundle

Install from Packagist

With 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],
];

Requirements

  • PHP >= 8.2
  • Symfony 7.4+ or 8 (^7.0 || ^8.0; tested on 7.4, 8.0, 8.1). 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.

Configuration

Create config/packages/nowo_doctrine_encrypt.yaml. You can use one encryptor (legacy) or multiple named profiles (recommended).

Multiple profiles (recommended)

Use different encryptors and keys per kind of data (e.g. personal vs financial):

nowo_doctrine_encrypt:
  default_profile: personal_data  # used when attribute has no profile or uses "default"
  profiles:
    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 profile, e.g. .Halite.personal_data.key, .Defuse.financial_data.key in the profile’s secret_directory_path. Add to .gitignore:

.Halite.key
.Defuse.key
.Halite.*.key
.Defuse.*.key

Generate keys: php bin/console doctrine:encrypt:generate-secret-key (creates missing Halite/Defuse keys for all profiles, or pass a profile alias). See docs/CONFIGURATION.md and docs/COMMANDS.md.

Single encryptor (one profile)

Use one entry under profiles (e.g. default):

nowo_doctrine_encrypt:
  default_profile: default
  profiles:
    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.

Usage

Mark entity properties with the Encrypted attribute. Use no argument (or "default") for the default config, or the config name when using multiple profiles:

use Nowo\DoctrineEncryptBundle\Configuration\Encrypted;

#[ORM\Entity]
class User
{
  #[ORM\Column(type: 'string')]
  #[Encrypted]  // or #[Encrypted('default')] — uses default_profile
  private ?string $email = null;
}

With multiple profiles, 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

The Symfony 8 demo is in demo/symfony8. It runs with FrankenPHP and Caddy (HTTP on port 80 in the container). FRANKENPHP_MODE selects classic or worker (default worker in .env.example) — docs/DEMO-FRANKENPHP.md. Default host port: 8008 via PORT. Quick start: docs/DEMO.md.

Development

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.

Documentation

Additional documentation

Tests and coverage

  • Tests: PHPUnit (unit and functional suites)
  • PHP: 100.00% of includable src/ (see docs/COVERAGE.md for justified exclusions)
  • Run: make test-coverage

License

The MIT License (MIT). Please see LICENSE for more information.

Author

Created by Héctor Franco Aceituno at Nowo.tech