erikwang2013/encryptable
Encryptable is a Laravel trait that transparently encrypts and decrypts Eloquent model attributes, keeping sensitive database fields safe with minimal code changes. Just declare encrypted columns and let the package handle the rest.
Architecture fit
This package operates at the persistence boundary — an Eloquent-level trait/cast that transparently encrypts attribute values before they hit the database and decrypts on read. For a Laravel application with PII, financial, or health-adjacent columns, that is the correct seam: it keeps crypto out of controllers and jobs, and it makes the encryption policy declarative per model/column rather than scattered across query code.
The v2.0.13 notes reveal something more important than the individual fixes: the package is deliberately framework-agnostic. Rules read the cipher from the "actual resolved encrypter" across Laravel, Webman, Hyperf, ThinkPHP, and native PHP. That means a shared core with per-framework adapters. Two implications for a TPM:
fix(rules) item is evidence: the validation layer's notion of the cipher could disagree with the runtime encrypter's notion, which is the classic precursor to "writes succeed, reads throw." That it was caught and fixed is reassuring; that it existed at all should shape how much weight we place on the non-Laravel adapters in our own testing.Integration feasibility
Low friction on the happy path: Composer install, publish config, add the trait/cast to a model, done. The documented Encryption::db()->encrypt() entry point gives an imperative escape hatch for raw queries, seeders, and one-off scripts — useful, but also a second surface that must be held to the same standards as the model layer.
Two constraints deserve early attention:
previous_keys with base64: support. This release aligns secondary-key parsing with primary-key parsing. It is a genuine quality-of-life fix for rotation, but it also confirms that key rotation is a manual, operator-driven workflow: you supply prior keys in config. There is no evidence in these notes of a re-encryption command, a key-version envelope per ciphertext, or KMS integration. Rotation is therefore a config-hygiene ritual with a long tail of retained keys — plan for that operationally rather than treating it as solved.Technical risk
Encryption is a maximum-blast-radius dependency: a bug is not a broken page, it is unrecoverable data. Three risks I would put in front of the architecture review:
fix(crypto) and fix(rules) items together show that key format and cipher source of truth have both been sources of defects in recent history. The critical unanswered question is whether the cipher used for a given ciphertext is recorded alongside the value or inferred from current config. If it is inferred, then changing APP_CIPHER (AES-256-CBC ↔ AES-256-GCM) makes existing rows unreadable and there is no recovery path. This must be answered before adoption.Key questions for the maintainer / spike
APP_CIPHER or APP_KEY without a maintenance window?null, or silently pass through? Do exception messages or logs ever include plaintext or key material?WHERE/ORDER BY on these fields simply off the table?previous_keys?Stack fit
Drop-in for Eloquent models on our current Laravel version, subject to the PHP/Laravel matrix check above. The realistic adoption boundary is column-scoped: encrypt a small, enumerated set of regulated fields (government IDs, bank details, tokens, health markers) and leave everything else plaintext. Whole-model encryption would be a mistake — it destroys index usability and query ergonomics across the entire schema.
Migration path
The backfill is the project, not the install. Sequence:
isEncrypted() lightweight format check exists precisely for this — it lets you branch without exception-driven control flow).Critically: the key must be in place and backed up before step 2, and the backfill must be idempotent, because a partially encrypted column with a lost key is unrecoverable.
Compatibility
varchar sizing, index, or unique constraint on a target column needs review before backfill; a silent truncation in non-strict MySQL would be catastrophic.fix(rules) change is a good sign, but it is the exact seam to test.base64: prefix handling on previous_keys should be rolled out with a rotation rehearsal, not as an assumption.Sequencing
Spike (2–3 days: round-trip, cipher change behaviour, failure modes, Laravel-version verification) → security/architecture review against the key questions → pilot on one low-traffic table in staging → production pilot on one non-critical column with a soak period → broaden field-by-field. Gate the whole programme on "we can restore a backup and decrypt it with a key we retrieved from cold storage."
Maintenance
Ongoing cost is low in code terms — a pinned Composer dependency, infrequent upgrades — but the key material is now a tier-0 asset. That means: keys in a managed secret store, not .env on a VM; an escrowed offline copy; an owner; a documented rotation cadence; and retention for old keys that outlives the data retention policy (you cannot delete a key while ciphertext encrypted with it still exists). This is a process cost that will outlive whoever writes the initial integration.
Support
Upstream support is a single-maintainer, community-time project. Expect reasonable responsiveness and no SLA. Mitigate with an internal README that documents our pinned version, our key inventory, and our own encrypt/decrypt/rotate regression suite, so that a future maintainer change or abandonment is survivable.
Scaling
Failure modes
| Mode | Impact | Mitigation |
|---|---|---|
| Encryption key lost / not escrowed | Permanent data loss on affected columns | Cold-storage escrow, restore drills, key owner named |
| Cipher changed without re-encrypt | Reads fail across all encrypted rows | Confirm per-ciphertext cipher metadata, or freeze cipher config |
previous_keys pruned too early |
Old rows unreadable | Retention policy tied to backfill completeness verification |
| Backfill partial / interrupted | Mixed plaintext+ciphertext state | Read-tolerant rollout, idempotent batched job with checkpointing |
| Column width / truncation | Silent corruption | Pre-flight schema review on all target columns |
| Non-Laravel service misconfigured | Cross-service decryption failure on shared tables | Shared config contract, integration test per runtime |
base64: prefix mismatch on secondary keys |
Decrypt failure during rotation | Rotation rehearsal in staging before any production key change |
Ramp-up
Half a day for a Laravel-fluent engineer to get encrypt/decrypt working on one column in a branch. One to two weeks to a production-safe first column once backfill tooling, schema review, key escrow, and the regression suite are included. The dependency itself is cheap; the key-management programme around it is the actual project, and it should be scoped and owned as such before the first pull request lands.
How can I help you explore Laravel packages today?