PHPackages                             meritech/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. [Database &amp; ORM](/categories/database)
4. /
5. meritech/encryption-bundle

ActiveSymfony-bundle[Database &amp; ORM](/categories/database)

meritech/encryption-bundle
==========================

Symfony bundle for transparent Doctrine column encryption with DBAL types and blind index support for searchable encrypted data.

v1.0.4(4mo ago)32MITPHPPHP ^8.2CI passing

Since Jan 17Pushed 4mo agoCompare

[ Source](https://github.com/meripushko/symfony-meritech-encryption-bundle)[ Packagist](https://packagist.org/packages/meritech/encryption-bundle)[ Docs](https://github.com/meritech/encryption-bundle)[ RSS](/packages/meritech-encryption-bundle/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (7)Versions (6)Used By (0)

Encryption Bundle
=================

[](#encryption-bundle)

Symfony bundle for transparent Doctrine column encryption using custom DBAL types. Supports AES-256-GCM encryption with blind indexes for searchable encrypted data.

Features
--------

[](#features)

- **DBAL Types**: `encrypted_string`, `encrypted_text`, `encrypted_json`, `encrypted_string_deterministic`
- **Blind Indexes**: HMAC-based searchable encryption with configurable bit length
- **Key Rotation**: Multi-key support for seamless key rotation
- **Modern PHP**: Requires PHP 8.2+, uses readonly classes and modern syntax

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

[](#requirements)

- PHP 8.2+
- Symfony 6.4+ or 7.x
- Doctrine ORM 2.15+ or 3.x
- OpenSSL extension

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

[](#installation)

```
composer require meritech/encryption-bundle
```

Generate an encryption key (32 bytes, base64-encoded):

```
php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
```

Set the key in your environment:

```
# .env.local
ENCRYPTION_KEY="base64:your-generated-key-here"
```

Configuration
-------------

[](#configuration)

```
# config/packages/meritech_encryption.yaml
meritech_encryption:
    key:
        value: '%env(ENCRYPTION_KEY)%'
        id: 'k1'  # Optional key ID for rotation tracking

    # Previous keys for decrypting old data
    rotated_keys:
        k0: '%env(OLD_ENCRYPTION_KEY)%'

    # Separate key for blind indexes (recommended)
    blind_index_key: '%env(BLIND_INDEX_KEY)%'

    # Blind index defaults
    blind_index:
        default_bits: 64    # 16-256, lower = more privacy
        algorithm: sha256
```

Register the bundle (if not using Symfony Flex):

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

Usage
-----

[](#usage)

### Basic Encrypted Columns

[](#basic-encrypted-columns)

Use DBAL types in your entity mappings:

```
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    // Encrypted string (randomized - different ciphertext each time)
    #[ORM\Column(type: 'encrypted_string')]
    private string $email;

    // Encrypted JSON data
    #[ORM\Column(type: 'encrypted_json', nullable: true)]
    private ?array $preferences = null;

    // Deterministic encryption (same plaintext = same ciphertext)
    // Use for exact-match searches without blind index
    #[ORM\Column(type: 'encrypted_string_deterministic')]
    private string $ssn;
}
```

### Searchable Encryption with Blind Index

[](#searchable-encryption-with-blind-index)

For columns that need WHERE clause searches, use blind indexes:

```
use Doctrine\ORM\Mapping as ORM;
use Meritech\EncryptionBundle\Attribute\BlindIndex;

#[ORM\Entity]
#[ORM\Index(columns: ['email_index'], name: 'idx_email_blind')]
class User
{
    #[ORM\Column(type: 'encrypted_string')]
    #[BlindIndex(indexProperty: 'emailIndex', bits: 64)]
    private string $email;

    // Blind index column - auto-populated on persist/update
    #[ORM\Column(type: 'blind_index', length: 16, nullable: true)]
    private ?string $emailIndex = null;

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;
        return $this;
    }
}
```

### Querying with Blind Index

[](#querying-with-blind-index)

```
use Meritech\EncryptionBundle\Crypto\BlindIndexer;

class UserRepository extends ServiceEntityRepository
{
    public function __construct(
        ManagerRegistry $registry,
        private readonly BlindIndexer $blindIndexer,
    ) {
        parent::__construct($registry, User::class);
    }

    public function findByEmail(string $email): ?User
    {
        // Compute blind index for the search value
        $normalized = $this->blindIndexer->normalize($email);
        $index = $this->blindIndexer->generate($normalized, 'User.email', 64);

        // Query - may return multiple results due to collisions
        $candidates = $this->createQueryBuilder('u')
            ->where('u.emailIndex = :index')
            ->setParameter('index', $index)
            ->getQuery()
            ->getResult();

        // Filter false positives by comparing decrypted values
        foreach ($candidates as $user) {
            if (mb_strtolower($user->getEmail()) === mb_strtolower($email)) {
                return $user;
            }
        }

        return null;
    }
}
```

DBAL Types
----------

[](#dbal-types)

TypeDescription`encrypted_string`Randomized AES-256-GCM encryption for strings`encrypted_text`Alias for `encrypted_string` (semantic)`encrypted_json`Encrypts arrays/objects as JSON`encrypted_string_deterministic`Same plaintext = same ciphertext (allows equality comparison)`blind_index`Simple VARCHAR for storing pre-computed blind indexesBlind Index Configuration
-------------------------

[](#blind-index-configuration)

The `#[BlindIndex]` attribute supports:

OptionDefaultDescription`indexProperty`(required)Property name for storing the blind index`context``ClassName.propertyName`HMAC domain separation context`bits`64Output bits (16-256). Lower = more collisions = more privacy`caseInsensitive`trueLowercase before hashingSecurity Notes
--------------

[](#security-notes)

- **Randomized encryption** (default): Same plaintext produces different ciphertext each time. Most secure but cannot be searched.
- **Deterministic encryption**: Same plaintext = same ciphertext. Leaks equality relationships. Use only when exact-match search is needed.
- **Blind indexes**: Truncated HMAC allows WHERE clause searches while preserving some privacy. Shorter bit length = more false positives = more privacy.
- **Key separation**: Use a separate `blind_index_key` so that compromising the blind index key doesn't reveal encrypted data.

Key Rotation
------------

[](#key-rotation)

1. Generate a new key and assign it a new ID
2. Add the current key to `rotated_keys`
3. Update `key.value` and `key.id` with the new key
4. Deploy - new data encrypted with new key, old data decrypted with rotated keys
5. Run migration to re-encrypt old data (implement in your application)

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance78

Regular maintenance activity

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

121d ago

### Community

Maintainers

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

---

Top Contributors

[![meripushko](https://avatars.githubusercontent.com/u/66920815?v=4)](https://github.com/meripushko "meripushko (24 commits)")

---

Tags

symfonysecurityencryptiondoctrinedbalSymfony Bundledatabase encryptionsearchable encryptionaes-256-gcmblind-index

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

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

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

###  Alternatives

[fresh/doctrine-enum-bundle

Provides support of ENUM type for Doctrine2 in Symfony applications.

4636.8M12](/packages/fresh-doctrine-enum-bundle)[sonata-project/doctrine-orm-admin-bundle

Integrate Doctrine ORM into the SonataAdminBundle

46117.7M155](/packages/sonata-project-doctrine-orm-admin-bundle)[ahmed-bhs/doctrine-doctor

Runtime analysis tool for Doctrine ORM integrated into Symfony Web Profiler. Unlike static linters, it analyzes actual query execution at runtime to detect performance bottlenecks, security vulnerabilities, and best practice violations during development with real execution context and data.

813.1k](/packages/ahmed-bhs-doctrine-doctor)[damienharper/auditor-bundle

Integrate auditor library in your Symfony projects.

4542.8M](/packages/damienharper-auditor-bundle)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1022.4k](/packages/rcsofttech-audit-trail-bundle)

PHPackages © 2026

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