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

Encryption Laravel Package

spaze/encryption

spaze/encryption is a lightweight PHP library for symmetric encryption, offering a simple API to encrypt and decrypt strings and application data using secure cryptographic primitives, with key handling that fits easily into existing PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps libsodium/Halite in a Laravel-friendly API, so day-to-day you mostly touch three things: the config file, the facade/service, and (for models) a cast.

  1. Install and publish config
    composer require <vendor>/<package>   # requires paragonie/halite ^5.1
    php artisan vendor:publish --tag=crypto-config
    
  2. Generate a key and put it in .env. Keys are 32-byte hex strings; sodium_bin2hex() output works, and lowercase or uppercase are both valid.
    // one-off, e.g. in tinker
    $hex = sodium_bin2hex(sodium_crypto_secretbox_keygen());
    
    CRYPTO_PREFIX_HEX=...
    
  3. Look at config/crypto.php first. The prefix_hex key is the default symmetric key; additional keys live under the same map with their own ids. Use the tagged form for anything asymmetric (prefix_secret_hex / prefix_public_hex) — it will refuse to boot the object if a key is in the wrong slot.
  4. First use case: encrypt a PII column on write and decrypt on read.
    use ...\Facades\Crypto;
    
    $ciphertext = Crypto::useKey('app')->encrypt($user->ssn);
    $plaintext  = Crypto::useKey('app')->decrypt($ciphertext);
    
    Then immediately reach for a cast so the rest of the app never sees ciphertext (see below).

Implementation Patterns

Eloquent cast for at-rest fields. The highest-leverage pattern: a CryptoCast on ssn, iban, notes, etc. Keep the ciphertext column as TEXT/LONGTEXT — v3 values are longer ($keyId$SymV1$... prefix plus base64), so an old VARCHAR(255) will start silently truncating and produce UnknownFormatMarkerException on read.

Row-bound encryption with additional data. When a field must not be swappable between rows, encrypt with the row's identity:

$ciphertext = Crypto::useKey('app')->encryptWithAd($value, $row->uuid);

This is the strongest pattern for anything with legal weight (KYC, audit trails), but it couples the ciphertext to the AD — remember this in every re-encryption path.

Key rotation as a queued sweep. Add the new key alongside the old, keep both configured, and run a chunked job/command:

Model::query()->whereNotNull('ssn')
    ->chunkById(500, function ($rows) {
        foreach ($rows as $row) {
            if (Crypto::needsReEncrypt($row->ssn)) {
                $row->ssn = Crypto::useKey('app_v2')->encrypt($row->ssn);
                $row->saveQuietly();
            }
        }
    });

Since v3, needsReEncrypt() returns true for every pre-v3 value, so your first run after upgrading is a full-table rewrite — throttle it, run it off-peak, and expect backup/WAL pressure.

Two-party service integration (AuthenticatedPublicKeyEncryption). Configure each side as one key id holding our secret + their public. Both directions encrypt/decrypt, and decryption proves origin. Good fit for: signed-ish webhook payloads, machine-to-machine API bodies where you also need a producer identity, multi-service deployments sharing a queue.

Write-only producers (AnonymousPublicKeyEncryption). The pattern that unlocks new deployments: a log shipper, telemetry collector, or CI job holds only recipients' public keys and physically cannot read back what it wrote. Use it when a compromise of the producer must not expose historical data. Note the trade-off: no AD slot and no origin proof — don't model authenticity on it.

Per-tenant / per-environment key ids. Multiple ids in config/crypto.php (one per partner or tenant) and select at runtime with useKey($id). Keep the id in a column or config, never derive it from the ciphertext alone — the marker makes the id visible, but you still need policy about which id is legal for which record.

Testing. Pin keys in phpunit.xml rather than .env, and build factories that write plaintext through the cast so tests exercise the round trip. Add one test asserting a fixed ciphertext decrypts, to catch accidental key rotation in CI.

Gotchas and Tips

  • Exception types changed in v3. Mismatched/unknown markers now throw FormatMarkerMismatchException / UnknownFormatMarkerException, not Halite's InvalidMessage or InvalidNumberOfComponentsException. Any catch written against the old types is now dead code — grep for them after upgrading, and prefer catching the package's own base exception.
  • Upgrade every deployment before writing. Older versions cannot read marked ($keyId$SymV1$...) values. In a multi-service fleet, writing from an upgraded node while an old node reads is a data-visible outage, not a graceful degradation.
  • encryptWithAd() rows must be re-encrypted with the same AD. A generic sweep that calls plain encrypt() on those columns will leave you with rows that decrypt fine but fail AD verification — i.e. corrupt from the application's point of view. Special-case those models.
  • The key id and marker are now inside the authenticated envelope. Don't build features that mutate the id prefix or strip markers to "normalize" storage; that is now an integrity failure. This caveat only survives for legacy-format values.
  • Use the tagged key form. prefix_secret_hex / prefix_public_hex fail at object construction instead of producing undecryptable ciphertext — the difference between a deploy-time error and a data-loss incident.
  • Don't parse exception messages. They are deliberately shortened and sanitized in v3 so key material and garbage can't leak into logs. Assert on class, not string.
  • Halite 5.1+ is required. If something else in the graph pins ^5.0 strictly, resolve it before deploying; check composer why paragonie/halite.
  • Anonymous encryption has no sealing/AD slot — a sealed value simply has nowhere to carry it. Don't assume decryptWithAd semantics apply symmetrically across the new classes.
  • Debugging flow for a failed decrypt: check the marker first (UnknownFormatMarkerException ⇒ truncated column, wrong column, or not from this package), then the key id (⇒ wrong useKey() or a key dropped from config), then the method/class (⇒ fed to the wrong API). Only then suspect tampering.
  • Extension points: bind your own implementation in the container / resolve keys from a vault instead of env by swapping the config resolver; keep markers intact so existing ciphertext stays readable. This is the safe place to add KMS-backed keys without touching call sites.
  • Keep old keys configured forever. The release explicitly guarantees old ciphertext always decrypts; you can only honour that if retired key ids stay in the key map (read-only) after rotation.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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