2012-10-07 142 views
0

感谢在最近发布的一些很好的建议,我正在实施PBKDF2从 https://defuse.ca/php-pbkdf2.htm到一个小的PHP图像库我正在建立自学一些PHP。PBKDF2 PHP的密码散列

我知道你将salt和hash存储在数据库中,然后重建它们以便在用户输入密码时匹配。我不明白的是,当上面的网站的validate_password函数为相同的密码生成不同的唯一salt时,它的工作原理如何。

例如,我创建了一个反复生成只是我的名字(安德鲁)密码的测试。每次最后的哈希值都不相同,大概是由于盐是独特的?然而,哪一个生成的散列并不重要,我也比较了密码,它验证了它。 (它显然没有验证完全不同的密码,如Andrew56或ndrew1)。

任何人都可以向我简单的自我解释为什么是这样吗?好像我甚至不需要储存盐?我希望这不是主题。

<?php 
/* 
* Password hashing with PBKDF2. 
* Author: havoc AT defuse.ca 
* www: https://defuse.ca/php-pbkdf2.htm 
*/ 

// These constants may be changed without breaking existing hashes. 
define("PBKDF2_HASH_ALGORITHM", "sha256"); 
define("PBKDF2_ITERATIONS", 1000); 
define("PBKDF2_SALT_BYTES", 24); 
define("PBKDF2_HASH_BYTES", 24); 

define("HASH_SECTIONS", 4); 
define("HASH_ALGORITHM_INDEX", 0); 
define("HASH_ITERATION_INDEX", 1); 
define("HASH_SALT_INDEX", 2); 
define("HASH_PBKDF2_INDEX", 3); 

function create_hash($password) 
{ 
// format: algorithm:iterations:salt:hash 
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM)); 
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" . 
    base64_encode(pbkdf2(
     PBKDF2_HASH_ALGORITHM, 
     $password, 
     $salt, 
     PBKDF2_ITERATIONS, 
     PBKDF2_HASH_BYTES, 
     true 
    )); 
} 

function validate_password($password, $good_hash) 
{ 
$params = explode(":", $good_hash); 
if(count($params) < HASH_SECTIONS) 
    return false; 
$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]); 
return slow_equals(
    $pbkdf2, 
    pbkdf2(
     $params[HASH_ALGORITHM_INDEX], 
     $password, 
     $params[HASH_SALT_INDEX], 
     (int)$params[HASH_ITERATION_INDEX], 
     strlen($pbkdf2), 
     true 
    ) 
); 
} 

// Compares two strings $a and $b in length-constant time. 
function slow_equals($a, $b) 
{ 
$diff = strlen($a)^strlen($b); 
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++) 
{ 
    $diff |= ord($a[$i])^ord($b[$i]); 
} 
return $diff === 0; 
} 

/* 
* PBKDF2 key derivation function as defined by RSA's PKCS #5:   https://www.ietf.org/rfc/rfc2898.txt 
* $algorithm - The hash algorithm to use. Recommended: SHA256 
* $password - The password. 
* $salt - A salt that is unique to the password. 
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000. 
* $key_length - The length of the derived key in bytes. 
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise. 
* Returns: A $key_length-byte key derived from the password and salt. 
* 
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt 
* 
* This implementation of PBKDF2 was originally created by https://defuse.ca 
* With improvements by http://www.variations-of-shadow.com 
*/ 
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) 
{ 
$algorithm = strtolower($algorithm); 
if(!in_array($algorithm, hash_algos(), true)) 
    die('PBKDF2 ERROR: Invalid hash algorithm.'); 
if($count <= 0 || $key_length <= 0) 
    die('PBKDF2 ERROR: Invalid parameters.'); 

$hash_length = strlen(hash($algorithm, "", true)); 
$block_count = ceil($key_length/$hash_length); 

$output = ""; 
for($i = 1; $i <= $block_count; $i++) { 
    // $i encoded as 4 bytes, big endian. 
    $last = $salt . pack("N", $i); 
    // first iteration 
    $last = $xorsum = hash_hmac($algorithm, $last, $password, true); 
    // perform the other $count - 1 iterations 
    for ($j = 1; $j < $count; $j++) { 
     $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true)); 
    } 
    $output .= $xorsum; 
} 

if($raw_output) 
    return substr($output, 0, $key_length); 
else 
    return bin2hex(substr($output, 0, $key_length)); 
} 
?> 
+0

盐与其他参数一起被包含在散列中。验证函数提取盐并使用相同的参数重新设置密码,这应该产生相同的哈希值。 – NullUserException

+0

好的@NullUserException谢谢。我现在看到我只是储存出来的整个大字符串,我不需要分解并单独存储它。谢谢。 –

回答

0

我发现这个寻找一个老的PHP版本的PBKDF2实现。对于别人发生在打这个页面,如果你直接使用这种方法:

pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) 

然后,它不包括与输出字符串,刚散,这是我希望的盐。

create_hash方法是原来的海报是用什么,并与上述他的设置,这将返回:

sha256:1000:$salt:$hash 

这可以让你给它的所有保存在一个数据库列,这是很容易使用。但是,我会注意到,如果您关心存储空间,那么您“可能”仍然希望将其分开。使用十六进制盐和密码,您可以通过将其存储在二进制列中来减少占用的一半存储空间......例如,在MySQL中,这是通过SET binaryColumn=UNHEX('0F0F'),然后SELECT HEX(binaryColumn) AS binaryColumn完成的。这是更多的工作,所以它归结于你自己的偏好和目标,但认为我会把它扔到那里。