In one sentence

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.

Why it matters

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.

The big picture

One-way vs two-way

Hashing (one-way) "secret123" Hash::make $2y$12$Kp9...e3Q (60 chars) ๐Ÿšซ There is no way back. To log in, hash the attempt and compare with Hash::check. Encryption (two-way) "sk_live_abc" encrypt ๐Ÿ”‘ decrypt ๐Ÿ”‘ eyJpdiI6Ik1...(uses APP_KEY)
A hash can only be compared. Encrypted text can be turned back into the original, but only with the same key.
HashingEncryption
Reversible?NoYes, with the key
Laravel APIHash::make(), Hash::check()Crypt::encryptString(), Crypt::decryptString()
Algorithmbcrypt (default) or Argon2AES-256-CBC + a signature (MAC)
Use it forPasswords, PINsAPI tokens, private notes, anything you must display later
Depends on APP_KEY?NoYes
Step by step

Protect passwords and secrets the Laravel way

  1. Hash a password with Hash::make

    bcrypt 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)
            
  2. Verify with Hash::check

    You can't decrypt a hash, so you check a login attempt by comparing. Hash::check re-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 use Hash::check directly for things like "confirm your current password".

  3. Let the 'hashed' cast do it automatically

    New Laravel apps ship with this cast on the User model. 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
              ]);
            
  4. 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 Crypt facade.

    
              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 size

    Encrypted values are much longer than the original. Use a text column for encrypted attributes.

  5. Look after APP_KEY

    APP_KEY is a random 32-byte key in your .env file. 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 .env to 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_KEYS so Laravel can still decrypt old values.
    • Changing APP_KEY does not affect password hashes.
Common mistakes

Avoid this, do that

โŒ Avoid

          $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.

โœ… Do

          $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.

โŒ Avoid

          if (Hash::make($input) === $user->password) {
              // never true!
          }
        

Each Hash::make uses a new random salt, so the strings never match.

โœ… Do

          if (Hash::check($input, $user->password)) {
              // ...
          }
        

Hash::check reads the salt from the stored hash and compares safely.

๐Ÿ’ก Pro tip: hashes upgrade themselves

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().

๐Ÿ‹๏ธ Try it yourself
  1. In php artisan tinker, run Hash::make('secret') twice and compare the results.
  2. Check that Hash::check('secret', $hash) returns true for both hashes.
  3. Encrypt a string with Crypt::encryptString, then decrypt it back.
  4. Add a newsletter_api_key text column with the 'encrypted' cast. Save a value and look at the raw database row.
  5. Confirm your User model has 'password' => 'hashed' in casts().
Recap

Key takeaways

Check yourself

Mini quiz