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

Hashing Laravel Package

illuminate/hashing

Laravel's hashing component provides secure password hashing through Bcrypt and Argon2 drivers, a simple Hash facade for make, check, and rehash operations, configurable work factors, and automatic algorithm verification to keep stored credentials safe across your PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

If you're in a Laravel app, you already have this package — laravel/framework replaces illuminate/hashing, so composer require illuminate/hashing is a no-op at best and a version conflict at worst. Just use the facade:

use Illuminate\Support\Facades\Hash;

$hash    = Hash::make('correct horse battery staple'); // bcrypt, 12 rounds by default
$ok      = Hash::check($request->password, $user->password);
$stale   = Hash::needsRehash($user->password);
$meta    = Hash::info($user->password); // ['algo' => ..., 'algoName' => 'bcrypt', 'options' => ['cost' => 12]]

Outside Laravel, install the split package plus its support packages (illuminate/container, illuminate/config, illuminate/support) and either bootstrap a container that has a config repository bound before constructing HashManager, or — simpler — skip the manager entirely and instantiate the concrete hasher:

use Illuminate\Hashing\BcryptHasher;

$hasher = new BcryptHasher(['rounds' => 12]);
$hasher->make($plain); $hasher->check($plain, $stored);

Where to look first, in order:

  1. Illuminate\Hashing\HashManager — driver resolution, driver(), extend(), getDefaultDriver().
  2. BcryptHasher, ArgonHasher, Argon2IdHasher — the actual options (rounds, memory, threads, time) and the verifyAlgorithm behaviour.
  3. config/hashing.php (or the framework's merged defaults) — driver, bcrypt.rounds, argon.*.
  4. Illuminate\Contracts\Hashing\Hasher — the four methods worth knowing: make, check, needsRehash, info.

First use case: a registration or password-change endpoint. Write Hash::make() on the way in, Hash::check() on the way out, nothing else. Don't roll your own salt, don't wrap it, don't md5 anything first.

Implementation Patterns

The make / check / needsRehash triad. This is 90% of daily usage. The upgrade path is the fourth line in the triad:

if (Hash::check($plain, $user->password)) {
    if (Hash::needsRehash($user->password)) {
        $user->forceFill(['password' => Hash::make($plain)])->save();
    }
}

Be aware Laravel's own EloquentUserProvider::validateCredentials() already does this rehash-on-login for you, so don't duplicate it in a custom attempt() wrapper unless you've replaced the user provider.

Pick a driver per call, not globally. Hash::driver('argon2id')->check(...) lets you verify legacy bcrypt hashes while writing new ones as Argon2id — the migration pattern without a flag day:

$stored = $user->password;
$valid  = str_starts_with($stored, '$2y$')
    ? Hash::driver('bcrypt')->check($plain, $stored)
    : Hash::check($plain, $stored);

if ($valid && Hash::needsRehash($stored)) { /* rehash with the new default */ }

Inject the contract, not the facade. For services, queue jobs, and anything you want to unit-test without hitting 12 rounds of bcrypt:

use Illuminate\Contracts\Hashing\Hasher;

final class PasswordService
{
    public function __construct(private Hasher $hasher) {}

    public function hash(string $plain): string { return $this->hasher->make($plain); }
}

In tests, bind a cheap fake or drop the cost: BCRYPT_ROUNDS=4 in phpunit.xml is the single biggest test-suite speedup you'll get in a Laravel app. Or $this->instance(Hasher::class, $fake) / Hash::shouldReceive('make')->andReturn('...').

Tune cost per environment, not per codebase. BCRYPT_ROUNDS (or argon.memory/argon.time) belongs in .env. Target ~100–250ms per hash on production hardware; measure with Hash::make() in a tinker loop before you ship. Argon2id is memory-hard — check your container's RAM limit, not just the CPU.

Normalize login timing. On "user not found", still run a check against a fixed dummy hash so an attacker can't distinguish unknown-email from wrong-password by response time:

$hash = $user?->password ?? Hash::make('dummy-password-for-timing');
$valid = Hash::check($plain, $hash) && $user !== null;

Don't use this package for API tokens. Bcrypt/Argon are deliberately slow and bcrypt caps at 72 bytes. For high-entropy random tokens use hash('sha256', $token) and compare with hash_equals(). Reserve Hash for low-entropy human secrets.

Custom drivers go in a service provider's register(), before anything resolves the hasher:

Hash::extend('pepper', fn ($app) => new PepperedHasher($app['config']['hashing.pepper']));

Gotchas and Tips

  • Never compare hashes with === or in SQL. Same password, different hash every time (per-hash salt). Hash::check() or nothing.
  • Switching HASH_DRIVER doesn't return false — it throws. Each hasher verifies the stored hash's algorithm prefix and raises RuntimeException('This password does not use the Bcrypt algorithm.'). A driver change in .env will 500 every existing user's login unless you route by prefix (see the migration pattern above) or migrate all hashes first.
  • Bcrypt silently truncates at 72 bytes. Long passphrases, or a maliciously huge password, get chopped. Keep a max:255 (ideally max:72) validation rule on password fields and prefer argon2id if you want to allow arbitrarily long secrets.
  • needsRehash() goes true when you raise the cost. That's the intended upgrade mechanism, but it means a config bump triggers a write on every login for a while. On a hot table this is real write amplification — roll it out deliberately, or rehash via a backfill job.
  • Argon2 availability is a runtime property. argon2id needs a PHP build with Argon2 support; guard with defined('PASSWORD_ARGON2ID') and fail loudly at deploy time rather than on the first user signup.
  • config:cache freezes HASH_DRIVER/BCRYPT_ROUNDS. Changing .env on a cached production app does nothing until you re-run php artisan config:cache. Classic "why is it still 10 rounds" bug.
  • Laravel 11+ ships no config/hashing.php by default. Framework defaults are merged in, and they read the env vars, so HASH_DRIVER still works. Publish the file only when you need values env vars can't express.
  • Hash::info() is driver-agnostic. It wraps password_get_info(), so you can audit which algorithm each row uses even when your default driver doesn't match — handy for a migration dashboard or a one-off php artisan report.
  • Don't put plaintext passwords in queued jobs, logs, or exception payloads. Hashing is fast to call from a job; serializing the secret across a queue/worker boundary is not worth it.
  • Beware double-hashing. A listener on User::saving that calls Hash::make() will re-hash an already-hashed value from a mass update. Use $user->isDirty('password') and hash explicitly at the edge (RegisterController, PasswordController), or cast consistently.
  • Extension point of last resort: HashManager::extend() is stable and supported, but this is a subtree-split package pinned to framework versions — keep any composer.json constraint on illuminate/hashing aligned with your laravel/framework major (^11.0), and expect zero releases outside Laravel's cadence.
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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager