philetaylor/ciphersweet-encrypt
A Laravel package for integrating CipherSweet field-level encryption, letting you store sensitive database columns as encrypted values while keeping them searchable through blind indexes. It provides Eloquent-friendly configuration and casts for encrypting and decrypting attributes transparently.
What it is. philetaylor/ciphersweet-encrypt (repo: doctrine-ciphersweet) is a thin integration layer that wires Paragon Initiative's CipherSweet field-level encryption library into Doctrine. Its value is not novel cryptography — that lives upstream in CipherSweet — but the plumbing: registering custom Doctrine DBAL column types so a mapped field is transparently encrypted on write and decrypted on hydration, plus (likely) helpers for building WHERE clauses against CipherSweet blind indexes so encrypted fields remain equality-searchable.
Architecture fit. Strong fit only for a Doctrine-based PHP application — Laravel using laravel-doctrine, Symfony with Doctrine ORM, or a standalone Doctrine service. It is not an Eloquent package, despite the Laravel-adjacent naming. The pattern it enables (ciphertext column + separate blind-index column, encryption at the persistence boundary) is the correct one for PII/GDPR/PCI scoped data, and the blind-index approach is meaningfully better than naive application-level Crypt::encrypt() because it preserves lookup capability without decrypting rows in application memory.
Integration feasibility. Low-to-moderate, with a caveat that dominates everything else: the last release was September 2020. Since then Doctrine DBAL 3.x reworked the Type API and DBAL 4.x removed Type::getName(); CipherSweet itself advanced through API-breaking majors (EncryptionService, provider/backend reorganisation, SensitiveField changes). A 2020 wrapper almost certainly targets CipherSweet 3.x and DBAL 2.x. Expect a version-matrix spike to be required before any commitment, and expect it to fail without patching.
Technical risk — high, and specifically cryptographic risk. One star, zero dependents, single maintainer, no release in ~5 years. That means: no bug reports from production users, no CVE surveillance, no third-party audit, unknown test coverage, and no observable response SLA. Crypto-adjacent code is the worst category to take on faith — the failure modes are silent (wrong key derivation, truncated ciphertext, unpinned blind-index salt) and the blast radius is permanent data loss or a compliance claim that does not hold. Secondary risk is supply chain: a dormant, low-traffic package with a single owner is a realistic hijack or typo-squat target. If used at all, pin to a commit hash and vendor the source.
Key questions to answer before any decision.
MultiTenant backend), or is it a static string in config?TEXT/BLOB in migrations, or will VARCHAR(255) silently truncate?Verdict. Do not adopt as a runtime dependency. Treat it as a reference implementation: read the ~100–200 lines of Type registration and query-building, reimplement them in-house against a pinned paragonie/ciphersweet, and own the result. The pattern is worth stealing; the package is not worth depending on.
Stack fit. Doctrine-only. For an Eloquent codebase the equivalent is a custom cast plus an explicit blind-index column and query scope — same architecture, different binding point; do not try to force this package into Eloquent.
Migration path. Greenfield is straightforward: define schema as ciphertext TEXT + blind_index CHAR(64) from day one and never store plaintext. Brownfield is the real work and needs a phased backfill: add nullable ciphertext/blind-index columns alongside the legacy plaintext, dual-write (encrypt on write, plaintext retained), batch-backfill existing rows in bounded chunks with checkpointing, run a read path that prefers ciphertext and falls back to plaintext, verify row-count and spot-check decrypts, then drop the plaintext columns in a separate, reversible-later deploy. Budget for a full re-encryption pass if keys ever change, since rotation is a rewrite, not a metadata update.
Compatibility. Pin the whole triad explicitly — PHP minor, doctrine/dbal, paragonie/ciphersweet — and treat any upstream major bump as a breaking change requiring re-validation of round-trip and blind-index behaviour. Validate on a throwaway branch first; the expected outcome is that the 2020 wrapper needs patching, which is itself the argument for vendoring.
Sequencing.
LIKE, and most reporting.app/Doctrine/Types, with tests asserting ciphertext-at-rest.Maintenance. Effectively unmaintained. Owning a vendored copy costs perhaps 2–4 engineer-days a year in upstream tracking, plus a re-validation sprint on each CipherSweet major. Depending on the package directly costs unbounded risk for zero saved effort.
Support. No vendor, no SLA, no community. This must have a named internal owner — ideally whoever owns the key management story — because the failure modes are security incidents, not bug tickets.
Scaling. Encryption itself is cheap (AES-GCM is microseconds per KB); the costs are elsewhere. Every sensitive field doubles storage into TEXT, every blind index adds an indexed column and an HMAC computation per write, and decryption sits in the hot path of every read that touches those fields. Pagination and selective column hydration become mandatory rather than nice-to-have. Search capability narrows permanently to exact-match blind indexes; anything resembling fuzzy, range, or sort-by-encrypted-field must move to a separate search index of deliberately chosen, non-sensitive derived values.
Failure modes to plan for.
Type API contract changes.Ramp-up. Low for application developers — transparent types and casts mean most engineers never see the machinery, which is the point. Moderate-to-high for DBAs and on-call: backup/restore, key escrow, rotation drills, and the ability to distinguish "record missing" from "record undecryptable" during an incident. One sprint of enablement plus a written runbook is the realistic floor.
How can I help you explore Laravel packages today?