Masalahnya di sini pada dasarnya adalah masalah entropi. Jadi mari kita mulai mencari di sana:
Entropi Per Karakter
Jumlah bit entropi per byte adalah:
- Karakter Hex
- Bit: 4
- Nilai: 16
- Entropi Dalam 72 Karakter: 288 bit
- Alpha-Numeric
- Bit: 6
- Nilai: 62
- Entropi Dalam 72 Karakter: 432 bit
- Simbol "Umum"
- Bit: 6.5
- Nilai: 94
- Entropi Dalam 72 Karakter: 468 bit
- Byte Penuh
- Bit: 8
- Nilai: 255
- Entropi Dalam 72 Karakter: 576 bit
Jadi, bagaimana kita bertindak tergantung pada tipe karakter yang kita harapkan.
Masalah Pertama
Masalah pertama dengan kode Anda, adalah langkah hash "pepper" Anda mengeluarkan karakter hex (karena parameter keempat ke hash_hmac()
tidak disetel).
Oleh karena itu, dengan memasukkan lada Anda, Anda secara efektif memotong entropi maksimum yang tersedia untuk kata sandi dengan faktor 2 (dari 576 hingga 288 bit yang mungkin ).
Masalah Kedua
Namun, sha256
hanya menyediakan 256
bit entropi di tempat pertama. Jadi Anda secara efektif memotong kemungkinan 576 bit menjadi 256 bit. Langkah hash Anda * segera *, menurut definisi, kehilangan
setidaknya 50% dari kemungkinan entropi dalam kata sandi.
Anda dapat menyelesaikan sebagian ini dengan beralih ke SHA512
, di mana Anda hanya akan mengurangi entropi yang tersedia sekitar 12%. Tapi itu masih perbedaan yang tidak signifikan. 12% itu mengurangi jumlah permutasi dengan faktor 1.8e19
. Itu angka yang besar ... Dan itulah faktor yang dikuranginya dengan ...
Masalah yang Mendasari
Masalah yang mendasarinya adalah ada tiga jenis kata sandi dengan lebih dari 72 karakter. Dampak sistem gaya ini terhadap mereka akan sangat berbeda:
Catatan: mulai saat ini saya mengasumsikan kami membandingkan dengan sistem lada yang digunakan SHA512
dengan keluaran mentah (bukan hex).
Kata sandi acak dengan entropi tinggi
Ini adalah pengguna Anda yang menggunakan generator kata sandi yang menghasilkan kunci besar untuk kata sandi. Mereka acak (dihasilkan, bukan dipilih manusia), dan memiliki entropi tinggi per karakter. Tipe ini menggunakan byte tinggi (karakter> 127) dan beberapa karakter kontrol.
Untuk grup ini, fungsi hashing Anda akan secara signifikan mengurangi entropi yang tersedia menjadi bcrypt
.
Biar saya katakan lagi. Untuk pengguna yang menggunakan entropi tinggi, sandi panjang, solusi Anda secara signifikan mengurangi kekuatan sandi mereka dengan jumlah yang dapat diukur. (62 bit entropi hilang untuk sandi 72 karakter, dan lebih banyak lagi untuk sandi yang lebih panjang)
Kata sandi acak entropi sedang
Grup ini menggunakan sandi yang berisi simbol umum, tetapi tidak ada byte tinggi atau karakter kontrol. Ini adalah kata sandi Anda yang dapat diketik.
Untuk grup ini, Anda akan membuka sedikit lebih banyak entropi (bukan membuatnya, tetapi mengizinkan lebih banyak entropi untuk masuk ke dalam kata sandi bcrypt). Ketika saya mengatakan sedikit, maksud saya sedikit. Impas terjadi ketika Anda memaksimalkan 512 bit yang dimiliki SHA512. Oleh karena itu, puncaknya ada pada 78 karakter.
Biar saya katakan lagi. Untuk kelas kata sandi ini, Anda hanya dapat menyimpan 6 karakter tambahan sebelum Anda kehabisan entropi.
Kata sandi non-acak entropi rendah
Ini adalah grup yang menggunakan karakter alfanumerik yang mungkin tidak dibuat secara acak. Sesuatu seperti kutipan Alkitab atau semacamnya. Frase ini memiliki sekitar 2,3 bit entropi per karakter.
Untuk grup ini, Anda dapat membuka lebih banyak entropi secara signifikan (bukan membuatnya, tetapi mengizinkan lebih banyak untuk dimasukkan ke dalam input kata sandi bcrypt) dengan melakukan hashing. Titik impas adalah sekitar 223 karakter sebelum Anda kehabisan entropi.
Katakan itu lagi. Untuk kelas kata sandi ini, pra-hashing pasti meningkatkan keamanan secara signifikan.
Kembali Ke Dunia Nyata
Perhitungan entropi semacam ini tidak terlalu penting di dunia nyata. Yang penting adalah menebak entropi. Itulah yang secara langsung mempengaruhi apa yang dapat dilakukan penyerang. Itulah yang ingin Anda maksimalkan.
Meskipun ada sedikit penelitian yang dilakukan untuk menebak entropi, ada beberapa poin yang ingin saya tunjukkan.
Peluang menebak secara acak 72 karakter yang benar berturut-turut sangat rendah. Anda lebih mungkin memenangkan lotre Powerball 21 kali, daripada mengalami tabrakan ini ... Itulah jumlah yang sedang kita bicarakan.
Tapi kita mungkin tidak tersandung pada statistik itu. Dalam kasus frase, kemungkinan 72 karakter pertama menjadi sama jauh lebih tinggi daripada kata sandi acak. Tapi itu masih sangat rendah (Anda lebih cenderung memenangkan lotere Powerball 5 kali, berdasarkan 2,3 bit per karakter).
Praktis
Secara praktis, itu tidak terlalu penting. Peluang seseorang menebak 72 karakter pertama dengan benar, sedangkan yang terakhir membuat perbedaan yang signifikan sangat rendah sehingga tidak perlu dikhawatirkan. Mengapa?
Nah, katakanlah Anda mengambil frase. Jika orang tersebut bisa mendapatkan 72 karakter pertama dengan benar, mereka benar - benar beruntung (tidak mungkin), atau itu adalah frasa yang umum. Jika itu frase umum, satu-satunya variabel adalah berapa panjangnya.
Mari kita ambil contoh. Mari kita ambil kutipan dari Alkitab (hanya karena itu adalah sumber umum dari teks panjang, bukan karena alasan lain):
Anda tidak akan mengingini rumah tetangga Anda. Anda tidak boleh mengingini istri tetangga Anda, atau pelayan atau pembantunya, lembu atau keledainya, atau apapun yang menjadi milik tetangga Anda.
Itu 180 karakter. Karakter ke-73 adalah g
yang kedua neighbor's
. Jika Anda sudah menebak sebanyak itu, kemungkinan besar Anda tidak akan berhenti nei
, tetapi melanjutkan dengan sisa ayat (karena begitulah kemungkinan besar kata sandi akan digunakan). Oleh karena itu, "hash" Anda tidak menambahkan banyak.
BTW: Saya BENAR-BENAR TIDAK menganjurkan penggunaan kutipan Alkitab. Padahal, justru sebaliknya.
Kesimpulan
Anda tidak akan banyak membantu orang yang menggunakan kata sandi panjang dengan melakukan hashing terlebih dahulu. Beberapa grup yang pasti bisa Anda bantu. Beberapa Anda pasti bisa melukai.
Tapi pada akhirnya, tidak ada yang terlalu signifikan. Angka-angka yang kita hadapi hanya WAY terlalu tinggi. Perbedaan entropi tidak akan banyak.
Anda lebih baik membiarkan bcrypt apa adanya. Anda lebih cenderung mengacaukan hashing (secara harfiah, Anda sudah melakukannya, dan Anda bukan yang pertama, atau terakhir membuat kesalahan itu) daripada serangan yang Anda coba cegah akan terjadi.
Berfokuslah untuk mengamankan sisa situs. Dan tambahkan pengukur entropi kata sandi ke kotak kata sandi saat pendaftaran untuk menunjukkan kekuatan kata sandi (dan tunjukkan jika kata sandi terlalu panjang sehingga pengguna mungkin ingin mengubahnya) ...
Itu setidaknya $ 0,02 saya (atau mungkin lebih dari $ 0,02) ...
Sejauh Menggunakan Lada "Rahasia":
Secara harfiah tidak ada penelitian untuk memasukkan satu fungsi hash ke bcrypt. Oleh karena itu, tidak jelas apakah memasukkan hash yang "dibumbui" ke dalam bcrypt akan pernah menyebabkan kerentanan yang tidak diketahui (kami tahu bahwa tindakan tersebut hash1(hash2($value))
dapat mengekspos kerentanan yang signifikan di sekitar resistensi tabrakan dan serangan preimage).
Mengingat Anda sudah mempertimbangkan untuk menyimpan kunci rahasia ("lada"), mengapa tidak menggunakannya dengan cara yang telah dipelajari dan dipahami dengan baik? Mengapa tidak mengenkripsi hash sebelum menyimpannya?
Pada dasarnya, setelah Anda mencirikan kata sandi, masukkan seluruh keluaran hash ke dalam algoritma enkripsi yang kuat. Kemudian simpan hasil yang dienkripsi.
Sekarang, serangan SQL-Injection tidak akan membocorkan sesuatu yang berguna, karena mereka tidak memiliki kunci sandi. Dan jika kuncinya bocor, penyerang tidak lebih baik daripada jika Anda menggunakan hash biasa (yang dapat dibuktikan, sesuatu dengan lada "pra-hash" tidak tersedia).
Catatan: jika Anda memilih untuk melakukan ini, gunakan perpustakaan. Untuk PHP, saya sangat merekomendasikan paket Zend Framework 2 Zend\Crypt
. Ini sebenarnya satu-satunya yang saya rekomendasikan pada saat ini. Ini telah ditinjau dengan kuat, dan itu membuat semua keputusan untuk Anda (yang merupakan hal yang sangat baik) ...
Sesuatu seperti:
use Zend\Crypt\BlockCipher;
public function createHash($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
return $blockCipher->encrypt($hash);
}
public function verifyHash($password, $hash) {
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
$hash = $blockCipher->decrypt($hash);
return password_verify($password, $hash);
}
Dan itu bermanfaat karena Anda menggunakan semua algoritme dengan cara yang dipahami dengan baik dan dipelajari dengan baik (setidaknya secara relatif). Ingat:
Siapa pun, dari amatir yang paling tidak mengerti hingga kriptografer terbaik, dapat membuat algoritme yang tidak dapat dia pecahkan sendiri.