laminas/laminas-crypt
Laminas Crypt offers cryptographic tools for PHP applications: symmetric encryption, block ciphers, secure password hashing, key derivation, and public-key/hybrid encryption helpers. It provides a consistent, framework-agnostic API within the Laminas ecosystem.
laminas/laminas-crypt is a low-level PHP cryptography library, not a Laravel package: there is no service provider, config file, facade, or auto-discovery. Treat it as a legacy dependency you isolate behind an adapter.
Minimal setup:
composer require laminas/laminas-crypt
Confirm your PHP build has ext-openssl (required for BlockCipher, RSA, and Hybrid). Argon2 support depends on how PHP was compiled; check defined('PASSWORD_ARGON2I') before using Laminas\Crypt\Password\Argon2.
Where to look first:
src/Password/Bcrypt.php and src/Password/Argon2.php — password hashing and verification.src/BlockCipher.php and src/Symmetric/Openssl.php — symmetric encryption adapters.src/PublicKey/Rsa.php, src/PublicKey/RsaOptions.php, and src/Hybrid.php — asymmetric and hybrid encryption.src/FileCipher.php — file encryption helpers.test/ — the most reliable usage examples, since documentation may lag the archived code.First real use case is usually one of two things: verifying passwords created by a pre-Laravel application, or decrypting data that was written by an older service. Start with the smallest possible operation and validate against existing data before writing anything.
use Laminas\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt();
$bcrypt->setCost(12);
$hash = $bcrypt->create('plain-password');
if ($bcrypt->verify('plain-password', $hash)) {
// valid
}
Note the argument order: verify($password, $hash), not the reverse.
Bind configured instances in a service provider. Avoid constructing ciphers ad hoc; centralize configuration so cipher, mode, key iteration, and hash settings cannot drift between encrypt and decrypt code.
use Laminas\Crypt\BlockCipher;
$this->app->singleton(BlockCipher::class, function () {
$cipher = BlockCipher::factory('openssl', [
'cipher' => 'aes',
'mode' => 'cbc',
]);
$cipher->setKey(config('legacy.crypto_key'));
return $cipher;
});
Wrap it in your own interface (LegacyCipher) so application code does not import Laminas\Crypt directly. That makes the eventual migration to Laravel Crypt or Sodium a one-file change.
Migrate legacy password hashes on login. This is the most common day-to-day reason to keep the package installed.
use Illuminate\Support\Facades\Hash;
use Laminas\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt();
$bcrypt->setCost(12);
if ($user->legacy_password_hash
&& $bcrypt->verify($request->input('password'), $user->legacy_password_hash)) {
$user->password = Hash::make($request->input('password'));
$user->legacy_password_hash = null;
$user->save();
}
Do the rehash inside a transaction or immediately after successful authentication, and keep Hash::needsRehash() in mind for Laravel-native hashes.
Use an adapter plus a migration command for encrypted columns. Read old values with BlockCipher::decrypt(), write new values with Crypt::encryptString(), and process rows with chunkById() so memory stays bounded.
$cipher = app(BlockCipher::class);
User::query()->whereNotNull('legacy_secret')->chunkById(500, function ($users) use ($cipher) {
foreach ($users as $user) {
$plain = $cipher->decrypt($user->legacy_secret);
$user->secret = Crypt::encryptString($plain);
$user->legacy_secret = null;
$user->save();
}
});
Run this with a backup, a dry-run mode, and logging for rows that fail to decrypt.
Use Hybrid for payloads exchanged with external systems. RSA alone cannot encrypt large payloads. Laminas\Crypt\Hybrid encrypts a random symmetric key with RSA and the payload with that key.
use Laminas\Crypt\Hybrid;
$hybrid = new Hybrid();
$hybrid->setPublicKey($recipientPublicKey);
$payload = $hybrid->encrypt($json);
$hybrid->setPrivateKey($ourPrivateKey);
$plain = $hybrid->decrypt($payload);
Keep the encrypted output as text (base64) in your database or queue, and never log the plaintext or private key.
Use RSA directly only for small values or signatures. For signing, Rsa::factory([...])->sign($data) and verify($data, $signature) are the relevant calls. For encryption, remember the plaintext size limit is roughly the key size minus padding overhead; a 2048-bit key gives you on the order of a couple hundred bytes, not a whole document.
Use FileCipher for one-off batch jobs. It handles file-sized streaming better than reading the whole file into BlockCipher, and is useful in artisan commands that process imports or exports.
Prefer deterministic salts only in tests. BlockCipher normally generates a random salt per encryption. For deterministic test vectors, call setSalt() explicitly, but never do that in production.
The repository is archived. There will be no security patches for this package. Pin the version in composer.json, run composer audit in CI, and treat it as technical debt with an owner and a removal date. If you are starting fresh, use Laravel's Hash and Crypt facade, or ext-sodium, instead.
Do not use Bcrypt for new Laravel passwords. Laravel's Hash::make() and Hash::check() are the supported path and already choose sensible defaults. laminas-crypt password classes are useful for legacy verification and migration, not as a new application standard.
BlockCipher is configuration-sensitive. Changing the cipher, mode, hash algorithm, key iteration count, salt size, or key makes all previously encrypted data unreadable. Persist the full configuration alongside the ciphertext — not just the key — and version it if you ever rotate settings.
BlockCipher is not automatically AEAD. It provides authentication through its own MAC construction, but if you specifically need AES-GCM with a tag, use PHP's native openssl_encrypt() with OPENSSL_RAW_DATA and the tag output parameter, or Laravel's Crypt. Do not assume mode => 'gcm' gives you the same semantics as a purpose-built AEAD API.
Catch decryption failures explicitly. Bad keys, truncated payloads, or tampered data can throw Laminas\Crypt\Exception\InvalidArgumentException or RuntimeException. In a migration command, log the record ID and continue; in a request path, convert it to a validation error rather than a 500.
Bcrypt::verify() argument order is easy to invert. It is verify($password, $hash). A reversed call will silently return false, which can look like a bad password during migration testing.
Bcrypt has a 72-byte input limit. PHP's password_hash() truncates longer passwords; if your legacy system did not, users with very long passwords may authenticate differently after migration. Normalize and test with representative data.
Argon2 is build-dependent. PASSWORD_ARGON2I may not exist on your host. Guard with defined() and fall back to Bcrypt rather than letting the constructor fail at runtime.
RSA key handling is the usual source of pain. Verify the private key passphrase, file permissions, and PEM format before debugging application code. Keep private keys outside the repository and outside public/. Use binary_output => false unless you have a specific need for raw bytes, and store base64 output as text.
Hybrid output is not a drop-in replacement for Laravel Crypt. It is for asymmetric exchange with another party. For same-application encryption at rest, Laravel's Crypt with APP_KEY is simpler and has a supported upgrade path.
Watch the dependency chain. RSA support historically pulled in laminas/laminas-math and related packages. Check what Composer actually installs, and avoid adding this package to a new project just for one helper function.
Test with real fixtures. Capture a ciphertext and password hash from the legacy system before you change anything, then write a test that decrypts or verifies it. That single fixture will catch configuration drift faster than reading the source.
Isolate and schedule the exit. Put all Laminas\Crypt usage behind one or two classes, add a @deprecated annotation, and track removal. Because the package is archived, the best day-to-day leverage is not adopting it more broadly — it is using it surgically to drain legacy data and hashes out of the system.
How can I help you explore Laravel packages today?