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

Encryptable Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

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:

  1. Portability upside — if the roadmap includes migrating off Laravel (or the estate already runs Hyperf/Webman services against the same schema), a single encryption implementation that behaves identically across runtimes is materially better than reimplementing crypto per service. Cross-service decryption compatibility is the hard part of any key-management story, and this package is explicitly optimising for it.
  2. Divergence risk — multiple integration codepaths for a crypto library is exactly where subtle inconsistency breeds. The 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:

  • PHP 8.0+ baseline. Laravel 9 requires PHP 8.0, Laravel 10 requires 8.1, Laravel 11 requires 8.2. A package floor of 8.0 does not prevent use on 10/11, but it tells us the CI matrix is anchored to an older runtime and we should not assume Laravel 11/12 behaviour for things like the encrypter contract, config resolution, or model serialisation is covered by upstream tests. Verify explicitly against our target Laravel version before committing.
  • 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:

  1. Key and cipher lifecycle. The 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.
  2. Version 2.x implies a prior breaking change. These are patch releases, but we are arriving at 2.0.13 with no history in hand. Whatever 1.x → 2.x changed (envelope format, trait name, config shape) determines whether existing encrypted data anywhere in the estate is still readable.
  3. Maintainership concentration. A crypto package under a single-maintainer GitHub org is a supply-chain and continuity concern. Not disqualifying — many fine packages look like this — but it argues for our own regression tests around encrypt/decrypt/rotate rather than reliance on upstream.

Key questions for the maintainer / spike

  • Is the cipher and key identifier persisted per ciphertext, or derived from configuration at read time? Can we change APP_CIPHER or APP_KEY without a maintenance window?
  • Is authenticated encryption (GCM) the default, and what is the fallback for CBC? Is there a MAC, and is it compared in constant time?
  • Does decrypt failure throw, return null, or silently pass through? Do exception messages or logs ever include plaintext or key material?
  • Is there a supported re-encryption/backfill command, or must we write our own?
  • What is the documented 1.x → 2.x breaking-change list, and is a migration path provided for previously encrypted rows?
  • Anything for querying encrypted columns — deterministic encryption, blind index, hash column — or are WHERE/ORDER BY on these fields simply off the table?
  • What decryption support window applies after a key is removed from previous_keys?

Integration Approach

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:

  1. Add the column-level trait but first deploy a read-tolerant state: accept both plaintext and ciphertext (the newly documented isEncrypted() lightweight format check exists precisely for this — it lets you branch without exception-driven control flow).
  2. Backfill existing rows in bounded batches, ideally online, with a checkpoint so a failure resumes rather than restarts.
  3. Verify by dual-read comparison in staging on a full production-shaped dataset — not a sample.
  4. Flip writes to encrypt-always, keep reads tolerant for a defined soak period, then tighten.

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

  • Schema: ciphertext is longer than plaintext. Any varchar sizing, index, or unique constraint on a target column needs review before backfill; a silent truncation in non-strict MySQL would be catastrophic.
  • Indexes and unique constraints on encrypted columns stop functioning as intended unless the package offers a deterministic/hash-index mode. Enumerate every query that filters or sorts on a target column.
  • Cross-runtime: if Hyperf/Webman services touch the same tables, confirm they resolve the same encrypter and key material. The 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."

Operational Impact

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

  • Per-row crypto is CPU cost on every read and write of a target column. For high-throughput paths, benchmark on production-shaped payload sizes before committing; typically fine, but not free, and it stacks with any ORM hydration overhead.
  • Large backfills compete with production I/O. Batch sizing and off-peak scheduling matter more than raw throughput.
  • Loss of index pushdown is the real scaling risk: any query that previously filtered on an encrypted column either degrades to a full scan plus in-PHP filtering or stops working. Inventory these before the decision, not after.

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.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource