【筆記】PBKDF2算法

PBKDF2是什麼

PBKDF2(Password-Based Key Derivation Function)是一個用來導出密鑰的函數,經常使用於生成加密的密碼。php

它的基本原理是經過一個僞隨機函數(例如HMAC函數),把明文和一個鹽值做爲輸入參數,而後重複進行運算,並最終產生密鑰。git

若是重複的次數足夠大,破解的成本就會變得很高。而鹽值的添加也會增長「彩虹表」攻擊的難度。github

破解PBKDF2生成的密碼要多久

20-and-5K-guesses-per-sec.png

上圖是14年在一臺多GPU的高端PC上進行的測試,能夠看到,在4個單詞(隨機從Diceware列表選擇)的狀況下,若是每秒能猜2萬個密碼,則要猜出密碼平均須要2890年。若是密碼的長度再高點,則這個時間是天文數字了。算法

PBKDF2函數的定義

DK = PBKDF2(PRF, Password, Salt, c, dkLen)
  • PRF是一個僞隨機函數,例如HASH_HMAC函數,它會輸出長度爲hLen的結果。安全

  • Password是用來生成密鑰的原文密碼。函數

  • Salt是一個加密用的鹽值。性能

  • c是進行重複計算的次數。測試

  • dkLen是指望獲得的密鑰的長度。ui

  • DK是最後產生的密鑰。編碼

PBKDF2的算法流程

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大體的流程圖以下:

pbkdf2-lastpass.png

PBKDF2算法在PHP中的使用

從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

基於安全考慮,須要注意如下幾點:

  1. $algo建議選擇SHA256或更安全的哈希算法。

  2. $salt至少爲8字節,且要爲隨機數。

  3. $count迭代次數建議至少爲50000次,除非有嚴格的性能要求。

參考

  1. http://en.wikipedia.org/wiki/PBKDF2

  2. http://php.net/manual/en/function.hash-pbkdf2.php

  3. https://blog.agilebits.com/2014/03/10/crackers-report-great-news-for-1password-4/

相關文章
相關標籤/搜索