Penanganan Kata Sandi

password_hash, password_verify, dan mengapa md5 tidak boleh dipakai.

Daftar isi

Penanganan kata sandi yang benar berdiri di atas satu aturan, yaitu server menyimpan hasil hash kata sandi dan tidak pernah menyimpan teks aslinya. Topik ini membahas fungsi password_hash dan password_verify bawaan PHP, alasan fungsi cepat seperti md5 tidak layak dipakai, serta pemeriksaan password_needs_rehash ketika kebijakan algoritma berubah. Setelah membacanya, pendaftaran dan verifikasi kata sandi dapat dibangun tanpa menyimpan rahasia yang rawan.

Mengapa md5 dan sha1 tidak layak#

Fungsi md5 dan sha1 dirancang untuk memeriksa keutuhan data dengan cepat, bukan untuk menyimpan rahasia. Pada keperluan kata sandi, kecepatan itu justru merugikan karena penyerang dapat mencoba miliaran sandi dalam waktu singkat menggunakan perangkat keras biasa. Keduanya juga tidak menyertakan salt, yaitu data acak yang membuat hash dua pengguna dengan sandi yang sama tetap berbeda. Tanpa salt, hash yang sama mudah dicari pada tabel jadi yang berisi jutaan pasangan sandi umum dan hash-nya.

Contoh berikut membandingkan perilaku keduanya atas sandi yang sama.

php
<?php
$md5Satu = md5('sandi123');
$md5Dua  = md5('sandi123');
var_dump($md5Satu === $md5Dua);

$hashSatu = password_hash('sandi123', PASSWORD_DEFAULT);
$hashDua  = password_hash('sandi123', PASSWORD_DEFAULT);
var_dump($hashSatu === $hashDua);
echo 'Panjang hash bcrypt : ' . strlen($hashSatu) . PHP_EOL;
echo 'Algoritma           : ' . password_get_info($hashSatu)['algoName'] . PHP_EOL;
text Keluaran
bool(true)
bool(false)
Panjang hash bcrypt : 60
Algoritma           : bcrypt

Keluaran memperlihatkan perbedaan mendasar. Dua panggilan md5 atas sandi yang sama menghasilkan nilai identik sehingga pengguna dengan sandi sama mudah dikenali, sedangkan dua panggilan password_hash menghasilkan dua hash berbeda. Karena itu, md5 dan sha1 hanya pantas dipakai untuk checksum berkas atau kunci cache. Keluarga fungsi password_* menjadi satu-satunya pilihan untuk kata sandi karena algoritma, salt, dan biayanya diatur otomatis.

Meng-hash kata sandi dengan password_hash#

Fungsi password_hash mengembalikan string 60 karakter yang menghimpun algoritma, biaya (cost), salt, dan hash itu sendiri dalam satu teks, sehingga penyimpanannya cukup satu kolom. Algoritma bawaan PHP 8.4 adalah bcrypt dengan biaya 12, dan biaya itu menentukan lamanya perhitungan hash sehingga upaya menebak menjadi mahal. Fungsi password_get_info membaca rincian tersebut langsung dari string hash tanpa memecahnya secara manual.

php
<?php
$hash = password_hash('SandiKelas2026', PASSWORD_DEFAULT);

echo 'Panjang hash: ' . strlen($hash) . PHP_EOL;
echo 'Algoritma: ' . password_get_info($hash)['algoName'] . PHP_EOL;
text Keluaran
Panjang hash: 60
Algoritma: bcrypt

Keluaran menegaskan bahwa panjang hash selalu 60 karakter untuk bcrypt, sehingga kolom VARCHAR(255) memberi ruang yang cukup untuk algoritma apa pun di masa depan. Konstanta PASSWORD_DEFAULT mengikat kode ke pilihan PHP saat itu, sehingga pembaruan PHP dapat membawa algoritma yang lebih kuat tanpa mengubah kode. Nilai hash tidak perlu diolah ulang atau dimodifikasi, karena seluruh rincian verifikasinya sudah terkandung di dalam string itu.

Memverifikasi dengan password_verify#

