PBKDF2(Password-Based Key Derivation Function)是一個用來導出密鑰的函數,經常使用於生成加密的密碼。php
它的基本原理是經過一個僞隨機函數(例如HMAC函數),把明文和一個鹽值做爲輸入參數,而後重複進行運算,並最終產生密鑰。git
若是重複的次數足夠大,破解的成本就會變得很高。而鹽值的添加也會增長「彩虹表」攻擊的難度。github
上圖是14年在一臺多GPU的高端PC上進行的測試,能夠看到,在4個單詞(隨機從Diceware列表選擇)的狀況下,若是每秒能猜2萬個密碼,則要猜出密碼平均須要2890年。若是密碼的長度再高點,則這個時間是天文數字了。算法
DK = PBKDF2(PRF, Password, Salt, c, dkLen)
PRF是一個僞隨機函數,例如HASH_HMAC函數,它會輸出長度爲hLen的結果。安全
Password是用來生成密鑰的原文密碼。函數
Salt是一個加密用的鹽值。性能
c是進行重複計算的次數。測試
dkLen是指望獲得的密鑰的長度。ui
DK是最後產生的密鑰。編碼
DK的值由一個以上的block拼接而成。block的數量是dkLen/hLen
的值。就是說若是PRF輸出的結果比指望獲得的密鑰長度要短,則要經過拼接多個結果以知足密鑰的長度:
DK = T1 || T2 || ... || Tdklen/hlen
而每一個block則經過則經過函數F
獲得:
Ti = F(Password, Salt, c, i)
在函數F裏,PRF
會進行c
次的運算,而後把獲得的結果進行異或運算,獲得最終的值。
F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
第一次,PRF
會使用Password
做爲key,Salt
拼接上編碼成大字節序的32位整型的i
做爲鹽值進行運算。
U1 = PRF(Password, Salt || INT_32_BE(i))
然後續的c
-1次則會使用上次獲得的結果做爲鹽值。
U2 = PRF(Password, U1) ... Uc = PRF(Password, Uc-1)
函數F
大體的流程圖以下:
從PHP5.5版本開始,PHP提供了原生的函數hash_pbkdf2
實現PBKDF2算法:
string hash_pbkdf2 ( string $algo , string $password , string $salt , int $iterations [, int $length = 0 [, bool $raw_output = false ]] )
具體用法請看:http://php.net/manual/en/function.hash-pbkdf2.php
而在這個版本以前,咱們可使用其餘用戶寫的兼容方法,例如:
<?php if (!function_exists('hash_pbkdf2')) { function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $raw_output = false) { if (!in_array(strtolower($algo), hash_algos())) trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING); if (!is_numeric($count)) trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . gettype($count) . ' given', E_USER_WARNING); if (!is_numeric($length)) trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . gettype($length) . ' given', E_USER_WARNING); if ($count <= 0) trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING); if ($length < 0) trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING); $output = ''; $block_count = $length ? ceil($length / strlen(hash($algo, '', $raw_output))) : 1; for ($i = 1; $i <= $block_count; $i++) { $last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true); for ($j = 1; $j < $count; $j++) { $xorsum ^= ($last = hash_hmac($algo, $last, $password, true)); } $output .= $xorsum; } if (!$raw_output) $output = bin2hex($output); return $length ? substr($output, 0, $length) : $output; } }
又或者這個:https://github.com/rchouinard/hash_pbkdf2-compat
基於安全考慮,須要注意如下幾點:
$algo建議選擇SHA256或更安全的哈希算法。
$salt至少爲8字節,且要爲隨機數。
$count迭代次數建議至少爲50000次,除非有嚴格的性能要求。