PHPackages                             kyzegs/doctrine-encryption-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. kyzegs/doctrine-encryption-bundle

ActiveSymfony-bundle[Security](/categories/security)

kyzegs/doctrine-encryption-bundle
=================================

Doctrine field encryption and blind indexes for Symfony applications.

00PHP

Since Jun 22Pushed todayCompare

[ Source](https://github.com/Kyzegs/doctrine-encryption-bundle)[ Packagist](https://packagist.org/packages/kyzegs/doctrine-encryption-bundle)[ RSS](/packages/kyzegs-doctrine-encryption-bundle/feed)WikiDiscussions master Synced today

READMEChangelog (1)DependenciesVersions (3)Used By (0)

[![Doctrine Encryption Bundle banner](banner.svg)](banner.svg)

Doctrine Encryption Bundle
==========================

[](#doctrine-encryption-bundle)

Field-level authenticated encryption and blind indexes for Doctrine entities in Symfony applications.

Maintained fork published as `kyzegs/doctrine-encryption-bundle`, replacing the unmaintained original package.

Requirements
------------

[](#requirements)

- PHP 8.2 or newer
- Symfony 6.4, 7.4, or 8.x
- Doctrine ORM 2.20 or 3.x
- OpenSSL with AES-256-GCM support

Installation
------------

[](#installation)

```
composer require kyzegs/doctrine-encryption-bundle
```

Register the bundle if Symfony Flex has not already done so:

```
// config/bundles.php
return [
    Kyzegs\DoctrineEncryptionBundle\DoctrineEncryptionBundle::class => ['all' => true],
];
```

Generate a 256-bit key:

```
bin/console encrypt:genkey
```

Store the result in a secret manager or an uncommitted environment file:

```
DOCTRINE_ENCRYPTION_ENCRYPT_KEY=base64-encoded-key
DOCTRINE_ENCRYPTION_BLIND_INDEX_KEY=a-different-secret
```

Configure the bundle:

```
# config/packages/doctrine_encryption.yaml
doctrine_encryption:
    encrypt_key: '%env(DOCTRINE_ENCRYPTION_ENCRYPT_KEY)%'
    blind_index_key: '%env(DOCTRINE_ENCRYPTION_BLIND_INDEX_KEY)%'
    key_id: '2026-01'
```

The encryption and blind-index keys should be different. Never commit either key.

Encrypting fields
-----------------

[](#encrypting-fields)

Use the PHP attribute on a Doctrine string field. Allow room for the versioned ciphertext envelope; `TEXT` is the least surprising choice.

```
use Doctrine\ORM\Mapping as ORM;
use Kyzegs\DoctrineEncryptionBundle\Attribute\Encrypted;

#[ORM\Column(type: 'text', nullable: true)]
#[Encrypted]
private ?string $personalNumber = null;
```

New values are written with AES-256-GCM in a versioned, authenticated envelope. The entity contains plaintext while it is in memory; Doctrine stores ciphertext. Existing unversioned AES-CBC and AES-GCM values from older bundle versions remain readable.

External XML, YAML, or PHP Doctrine mappings can opt in without modifying the entity:

```

        true

```

The equivalent field option is `encrypted: true`.

Encrypted scalar properties inside Doctrine embeddables are supported. Mark the embeddable property normally; bundle uses Doctrine's nested field path for encryption, decryption, change tracking, and database maintenance:

```
#[ORM\Embeddable]
final class ContactDetails
{
    #[ORM\Column(type: 'text')]
    #[Encrypted]
    public string $privateNote;
}
```

Searching encrypted values
--------------------------

[](#searching-encrypted-values)

Randomized encryption cannot be queried by plaintext and must not carry a meaningful unique constraint. Add a blind-index column instead:

```
use Kyzegs\DoctrineEncryptionBundle\Attribute\BlindIndex;
use Kyzegs\DoctrineEncryptionBundle\Attribute\Encrypted;

#[ORM\Column(type: 'text')]
#[Encrypted]
private string $email;

#[ORM\Column(type: 'string', length: 64, unique: true, nullable: true)]
#[BlindIndex(sourceField: 'email', normalizer: BlindIndex::NORMALIZE_LOWERCASE)]
private ?string $emailLookupHash = null;
```

Create the same hash when querying:

```
use Kyzegs\DoctrineEncryptionBundle\Hashers\BlindIndexHasherInterface;

$hash = $blindIndexHasher->hash($email, BlindIndex::NORMALIZE_LOWERCASE);
$user = $repository->findOneBy(['emailLookupHash' => $hash]);
```

Available normalizers are `none`, `trim`, `lowercase`, and `uppercase`. Blind indexes reveal equality patterns; use them only for fields that genuinely need lookups.

Rebuild indexes in bounded batches:

```
bin/console encrypt:blind-index --batch-size=500 --dry-run
bin/console encrypt:blind-index --batch-size=500
```

Key rotation
------------

[](#key-rotation)

Change the current key and key ID, then retain old keys under `decryption_keys`:

```
doctrine_encryption:
    encrypt_key: '%env(DOCTRINE_ENCRYPTION_ENCRYPT_KEY_2026)%'
    key_id: '2026'
    decryption_keys:
        '2025': '%env(DOCTRINE_ENCRYPTION_ENCRYPT_KEY_2025)%'
```

Back up the database, inspect the operation, and rotate in batches:

```
bin/console encrypt:database rotate --dry-run
bin/console encrypt:database rotate --batch-size=250
```

Remove a retired key only after every value using its key ID has been rotated and verified.

Database maintenance
--------------------

[](#database-maintenance)

The maintenance command supports `encrypt`, `decrypt`, and `rotate`, custom and composite scalar identifiers, quoted identifiers, transactions, batches, confirmation, and dry runs:

```
bin/console encrypt:database encrypt --dry-run
bin/console encrypt:database decrypt --manager=tenant --batch-size=100
```

Association identifiers are rejected because they cannot be updated safely by the low-level command. Always take a verified backup before a write operation.

Multiple Doctrine connections
-----------------------------

[](#multiple-doctrine-connections)

```
doctrine_encryption:
    encrypt_key: '%env(DOCTRINE_ENCRYPTION_ENCRYPT_KEY)%'
    connections: ['default', 'tenant']
```

Optional Twig filter
--------------------

[](#optional-twig-filter)

Install Twig and leave `enable_twig` enabled to expose `value|decrypt`:

```
composer require symfony/twig-bundle
```

Decrypting in templates increases the number of places where plaintext exists. Prefer decrypting only at the application boundary that needs it.

Custom integrations
-------------------

[](#custom-integrations)

`KeyProviderInterface` is the extension point for Vault, KMS, HSM, or another secret source. It returns binary 32-byte keys and supports lookup by ciphertext key ID. Register the implementation as a service and configure it directly:

```
doctrine_encryption:
    key_provider_service: 'App\\Encryption\\KmsKeyProvider'
    blind_index_key: '%env(DOCTRINE_ENCRYPTION_BLIND_INDEX_KEY)%'
```

Likewise, `encryptor_service` accepts any registered `EncryptorInterface` service with arbitrary injected dependencies. `encryptor_class` remains available as a deprecated compatibility path for classes using the historical event-dispatcher constructor.

Security notes
--------------

[](#security-notes)

- Encryption does not replace access control, TLS, backups, audit logging, or database hardening.
- Losing an encryption key permanently loses the corresponding data.
- Application compromise can expose plaintext and keys while the process is running.
- Blind indexes permit equality analysis and should use a separate high-entropy secret.
- Test restoration and rotation on a copy of production data before operating on production.

See [UPGRADE.md](UPGRADE.md) before upgrading an existing installation and [SECURITY.md](SECURITY.md) for vulnerability reporting.

###  Health Score

23

—

LowBetter than 26% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity14

Early-stage or recently created project

 Bus Factor1

Top contributor holds 68.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/eb13f61de13b186245a047cd1d882784e4dacb5b9f2a8f930b20e5992159e61a?d=identicon)[Kyzegs](/maintainers/Kyzegs)

---

Top Contributors

[![mogilvie](https://avatars.githubusercontent.com/u/7894754?v=4)](https://github.com/mogilvie "mogilvie (103 commits)")[![Lpsd](https://avatars.githubusercontent.com/u/40902730?v=4)](https://github.com/Lpsd "Lpsd (14 commits)")[![Kyzegs](https://avatars.githubusercontent.com/u/45851377?v=4)](https://github.com/Kyzegs "Kyzegs (11 commits)")[![danabrey](https://avatars.githubusercontent.com/u/10567440?v=4)](https://github.com/danabrey "danabrey (3 commits)")[![tsiatka](https://avatars.githubusercontent.com/u/47247055?v=4)](https://github.com/tsiatka "tsiatka (2 commits)")[![David-Dadon](https://avatars.githubusercontent.com/u/180660987?v=4)](https://github.com/David-Dadon "David-Dadon (2 commits)")[![raulpuente](https://avatars.githubusercontent.com/u/6062825?v=4)](https://github.com/raulpuente "raulpuente (2 commits)")[![Seros](https://avatars.githubusercontent.com/u/9016208?v=4)](https://github.com/Seros "Seros (2 commits)")[![FournyP](https://avatars.githubusercontent.com/u/64586968?v=4)](https://github.com/FournyP "FournyP (1 commits)")[![feymo](https://avatars.githubusercontent.com/u/60115888?v=4)](https://github.com/feymo "feymo (1 commits)")[![TravisMarkvh](https://avatars.githubusercontent.com/u/62843006?v=4)](https://github.com/TravisMarkvh "TravisMarkvh (1 commits)")[![Rindula](https://avatars.githubusercontent.com/u/20195655?v=4)](https://github.com/Rindula "Rindula (1 commits)")[![russelomua](https://avatars.githubusercontent.com/u/313658?v=4)](https://github.com/russelomua "russelomua (1 commits)")[![brysn](https://avatars.githubusercontent.com/u/3696643?v=4)](https://github.com/brysn "brysn (1 commits)")[![spacecodeit](https://avatars.githubusercontent.com/u/25511964?v=4)](https://github.com/spacecodeit "spacecodeit (1 commits)")[![tismaximo](https://avatars.githubusercontent.com/u/119903808?v=4)](https://github.com/tismaximo "tismaximo (1 commits)")[![TonySma](https://avatars.githubusercontent.com/u/6605868?v=4)](https://github.com/TonySma "TonySma (1 commits)")[![joelterry-promenade](https://avatars.githubusercontent.com/u/106996220?v=4)](https://github.com/joelterry-promenade "joelterry-promenade (1 commits)")[![jdecool](https://avatars.githubusercontent.com/u/433926?v=4)](https://github.com/jdecool "jdecool (1 commits)")

### Embed Badge

![Health badge](/badges/kyzegs-doctrine-encryption-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/kyzegs-doctrine-encryption-bundle/health.svg)](https://phpackages.com/packages/kyzegs-doctrine-encryption-bundle)
```

###  Alternatives

[mews/purifier

Laravel 5/6/7/8/9/10 HtmlPurifier Package

2.0k18.0M134](/packages/mews-purifier)[paragonie/ecc

PHP Elliptic Curve Cryptography library

24772.0k35](/packages/paragonie-ecc)[fof/recaptcha

Increase your forum's security with Google reCAPTCHA

1436.9k](/packages/fof-recaptcha)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
