Topic 38 of 75·M16
Encryption & Hashing
Senior-levelthe difference · one-way hashing vs reversible encryption · `Hash::make()` · `Crypt::encryptString()`
The one-sentence answer
Hashing is one-way — password → hash, irreversible — for verifying without storing; encryption is reversible with a key — data → ciphertext → data — for storing data you need back.
The mental model
The checklist's diagram:
HASHING (one-way)
password ──► hash (can never go back)
verify: hash(input) === stored hash
ENCRYPTION (reversible with a key)
data ──► encrypted data ──► original data
(with the key, you get the data back)
The decision is do you ever need the value back?
| Hashing | Encryption | |
|---|---|---|
| Direction | One-way — irreversible | Two-way — reversible with a key |
| Purpose | Verify without storing | Store data you'll read later |
| Laravel | Hash::make() / Hash::check() | Crypt::encryptString() / Crypt::decryptString() |
| Example | Passwords | API secrets, PII at rest |
| Leak result | Attacker gets hashes (not usable as passwords) | Attacker with the key gets the data |
How it works
Hashing — passwords
// on write — never store plaintext:
User::create(['password' => Hash::make($request->password)]);
// on login — verify against the stored hash:
if (Hash::check($request->password, $user->password)) {
// correct — Auth::attempt() does exactly this
}
- The hash is one-way: you can verify a candidate, but you can't reverse the stored hash into the password.
- bcrypt/argon2 by default — salted and slow by design (slow hashing is the point; it makes brute-force expensive).
Hash::check()re-hashes the input and compares — constant-time, salted.
Encryption — data you need back
// on write — the value is stored encrypted:
$encrypted = Crypt::encryptString($apiKey); // → ciphertext in the DB
DB::table('integrations')->update(['secret' => $encrypted]);
// on read — decrypt with the app key:
$apiKey = Crypt::decryptString($row->secret);
- Reversible with the app's key (
APP_KEY— the key from.env, Lesson 37). - Use for data you must retrieve and that shouldn't sit in plaintext at rest: third-party API keys, tokens, PII where required.
- Laravel's
cast'encrypted'does this per-attribute:
protected $casts = [
'stripe_secret' => 'encrypted', // encrypted at rest, decrypted on read, automatically
];
The key difference — what a leak means
| If the DB leaks | You need the key to… | |
|---|---|---|
| Hashed password | Attacker can't log in as the user | — (they'd brute-force the hash) |
| Encrypted secret | Attacker gets ciphertext | Have APP_KEY to decrypt it |
That's why APP_KEY is the crown jewel: anyone with it can decrypt everything Crypt-encrypted.
Interview questions
Q1. What's the difference between hashing and encryption?
Hashing is one-way — irreversible by design; you verify a candidate against the hash but can't recover the original. Encryption is two-way — reversible with the key; you can store ciphertext and read the original back. Passwords get hashed (never need them back); secrets and PII get encrypted (need them back).
Q2. Why can't you store passwords encrypted?
Because you never need the password back — you only need to verify it. If you encrypted it, anyone with
APP_KEYcould recover every password; a hashed password is useless to an attacker even with the app key. Also, encryption invites "decrypt and show me" features that shouldn't exist for passwords.
Q3. What does Hash::make do, and why is it slow?
It runs the password through a salted, computationally expensive hash (bcrypt/argon2). The salt makes identical passwords hash differently; the cost makes brute-force attempts expensive per guess.
Hash::checkverifies a candidate. "Slow by design" is a feature, not a bug — speed is the attacker's friend.
Q4. When do you use Crypt::encryptString?
For data you must store and retrieve later that shouldn't be plaintext at rest — third-party API keys, tokens, sensitive PII. Write
Crypt::encryptString, readCrypt::decryptString(or cast the attribute'encrypted'). The app key is the decrypt key, so key security is the security.
Q5. What is APP_KEY and why does it matter?
The key Laravel uses for all
Cryptoperations — encryption, and parts of sessions/cookies/signed URLs. It's in.env, generated byphp artisan key:generate. Anyone with it can decrypt everything encrypted with it, so it's the crown jewel: never committed, never shared, rotated carefully (rotating it invalidates old ciphertext).
Senior follow-up: How do you handle key rotation with encrypted data?
The honest answer: rotating
APP_KEYbreaks existing ciphertext, so plan for it — re-encrypt on read with the new key (read with old, write with new), or version your keys (key id prefix + a key ring, like AWS KMS's envelope encryption). The senior point is that encryption is a system — key storage, rotation, and the leak response are part of the design, not an afterthought.
Common mistakes
❌ Encrypting passwords — the reversible mistake; hashing is for verification.
❌ Storing plaintext API keys/PII when Crypt/'encrypted' cast exists.
❌ Committing APP_KEY — it's the decryption key for everything.
❌ Hashing things you need back — a hashed order ID can't be recovered; use encryption or a nonce table.
Quick revision notes
- Hashing = one-way (
Hash::make/Hash::check) — passwords, verification - Encryption = two-way (
Crypt::encryptString/decryptString,'encrypted'cast) — secrets, PII - Slow salted hashing is by design (brute-force cost)
APP_KEY= the decrypt key — the crown jewel, never committed- Decision rule: need it back? → encrypt · just verify? → hash
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/5Why is hashing one-way, and why is that a feature?
When is encryption the right tool instead?
What does Hash::check actually do?
Why is APP_KEY the most sensitive value in the app?
What breaks when you rotate the key, and how do you plan for it?