Fungsi password_verify membaca algoritma, biaya, dan salt dari hash tersimpan, lalu menghitung ulang hash atas sandi yang dikirim dan membandingkan hasilnya dalam waktu konstan. Fungsi ini tidak perlu mengetahui algoritma apa yang dipakai karena seluruh rinciannya terbaca dari string hash.

php
<?php
$hash = password_hash('SandiKelas2026', PASSWORD_DEFAULT);

var_dump(password_verify('SandiKelas2026', $hash));
var_dump(password_verify('sandikelas2026', $hash));
text Keluaran
bool(true)
bool(false)

Keluaran menunjukkan sandi yang tepat lolos, sedangkan sandi dengan perbedaan huruf kapital ditolak, sehingga pemeriksaan huruf besar dan kecil berlaku penuh. Pola penerapannya pada login berjalan dua langkah, yaitu mengambil hash tersimpan berdasarkan nama pengguna melalui prepared statement, kemudian memanggil password_verify dengan sandi yang dikirim. Pesan kegagalan login tidak boleh membedakan antara pengguna tidak dikenal dan sandi salah, karena perbedaan itu membantu penyerang memetakan nama akun yang ada.

Mengikuti perubahan algoritma#

Konstanta PASSWORD_DEFAULT dan biaya bawaannya dapat berubah antar versi PHP, sedangkan hash lama di basis data tetap bertahan bertahun-tahun. Fungsi password_needs_rehash menangani kesenjangan itu dengan memeriksa apakah sebuah hash sudah dibuat dengan algoritma dan opsi yang berlaku saat ini. Bila belum, sandi asli yang baru saja dikirim pada proses login dihitung ulang dengan password_hash, lalu hasilnya disimpan menggantikan hash yang lama.

php
<?php
$hash = password_hash('SandiKelas2026', PASSWORD_DEFAULT);

var_dump(password_needs_rehash($hash, PASSWORD_DEFAULT));
var_dump(password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 14]));
text Keluaran
bool(false)
bool(true)

Keluaran menunjukkan bahwa hash yang dibuat dengan pengaturan bawaan tidak memerlukan penghitungan ulang, sedangkan kebijakan yang menaikkan biaya bcrypt menjadi 14 menandai hash lama untuk diperbarui. Pemanggilan password_needs_rehash ditempatkan tepat setelah password_verify berhasil pada proses login. Pada titik itu sandi asli tersedia tanpa perlu disimpan, sehingga pembaruan berjalan bertahap mengikuti pengguna yang masuk.

Kesalahan umum#

Empat kesalahan berikut paling sering muncul saat menangani kata sandi.

  • Kata sandi disimpan sebagai teks asli di basis data. Kebocoran satu tabel langsung membuka seluruh akun. Perbaikannya adalah menyimpan hasil password_hash sejak pendaftaran pertama.
  • Sandi di-hash dengan md5 lalu hasilnya dianggap aman. Algoritma cepat tanpa salt mudah dicari pada tabel jadi dan diretakkan dalam waktu singkat. Perbaikannya adalah keluarga fungsi password_* untuk seluruh siklus hidup kata sandi.
  • Kolom penyimpanan dipendekkan, misalnya VARCHAR(20). Hash bcrypt 60 karakter terpotong sehingga password_verify selalu mengembalikan false tanpa pesan yang jelas. Perbaikannya adalah kolom VARCHAR(255).
  • Kecocokan sandi diperiksa dengan menghitung hash baru lalu membandingkannya terhadap kolom basis data. Perhitungan manual membutuhkan salt yang benar dan perbandingan dalam waktu konstan yang tidak tersedia begitu saja. Perbaikannya adalah password_verify sebagai satu-satunya jalur verifikasi.

Ringkasan#

  • Server menyimpan hanya hasil hash kata sandi, dan md5 beserta sha1 tidak layak karena cepat serta tanpa salt.
  • password_hash menghasilkan string 60 karakter yang menghimpun algoritma, biaya, dan salt, sehingga penyimpanannya cukup satu kolom.
  • password_verify menghitung ulang dan membandingkan dalam waktu konstan, dengan huruf besar dan kecil yang berpengaruh.
  • password_needs_rehash menandai hash lama agar dihitung ulang dengan algoritma atau biaya terbaru pada proses login berikutnya.

Referensi#