Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Encrypt Bundle Laravel Package

specshaper/encrypt-bundle

SpecShaper EncryptBundle adds transparent Doctrine field encryption to Symfony apps. Mark entity properties as encrypted and the bundle automatically handles encryption and decryption, keeping sensitive database data protected at rest with configurable keys.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the caveat that shapes everything else: specshaper/encrypt-bundle is a Symfony bundle built around Doctrine ORM event subscribers, not a Laravel package. There is no service provider, no config/encrypt.php, and no Eloquent integration. A Laravel developer has two realistic paths:

  • If you already run laravel-doctrine/orm — register the bundle's DoctrineEncryptSubscriber on the EntityManager's event manager in a service provider and you get transparent field encryption.
  • If you're on stock Eloquent — you almost certainly don't need this package. Laravel ships an encrypted cast ('ssn' => 'encrypted', plus encrypted:array|json|collection) that covers the same use case with zero extra dependencies. Use this bundle only if you specifically need its Doctrine attribute-driven, driver-swappable design.

Assuming the Doctrine path:

  1. composer require specshaper/encrypt-bundle — this is a low-visibility fork of mogilvie/EncryptBundle (105 stars upstream), so verify the namespace (Specshaper\… vs Mogilvie\…) and config alias before wiring anything. There is no Flex recipe; register the bundle manually in config/bundles.php.
  2. Generate a key: openssl rand -base64 32, store as ENCRYPT_KEY in .env.
  3. Configure the cipher and encryptor driver (config root is specshaper_encrypt or the retained upstream mogilvie_encrypt — check DependencyInjection/Configuration.php):
# config/packages/specshaper_encrypt.yaml
specshaper_encrypt:
    key: '%env(ENCRYPT_KEY)%'
    cipher: 'aes-256-gcm'
    encryptor_class: Specshaper\EncryptBundle\Encryptors\OpenSslEncryptor
  1. Annotate the field:
use Specshaper\EncryptBundle\Configuration\Encrypted;

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

Where to look first: EncryptService / the encryptor interface (driver selection and output encoding), DoctrineEncryptSubscriber (the postLoad / prePersist / preUpdate hooks), and the Configuration/Encrypted attribute. First use case: PII columns — SSNs, medical notes, API tokens — where you want encryption to be invisible to repository and controller code.

Implementation Patterns

  • Transparent field encryption. The subscriber decrypts on postLoad and encrypts before flush, so $repo->find($id) returns plaintext while the DB holds base64 ciphertext. No repository changes required; this is the bundle's whole value proposition.
  • Manual encryption outside Doctrine. Inject EncryptServiceInterface anywhere and call encrypt() / decrypt() for queue payloads, cache entries, or files that never touch an entity.
  • Blind indexes for lookup. Because ciphertext is non-deterministic, add a sibling column storing hash_hmac('sha256', strtolower($email), $indexKey) and query on that. Keep the index key separate from the encryption key.
  • Key rotation as a command. Write a console command that pages through entities in batches of ~500, calls $em->refresh()/clear() between batches, decrypts with the old key and re-encrypts with the new one. Do not hold the whole table in the identity map.
  • Encrypt at the boundary, not the edges. Encrypt in a domain service before persisting; decrypt in an API resource/transformer. Never let a raw entity be json_encode'd — decrypted values leak into logs and responses.
  • JSON payloads. Encrypt the serialized blob into a text column and json_decode in a getter, rather than annotating each nested property.
  • Laravel-native equivalents for the same patterns: protected $casts = ['meta' => 'encrypted:array'], Crypt::encryptString() for payloads, and a custom cast + hash_hmac column for deterministic search.

Gotchas and Tips

  • Encrypted columns are not queryable. WHERE ssn = :ssn returns zero rows because every write produces different ciphertext. This fails silently — no exception, just empty results. Always use a blind-index column for filtering, sorting, or uniqueness.
  • Column width. Base64 inflates ciphertext ~1.35x, and GCM adds an IV plus auth tag. A varchar(255) that fit the plaintext will truncate the ciphertext, and truncation manifests as a decryption failure at read time, not at write time. Use text.
  • Hydration bypasses the subscriber. SELECT PARTIAL, scalar DQL, native queries, and DB::select() all return raw ciphertext. Doctrine's change tracking also compares decrypted values, so writes triggered outside the subscriber can persist plaintext.
  • Wrong key vs. wrong cipher look identical. openssl_decrypt returns false and the bundle throws a generic exception; the usual cause is an ENCRYPT_KEY mismatch between environments, not a cipher change. Check the key first.
  • Key loss is unrecoverable. There is no re-derivation path. Back the key up outside the app repo, and keep the old key available during rotation.
  • Connection charset. A latin1 or misconfigured connection can mangle stored ciphertext; verify with SHOW VARIABLES LIKE 'character_set_%' before blaming the crypto.
  • Do not double-encrypt. If a column already uses Laravel's encrypted cast, adding this bundle's #[Encrypted] attribute produces ciphertext-of-ciphertext and confusing failures. One mechanism per column.
  • Config and metadata caching. Symfony caches bundle config and Doctrine metadata; a newly added #[Encrypted] attribute won't take effect until cache clear/warmup.
  • Extension point: implement the bundle's encryptor interface to delegate to KMS/Vault transit — the subscriber only talks to the service, so this is the clean seam for key custody, envelope encryption, and rotation without touching entities.
  • Performance. Every field pays an openssl_* call per persist/load. Bulk inserts of tens of thousands of rows degrade measurably; measure before assuming the overhead is free.
  • Pin the version. With zero dependents and a recent fork date, treat this as a vendored dependency: lock the exact version, read the diff against upstream mogilvie/EncryptBundle, and be prepared to maintain it yourself.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core