Skip to content

Topic 38 of 75·M16

Encryption & Hashing

Senior-level

the 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?

HashingEncryption
DirectionOne-way — irreversibleTwo-way — reversible with a key
PurposeVerify without storingStore data you'll read later
LaravelHash::make() / Hash::check()Crypt::encryptString() / Crypt::decryptString()
ExamplePasswordsAPI secrets, PII at rest
Leak resultAttacker 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 leaksYou need the key to…
Hashed passwordAttacker can't log in as the user— (they'd brute-force the hash)
Encrypted secretAttacker gets ciphertextHave 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_KEY could 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::check verifies 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, read Crypt::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 Crypt operations — encryption, and parts of sessions/cookies/signed URLs. It's in .env, generated by php 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_KEY breaks 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/5
1

Why is hashing one-way, and why is that a feature?

2

When is encryption the right tool instead?

3

What does Hash::check actually do?

4

Why is APP_KEY the most sensitive value in the app?

5

What breaks when you rotate the key, and how do you plan for it?