Hashing is a one-way scramble you can only check (perfect for passwords), while encryption is a two-way lock you can open with a key (for secrets you need to read again).
๐ฅHashing is like scrambling an egg: you can scramble another egg and compare, but you can never unscramble it. Encryption is like a locked diary: anyone with the key (your APP_KEY) can open it and read the original.
Assume your database will leak one day
Passwords get reused
If you store passwords readably, a leak exposes your users' email and bank accounts too, not just your app.
Right tool, right job
Hash what you only need to verify. Encrypt what you need to read back, like an API token for a third-party service.
One key protects a lot
APP_KEY encrypts your cookies, sessions and encrypted columns. Losing or leaking it has real consequences.
One-way vs two-way
| Hashing | Encryption | |
|---|---|---|
| Reversible? | No | Yes, with the key |
| Laravel API | Hash::make(), Hash::check() | Crypt::encryptString(), Crypt::decryptString() |
| Algorithm | bcrypt (default) or Argon2 | AES-256-CBC + a signature (MAC) |
| Use it for | Passwords, PINs | API tokens, private notes, anything you must display later |
Depends on APP_KEY? | No | Yes |
Protect passwords and secrets the Laravel way
-
Hash a password with
Hash::makebcrypt adds a random salt (extra random characters) to each password before hashing, so two users with the same password get different hashes. It's also deliberately slow, which makes guessing billions of passwords impractical.
use Illuminate\Support\Facades\Hash; $hash = Hash::make('secret123'); // $2y$12$Kp9.... (different every time you run it) -
Verify with
Hash::checkYou can't decrypt a hash, so you check a login attempt by comparing.
Hash::checkre-hashes the attempt using the salt stored inside the hash.if (Hash::check($request->input('password'), $user->password)) { // correct password }In practice
Auth::attempt()does this for you (see Sessions & Guards). You'll useHash::checkdirectly for things like "confirm your current password". -
Let the
'hashed'cast do it automaticallyNew Laravel apps ship with this cast on the
Usermodel. Any plain password you assign is hashed before it's saved, and an already-hashed value is left alone.protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; }User::create([ 'name' => 'Ada', 'email' => 'ada@example.com', 'password' => $request->validated('password'), // hashed by the cast ]); -
Encrypt secrets you need to read back
Say each user connects their own newsletter service and you must store their API key. You need the real value later, so hash won't work. Use the
Cryptfacade.use Illuminate\Support\Facades\Crypt; use Illuminate\Contracts\Encryption\DecryptException; $stored = Crypt::encryptString('sk_live_abc'); try { $apiKey = Crypt::decryptString($stored); } catch (DecryptException $e) { // tampered with, or encrypted with a different APP_KEY }Even simpler: the
'encrypted'cast encrypts on save and decrypts on read.'newsletter_api_key' => 'encrypted',โน๏ธ Column sizeEncrypted values are much longer than the original. Use a
textcolumn for encrypted attributes. -
Look after
APP_KEYAPP_KEYis a random 32-byte key in your.envfile. Laravel uses it for encryption: encrypted casts,Crypt, and encrypted cookies (which carry your session ID).APP_KEY=base64:7v2Jw0b3...44-char-random-string...=php artisan key:generate- Generate it once per environment. Never commit
.envto git. - If it leaks, anyone can decrypt your data and forge cookies. Rotate it.
- If you change it carelessly, existing encrypted data can't be decrypted and everyone is logged out. To rotate safely, put the old key in
APP_PREVIOUS_KEYSso Laravel can still decrypt old values. - Changing
APP_KEYdoes not affect password hashes.
- Generate it once per environment. Never commit
Avoid this, do that
$user->password = Crypt::encryptString($pw);
// or md5($pw), sha1($pw)
Encrypted passwords can be decrypted by anyone with the key, and md5/sha1 are fast enough to brute-force.
$user->password = Hash::make($pw);
// or rely on the 'hashed' cast
A slow, salted one-way hash means even you can't recover the password, and that's the point.
if (Hash::make($input) === $user->password) {
// never true!
}
Each Hash::make uses a new random salt, so the strings never match.
if (Hash::check($input, $user->password)) {
// ...
}
Hash::check reads the salt from the stored hash and compares safely.
The work factor is set by BCRYPT_ROUNDS in .env (default 12). When you raise it, Laravel's login automatically re-hashes each user's password the next time they log in, thanks to Hash::needsRehash().
- In
php artisan tinker, runHash::make('secret')twice and compare the results. - Check that
Hash::check('secret', $hash)returnstruefor both hashes. - Encrypt a string with
Crypt::encryptString, then decrypt it back. - Add a
newsletter_api_keytext column with the'encrypted'cast. Save a value and look at the raw database row. - Confirm your
Usermodel has'password' => 'hashed'incasts().
Key takeaways
- Hashing is one-way: use
Hash::makeandHash::checkfor passwords. - Encryption is two-way: use
Crypt::encryptStringor the'encrypted'cast for secrets you must read back. - The
'hashed'cast hashes passwords automatically when you assign them. - Never store passwords encrypted, plain, or with md5/sha1.
APP_KEYpowers encryption and cookies: keep it secret, back it up, rotate withAPP_PREVIOUS_KEYS.