Kolom password adalah aset paling berbahaya di seluruh database: pengguna nyaris selalu memakai password yang sama lintas layanan, sehingga satu database bocor bisa menjatuhkan ratusan akun lain. Karena itu aturannya mutlak — plaintext tidak boleh disimpan, titik. Password harus melewati fungsi hash satu arah: mudah dihitung dari plaintext, mustahil dibalik dari hash. Laravel membungkus praktik ini dalam facade Hash dengan bcrypt sebagai algoritma default. Chapter ini membedah cara kerjanya lengkap dengan bukti nyata dari setiap pemanggilan.
Mengapa Bukan MD5 atau SHA-1 #
MD5 dan SHA-1 memang fungsi hash, tetapi keduanya dirancang untuk cepat — dan justru di situlah masalahnya. GPU modern bisa menghitung miliaran hash MD5 per detik, sehingga penyerang yang membawa hasil kebocoran cukup menebak setiap kandidat password lalu mencocokkan hasilnya. Hash semacam itu juga menghasilkan nilai yang sama untuk password yang sama, sehingga semua pengguna yang berbagi password populer langsung tertandai lewat tabel rainbow table yang sudah tersedia di mana-mana.
Password hashing menuntut kebalikannya: algoritma yang sengaja lambat dan menghasilkan hash berbeda untuk input yang sama. Bcrypt memenuhi keduanya lewat cost (jumlah putaran komputasi yang bisa dinaikkan seiring hardware makin cepat) dan salt (nilai acak yang dicampur ke setiap hash).
Hash::make dan Salt Acak #
Facade Hash membuat hashing password menjadi satu baris kode. Kita buktikan perilakunya dengan runner kecil:
use Illuminate\Support\Facades\Hash;
$a = Hash::make('rahasia-kuat');
$b = Hash::make('rahasia-kuat');
echo "hash pertama : $a\n";
echo "hash kedua : $b\n";Output yang Dihasilkan:
hash pertama : $2y$12$biF6KjL1TSbnNAiEGUCBJ.5PHQizCHGnWvYv3NRs0KPysj/oMN9hC
hash kedua : $2y$12$XU0SvjV8JnApbxNN2CMkOOFq2WjobGpqG2J/UCM0yJbmeOcX9Zoy2Dua password yang sama menghasilkan dua hash yang sama sekali berbeda — dan hasil di atas akan berbeda lagi bila kode tersebut dijalankan ulang, karena bcrypt memilih salt acak pada setiap pemanggilan. Struktur hash-nya bisa dibaca dari tanda $: $2y$ menandai algoritma bcrypt, angka 12 adalah cost, lalu 22 karakter setelahnya adalah salt dan 31 karakter terakhir adalah hash password. Salt tersimpan di dalam string hash, sehingga tidak ada kolom salt terpisah yang perlu dikelola.
Memverifikasi dengan Hash::check #
Karena setiap hash berbeda, verifikasi tidak bisa membandingkan string — bcrypt justru menghitung ulang hash password yang diketik dengan salt dari hash tersimpan, lalu membandingkan hasil akhirnya:
echo 'check benar : '.var_export(Hash::check('rahasia-kuat', $a), true)."\n";
echo 'check salah : '.var_export(Hash::check('password-salah', $a), true)."\n";Output yang Dihasilkan:
check benar : true
check salah : falseInilah jalur yang dipakai Auth::attempt di chapter sebelumnya: password dari form login tidak pernah dibandingkan sebagai plaintext, melainkan diverifikasi lewat Hash::check terhadap hash di database.
Hash::info dan Hash::needsRehash #
Dua method pelengkap membantu mengawasi kualitas hash yang tersimpan. Hash::info membongkar metadata sebuah hash, dan Hash::needsRehash menanyakan apakah hash tersebut dibuat dengan parameter lebih lemah dari konfigurasi sekarang:
print_r(Hash::info($a));
echo 'rehash rounds=12 : '.var_export(Hash::needsRehash($a, ['rounds' => 12]), true)."\n";
echo 'rehash rounds=15 : '.var_export(Hash::needsRehash($a, ['rounds' => 15]), true)."\n";Output yang Dihasilkan:
Array
(
[algo] => 2y
[algoName] => bcrypt
[options] => Array
(
[cost] => 12
)
)
rehash rounds=12 : false
rehash rounds=15 : trueHash tersebut beralgoritma bcrypt dengan cost 12. Karena itu needsRehash terhadap rounds 12 mengembalikan false — hash sudah sesuai target — sedangkan terhadap rounds 15 mengembalikan true. Skenario nyatanya: hardware server baru mengizinkan cost lebih tinggi, lalu kita ingin meng-upgrade seluruh hash lama. Hash lama tidak bisa diubah tanpa plaintext-nya, jadi rehash yang benar dilakukan saat user berikutnya login: verifikasi dengan cost lama, lalu simpan ulang hasil Hash::make dengan cost baru. Laravel 13 bahkan menyalakan opsi rehash_on_login secara default sehingga peningkatan cost terjadi otomatis di setiap login yang sukses.
Driver dan Konfigurasi #
Aplikasi baru Laravel 13 tidak lagi menyertakan file config/hashing.php; nilai defaultnya (bcrypt, rounds 12) hidup di dalam framework. Konfigurasi itu bisa kita terbitkan kapan pun:
php artisan config:publish hashingOutput yang Dihasilkan:
INFO Published 'hashing' configuration file. File yang dihasilkan memperlihatkan seluruh kenop yang tersedia:
return [
'driver' => env('HASH_DRIVER', 'bcrypt'),
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12),
'verify' => env('HASH_VERIFY', true),
'limit' => env('BCRYPT_LIMIT', null),
],
'argon' => [
'memory' => env('ARGON_MEMORY', 65536),
'threads' => env('ARGON_THREADS', 1),
'time' => env('ARGON_TIME', 4),
'verify' => env('HASH_VERIFY', true),
],
'rehash_on_login' => true,
];Melalui HASH_DRIVER kita bisa berpindah ke argon2id hanya dengan environment variable — tanpa mengubah satu baris kode aplikasi, karena semua panggilan Hash::make dan Hash::check otomatis mengikuti driver aktif. Driver argon mengonsumsi memori besar secara sengaja (memory 65536 KB × iterasi time 4) agar serangan perangkat keras mahal. Satu catatan jujur dari lingkungan Course ini: PHP pada sandbox kita dikompilasi tanpa dukungan argon2id, sehingga driver tersebut tidak bisa dijalankan di sini — bcrypt tetap aman dan memadai untuk aplikasi nyata.
Cast hashed pada Model User #
Model User bawaan membawa satu penyederhanaan menarik: cast 'password' => 'hashed' pada method casts(), yang membuat assign plaintext langsung di-hash otomatis. Kita buktikan kedua sisinya:
$user = new User;
$user->password = 'rahasia-kuat';
$disimpan = $user->getAttributes()['password'];
echo 'setelah assign plaintext : '.substr($disimpan, 0, 30)."\n";
echo 'check plaintext : '.var_export(Hash::check('rahasia-kuat', $disimpan), true)."\n";
$hash = Hash::make('rahasia-kuat');
$user2 = new User;
$user2->password = $hash;
$disimpan2 = $user2->getAttributes()['password'];
echo 'setelah assign hash : '.($disimpan2 === $hash ? 'identik (tidak di-hash ulang)' : 'BERUBAH (di-hash ulang!)')."\n";Output yang Dihasilkan:
setelah assign plaintext : $2y$12$tgTpLOQHI/jd.rkqqGVF1.H
check plaintext : true
setelah assign hash : identik (tidak di-hash ulang)Assign plaintext menghasilkan hash bcrypt (terbukti lolos Hash::check), sementara assign hash yang sudah jadi disimpan apa adanya — cast mendeteksi string $2y$... dan menolak meng-hash dua kali. Kebiasaan eksplisit Hash::make di controller registrasi dan cast otomatis di model pun dijamin tidak saling bertabrakan.
Dengan hashing yang tertangani, rantai authentication pokok kita sudah lengkap — kecuali satu pintu darurat: pengguna yang lupa password sama sekali tidak bisa lewat Hash::check mana pun. Untuk itulah Laravel menyediakan alur reset password berbasis token yang dikirim lewat email, yang kita bangun di chapter berikutnya. Seluruh perilaku facade Hash terdokumentasi resmi di dokumentasi Hashing
Laravel.