rinzler / user-encryption
User asymmetric encryption wrapper using private and public keys for Crypton.
Requires
- php: ^8.2
- laravel/framework: ^11.0|^12.0
- paragonie/halite: ^5.1
- paragonie/pqcrypto_compat: ^0.3
- paragonie/sodium_compat: ^2
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-03 23:38:47 UTC
README
Laravel package for per-user asymmetric encryption: legacy RSA-OAEP or ML-KEM-768 wraps the Halite / libsodium encryption secret; user content is sealed with Crypto::seal and unsealed with the user’s secret (typically from a session cookie after unlock).
Requires: PHP ^8.2, Laravel ^11 or ^12, paragonie/halite ^5.1, paragonie/pqcrypto_compat, paragonie/sodium_compat, and the host app’s App\User model with an encryption() relation and a uuid attribute (see tests/Stubs/App/User.php). Version is defined in composer.json.
Demo app (demo/)
A self-contained Laravel skeleton lives under demo/. It Composer-links the parent package from .. so you can run create / decrypt in a browser without installing from GitLab.
cd demo
composer install
cp .env.example .env && php artisan key:generate
touch database/database.sqlite
php artisan migrate --seed
php artisan serve
Package migrations load from ../src/migrations (no vendor:publish required for the schema). Optionally publish config: php artisan vendor:publish --tag=userencryption.
See demo/README.md for details and caveats (auto-login is dev-only).
Installation
composer require rinzler/user-encryption
Publish and run migrations:
php artisan vendor:publish --tag=userencryption
php artisan migrate
Register the service provider if discovery is disabled:
Rinzler\UserEncryption\UserEncryptionServiceProvider::class,
Routes default to web, auth, and throttle:30,1 (30 attempts per minute per IP). Hosts can append extra middleware (for example 2FA) via config('user_encryption.route_middleware') after publishing config. This package does not ship host-specific aliases.
Routes
| Method | URI | Route name |
|---|---|---|
| POST | /encryption/new | user-encryption.new |
| POST | /encryption/decrypt | user-encryption.decrypt |
new— First-time create only: generate key material, wrap it with the passphrase (ML-KEM-768 or RSA-OAEP), store oneuser_encryptionrow, set the unlock cookie. If a row already exists for the authenticated user, the request fails closed (validation error) and does not replace keys or recovery wrap columns.decrypt— Submit passphrase to unlock the existing master key and set the same style of cookie.
Use the default web stack (session, CSRF) from the browser. Unlock cookies use HttpOnly, Secure when config('session.secure') is true, and SameSite when your Laravel cookie jar supports session.same_site.
Database
Table user_encryption (after migrate): belongs_to (unique), rsa_master_private_key, rsa_master_public_key (legacy RSA-OAEP PEM material; empty for ML-KEM-only rows), encryption_private_key, encryption_public_key, timestamps. The unique index on belongs_to is skipped if duplicate rows already exist.
Main API
- Facade / container:
UserEncryption→Rinzler\UserEncryption\Support\UserEncryption(seeRinzler\UserEncryption\Facades\UserEncryption). Rinzler\UserEncryption\Support\UserEncryptiongenerateEncryptionKey($passphrase)— throwsRuntimeExceptionif OpenSSL key generation fails.generateAESKey()— Halite encryption keypair (hex-encoded material in the returned array; name is historical).encryptAESKey/decryptAESKey— RSA-OAEP wrap/unwrap of the Halite secret (falseon failure).encryptString($data, $publicKeyString)/decryptString($data)— Halite seal/unseal;decryptStringreturns a plain string and may throwHttpResponseExceptionfor redirects when the unlock cookie is missing or invalid.
See src/ for implementation details.
Tests
From a clone of this repo (dev dependencies include Orchestra Testbench 9 and PHPUnit 11):
composer install
composer test
Tests use Orchestra Testbench with an in-memory SQLite database and a test-only App\User under tests/Stubs/App/ (your production app supplies the real App\User).
Security
This package handles high-value secrets (keys in the DB, material in cookies). Use HTTPS in production (session.secure / APP_URL), keep a strong passphrase policy in your forms if you need more than required, and review your overall threat model (XSS, DB access, backups).
POST /encryption/new is not a rotation endpoint. Existing keys are never deleted or replaced there; a second create fails closed so ciphertext sealed to the current public key stays decryptable. Host-level rotation (re-encrypting conversations, contacts, messages) is out of scope for this package.
License
GNU General Public License v3.0 or later (SPDX: GPL-3.0-or-later).