Menu

password_hash() dan password_verify() di PHP

Hash sebuah password dengan password_hash($password, PASSWORD_DEFAULT), simpan hasilnya yang berukuran 60 karakter, lalu periksa login dengan password_verify($password, $hash). Pelajari kenapa hash-nya berubah setiap kali, cost, password_needs_rehash, Argon2id, dan kenapa md5 serta sha1 salah untuk password.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Simpan password dengan password_hash($password, PASSWORD_DEFAULT) dan periksa login dengan password_verify($password, $hash), yang mengembalikan true atau false. Jangan pernah menyimpan password-nya sendiri, dan jangan pernah memakai md5() atau sha1() untuknya.

Jalankan dua kali: hash-nya berbeda setiap kali, dan keduanya tetap lolos verifikasi. Hal yang sama berlaku untuk setiap hash di halaman ini, jadi output-mu tidak akan sama dengan milik orang lain.

Login dengan password_verify

Proses login memeriksa password yang dikirim terhadap hash yang disimpan saat akun dibuat. Blok ini "mendaftarkan" satu user lalu menampilkan form login; coba password yang benar (hunter2), lalu yang salah.

Pesan error-nya sama untuk email yang tidak dikenal dan password yang salah. Memberi tahu mana yang salah akan memberi tahu penyerang email mana yang punya akun. Setelah login berhasil, situs sungguhan menyimpan id user di session, seperti yang ditunjukkan di halaman session.

Apa yang ada di dalam hash

Hash bcrypt adalah satu string yang memuat semua yang dibutuhkan password_verify(): algoritma, cost, dan salt, diikuti hash-nya sendiri. password_get_info() membaca kembali bagian-bagian awalnya.

Karena salt disimpan di dalam hash, kamu hanya butuh satu kolom database, dan dua user dengan password yang sama tetap mendapat hash yang berbeda. Jadikan kolom itu VARCHAR(255): bcrypt berukuran 60 karakter, tetapi PASSWORD_DEFAULT boleh berubah menjadi algoritma dengan output yang lebih panjang.

Memilih cost

Cost adalah seberapa lambat setiap hash dibuat dengan sengaja, dan setiap kenaikan satu tingkat menggandakan pekerjaannya. PHP 8.3 memakai cost 10 untuk bcrypt (PHP 8.4 menaikkan default-nya menjadi 12). Proses login sebaiknya butuh kira-kira 100 sampai 300 ms untuk hashing; ukur waktunya di server-mu sendiri:

Angkanya bergantung pada mesinnya, tetapi setiap baris seharusnya kira-kira dua kali baris sebelumnya. Makin tinggi makin aman terhadap cracking dan makin lambat untuk setiap login, jadi pilih cost tertinggi yang masih sanggup ditanggung halaman login-mu.

Memperbarui hash lama dengan password_needs_rehash

Saat kamu menaikkan cost atau PHP mengubah PASSWORD_DEFAULT, hash yang sudah ada tetap berfungsi, dan password_needs_rehash() memberi tahu hash mana yang sudah usang. Satu-satunya saat kamu memegang password aslinya adalah ketika login, jadi di situlah kamu memperbaruinya:

Prefix $2y$10$ berubah menjadi $2y$12$, dan pada login berikutnya password_needs_rehash() mengembalikan false.

Jangan pernah memakai md5 atau sha1 untuk password

md5() dan sha1() adalah hash umum yang cepat dan tanpa salt. Cepat justru salah untuk password: penyerang yang memegang tabel yang bocor bisa mencoba miliaran tebakan per detik dengan GPU, dan hash tanpa salt dari password yang umum sudah ada di tabel lookup publik. Bandingkan kecepatannya:

Dalam waktu yang dibutuhkan password_hash() untuk membuat satu hash, md5 membuat ratusan ribu, jadi setiap tebakan cracker jadi jauh lebih murah sebanyak itu. Cari 5f4dcc3b5aa765d61d8327deb882cf99 dan kamu akan langsung menemukan kata "password". Hash generator mengubah "password" menjadi md5 yang sama itu setiap kali.

Jika kamu mewarisi tabel berisi hash md5, kamu tidak bisa mengonversinya secara langsung. Bungkus saja: simpan password_hash($oldMd5) dan verifikasi dengan password_verify(md5($input), $stored), lalu ganti masing-masing dengan hash yang normal saat user itu login berikutnya.

Argon2id dan batas 72 byte

PASSWORD_ARGON2ID memakai lebih banyak memori per hash, yang membuat cracking dengan GPU jauh lebih mahal. Algoritma ini tersedia saat PHP di-build dengan libargon2 (sebagian besar paket Linux begitu); periksa dengan defined('PASSWORD_ARGON2ID').

Baris terakhir mencetak bool(true): dua password yang berbeda dianggap cocok karena bcrypt mengabaikan semua yang ada setelah byte ke-72. Password sungguhan jarang sepanjang itu, tetapi passphrase dalam bahasa Jepang atau aksara multibyte lain mencapai 72 byte pada sekitar 24 karakter. Jika kamu mengizinkan password yang sangat panjang, pakai Argon2id, yang tidak punya batas seperti itu. password_verify() bekerja dengan kedua algoritma, jadi kamu bisa beralih dengan password_needs_rehash() seperti di atas.

Pertanyaan yang sering diajukan

Kenapa password_hash memberi hasil yang berbeda setiap kali?

Setiap pemanggilan membuat salt acak yang baru dan menyimpannya di dalam hash, jadi password yang sama menghasilkan string yang berbeda setiap kali. Itu memang disengaja: password_verify() membaca kembali salt dari hash yang tersimpan, jadi fungsi itu tetap mengembalikan true untuk password yang benar.

Berapa panjang kolom password yang dibutuhkan untuk password_hash?

Hash bcrypt dari PASSWORD_DEFAULT saat ini berukuran 60 karakter, tetapi algoritma default-nya bisa berubah di versi PHP mendatang. Pakai VARCHAR(255) agar hash yang lebih panjang tetap muat tanpa migrasi.

Kenapa password_verify selalu mengembalikan false?

Penyebab yang biasa adalah hash yang terpotong (kolom VARCHAR(50) memotong hash yang berukuran 60 karakter), memberikan password_hash() baru dari sesuatu sebagai argumen kedua alih-alih hash yang kamu simpan, membandingkan dengan hash yang dibuat oleh md5(), atau spasi berlebih di nilai yang tersimpan. Periksa bahwa strlen($hash) bernilai 60 dan berikan password asli sebagai argumen pertama.

Bisakah saya mendekripsi nilai password_hash di PHP?

Tidak. Hash bersifat satu arah: tidak ada fungsi yang mengubahnya kembali menjadi password. Untuk memeriksa password, kamu meng-hash percobaannya lalu membandingkan, persis yang dilakukan password_verify(). Untuk password yang terlupa, kirim link reset alih-alih password lama.

Sebaiknya saya memakai PASSWORD_DEFAULT, PASSWORD_BCRYPT, atau PASSWORD_ARGON2ID?

PASSWORD_DEFAULT adalah pilihan yang biasa: saat ini nilainya bcrypt dan PHP bisa memindahkannya ke algoritma yang lebih kuat nanti, yang bisa kamu terapkan secara bertahap dengan password_needs_rehash(). PASSWORD_ARGON2ID lebih kuat terhadap cracking dengan GPU dan aman dipakai jika build PHP-mu menyertakannya.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai