Kata sandi pengguna tidak boleh disimpan apa adanya di basis data. Topik ini membahas hashing kata sandi dengan bcrypt, peran salt acak, pemilihan cost factor, serta perbandingan dengan argon2 sebagai alternatif yang lebih modern. Setelah membaca topik ini, kata sandi dapat dihash dengan bcrypt, hash dapat diverifikasi saat pengguna masuk, serta alasan salt dan cost factor dapat dijelaskan sebagai pertahanan terhadap serangan rainbow table dan brute force.
Mengapa kata sandi dihash#
Bila kata sandi disimpan apa adanya di basis data, satu kebocoran basis data membuka seluruh akun pengguna. Banyak pengguna memakai kata sandi yang sama untuk beberapa layanan, sehingga kebocoran pada satu situs berakibat pada akun di situs lain. Penyimpanan dalam bentuk terenkripsi pun tidak ideal karena kunci dekripsi sering kali berada pada server yang sama dengan basis data, sehingga kebocoran satu proses membuka keduanya.
Hashing membuat proses penyimpanan kata sandi menjadi satu arah, yaitu tidak dapat dibalik untuk memperoleh kata sandi asli. Kata sandi yang dimasukkan pengguna dihash dan dibandingkan dengan hash yang tersimpan. Bila hash cocok, kata sandi diterima. Bila basis data bocor, penyerang memperoleh daftar hash, bukan kata sandi. Penyerang masih dapat mencoba menebak dengan menjalankan hash atas daftar kata sandi umum, tetapi pekerjaan ini diperlambat oleh algoritma yang sengaja dibuat mahal (bcrypt, argon2) dan oleh salt yang mencegah pemakaian ulang hasil tebakan antar pengguna.
Fungsi hash kriptografi umum seperti SHA-256 tidak cocok untuk kata sandi karena terlalu cepat. Sebuah kartu grafis modern dapat menghitung miliaran hash SHA-256 per detik, sehingga daftar kata sandi umum dapat ditebak dalam hitungan menit. Algoritma kata sandi seperti bcrypt dan argon2 sengaja dibuat lambat lewat parameter cost factor (bcrypt) atau parameter memori-paralel (argon2), sehingga tiap upaya tebakan memerlukan waktu dan sumber daya yang nyata.
Hashing dengan bcrypt#
bcrypt adalah algoritma hashing kata sandi yang menggabungkan salt acak ke dalam hasil hash dan menyertakan parameter cost factor yang mengatur banyaknya putaran komputasi. Hash bcrypt disimpan sebagai satu string dengan format $2b$<cost>$<salt><hash> sehingga salt dan cost dapat dibaca kembali dari hash itu sendiri pada saat verifikasi.
const bcrypt = require("bcrypt");
// Hash berikut ditangkap dari satu eksekusi bcrypt.hashSync("rahasia-latihan-2026", 10).
// Hash bcrypt berisi salt acak sehingga hashSync menghasilkan hash berbeda tiap
// pemanggilan. Pada produksi, hash disimpan tanpa dicetak.
const hashTersimpan = "$2b$10$t6mX4daiRc3AsNjBhAfmLOfC0TXaXyN1H6nB/v.NRz5bIvZWluGQe";
console.log("hash tersimpan:", hashTersimpan);
console.log("panjang:", hashTersimpan.length);
// compareSync membandingkan kata sandi yang dimasukkan dengan hash tersimpan.
const sandiBenar = "rahasia-latihan-2026";
const sandiSalah = "sandi-yang-salah";
console.log("compare benar:", bcrypt.compareSync(sandiBenar, hashTersimpan));
console.log("compare salah:", bcrypt.compareSync(sandiSalah, hashTersimpan));
// Menerbitkan hash baru memperlihatkan salt acak: dua panggilan berturut-turut
// menghasilkan hash yang berbeda walaupun kata sandi sama.
const hashBaru1 = bcrypt.hashSync(sandiBenar, 10);
const hashBaru2 = bcrypt.hashSync(sandiBenar, 10);
console.log("hash baru berbeda:", hashBaru1 !== hashBaru2);
console.log("panjang hash baru:", hashBaru1.length, hashBaru2.length);
console.log("prefix hash baru:", hashBaru1.substring(0, 7), hashBaru2.substring(0, 7));hash tersimpan: $2b$10$t6mX4daiRc3AsNjBhAfmLOfC0TXaXyN1H6nB/v.NRz5bIvZWluGQe
panjang: 60
compare benar: true
compare salah: false
hash baru berbeda: true
panjang hash baru: 60 60
prefix hash baru: $2b$10$ $2b$10$String hash $2b$10$... pada keluaran terurai sebagai berikut. $2b menandai algoritma bcrypt versi 2b. Angka 10 adalah cost factor, yaitu banyaknya putaran komputasi yang dinyatakan sebagai 2^10 = 1024 iterasi. Dua puluh dua karakter setelah tanda dolar kedua adalah salt dalam encoding Base64 khusus bcrypt, dan tiga puluh satu karakter sisanya adalah hash kata sandi. Total panjang hash bcrypt adalah enam puluh karakter.
compareSync tidak membandingkan dua string hash secara langsung. bcrypt mengurai salt dari hash tersimpan, memakai salt itu untuk menghash kata sandi yang dimasukkan dengan cost yang sama, lalu membandingkan hasilnya dengan bagian hash pada string tersimpan. Inilah sebabnya dua hash yang dihasilkan dari kata sandi yang sama selalu berbeda, tetapi compareSync tetap mengembalikan true bila kata sandi benar.
Salt acak dan cost factor#
Salt adalah nilai acak yang ditambahkan ke kata sandi sebelum dihash. Salt berbeda untuk tiap pengguna sehingga dua pengguna dengan kata sandi yang sama menghasilkan hash yang berbeda. Tanpa salt, penyerang dapat menyusun rainbow table (tabel hash yang sudah dihitung untuk jutaan kata sandi umum) sekali dan memakainya untuk seluruh basis data. Dengan salt acak per pengguna, rainbow table harus dibuat ulang untuk tiap baris, yang menghapus nilai ekonominya.
Cost factor pada bcrypt mengatur banyaknya iterasi. Cost 10 berarti 2^10 = 1024 putaran, sedangkan cost 12 berarti 4096 putaran. Peningkatan satu angka cost menggandakan waktu komputasi. Cost yang tepat bergantung pada perangkat keras server dan kebijakan toleransi latensi pada endpoint login. Pedoman umum saat ini menyarankan cost antara 10 dan 12 agar tiap hash memerlukan sekitar 100-300 milidetik pada perangkat keras biasa.
Karena cost disimpan di dalam string hash, cost dapat ditingkatkan tanpa memaksa pengguna mengganti kata sandi. Saat pengguna masuk dan compareSync berhasil, server memeriksa cost pada hash tersimpan. Bila cost itu lebih rendah dari kebijakan saat ini, server menghitung ulang hash dengan cost baru dan memperbarui baris basis data. Pola ini disebut rehash on login.
Argon2 sebagai alternatif#
argon2 adalah pemenang Kompetisi Password Hashing 2015 dan dirancang untuk tahan terhadap serangan berbasis kartu grafis serta ASIC (sirkuit terpadu khusus aplikasi, yaitu perangkat keras yang dibuat untuk satu tugas tertentu). Berbeda dengan bcrypt yang mengatur waktu CPU saja, argon2 mengatur tiga parameter: memori (m), paralelisme (p), dan iterasi (t). Penambahan memori membuat serangan dengan perangkat keras paralel menjadi mahal karena tiap instance memerlukan sejumlah memori yang besar.
// tidak-dijalankan
const argon2 = require("argon2");
async function hashDanVerifikasi(sandi) {
const hash = await argon2.hash(sandi);
// hash berbentuk: $argon2id$v=19$m=65536,p=4,t=3$<salt>$<hash>
const cocok = await argon2.verify(hash, sandi);
return { hash, cocok };
}argon2 menyediakan tiga varian: argon2d (tahan serangan time-memory tradeoff, cocok untuk kriptografi), argon2i (tahan serangan side-channel, cocok untuk kata sandi), dan argon2id (hibrida, menjadi default pustaka argon2 di npm). Varian argon2id direkomendasikan oleh OWASP untuk penyimpanan kata sandi baru. Pustaka argon2 pada Node.js bersifat asinkron karena komputasinya membebani CPU dan memori, sehingga Promise yang dikembalikan perlu ditangani dengan await.
Kesalahan umum#
Menyimpan kata sandi dengan hash kriptografi cepat seperti MD5, SHA-1, atau SHA-256. Fungsi-fungsi ini dirancang untuk integritas data, bukan untuk kata sandi. Kecepatannya memungkinkan penyerang menebak miliaran kandidat per detik pada perangkat keras khusus. Untuk kata sandi, gunakan bcrypt, argon2, atau scrypt yang sengaja dibuat mahal secara komputasi.
Memakai salt statis untuk seluruh pengguna. Salt yang sama untuk semua baris mengembalikan manfaat salt menjadi nol karena dua pengguna dengan kata sandi yang sama menghasilkan hash yang sama, dan rainbow table yang dibuat untuk salt itu berlaku untuk seluruh basis data. bcrypt menghasilkan salt acak per panggilan lewat bcrypt.hashSync(sandi, cost), sehingga pola yang salah adalah menghasilkan salt satu kali dan menyimpannya sebagai konstanta lalu memakainya untuk seluruh pengguna.
Mengubah cost lalu memaksa semua pengguna mengganti kata sandi. Cost disimpan di dalam string hash bcrypt, sehingga cost baru berlaku untuk hash baru tanpa memengaruhi hash lama. Pola rehash on login menaikkan cost secara bertahap: setiap kali pengguna masuk, server memeriksa cost hash lama, dan bila lebih rendah dari kebijakan saat ini, hash baru dengan cost baru dihitung dan disimpan. Pengguna tidak perlu diganggu dengan reset kata sandi.
Ringkasan#
- Kata sandi disimpan sebagai hash, bukan sebagai teks bias atau terenkripsi, sehingga kebocoran basis data hanya membuka hash, bukan kata sandi. Algoritma hash kriptografi cepat (SHA-256) tidak cocok karena memungkinkan tebakan massal.
- bcrypt menghasilkan satu string
$2b$<cost>$<salt><hash>yang menyimpan algoritma, cost, salt, dan hash.bcrypt.hashSync(sandi, cost)menghasilkan hash dengan salt acak, sedangkanbcrypt.compareSync(sandi, hash)memverifikasi tanpa menerbitkan ulang hash sebagai string. - Salt acak per pengguna mencegah pemakaian ulang rainbow table, sedangkan cost factor mengatur banyaknya iterasi (
2^cost). Cost10sampai12lazim dipakai, dan dapat ditingkatkan lewat pola rehash on login tanpa memaksa reset kata sandi. - argon2 adalah alternatif modern yang mengatur memori, paralelisme, dan iterasi. Varian
argon2iddirekomendasikan OWASP untuk kata sandi baru.
Referensi#
- bcrypt — npm
— Dokumentasi pustaka
bcryptuntuk Node.js, mencakuphashSync,compareSync, pemilihan cost, dan format string hash. - argon2 — npm
— Dokumentasi pustaka
argon2untuk Node.js, mencakup parameter memori, paralelisme, dan iterasi serta varianargon2id. - Password Storage Cheat Sheet — OWASP — Panduan resmi OWASP tentang penyimpanan kata sandi, pemilihan algoritma (bcrypt, argon2, scrypt), serta parameter cost dan memori yang direkomendasikan.