PHP Conference Japan 2024

hash_pbkdf2

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

hash_pbkdf2生成提供的密码的 PBKDF2 密钥派生

描述

hash_pbkdf2(
    字符串 $algo,
    #[\SensitiveParameter] 字符串 $password,
    字符串 $salt,
    整数 $iterations,
    整数 $length = 0,
    布尔值 $binary = false,
    数组 $options = []
): 字符串

参数

algo

选择的哈希算法名称(例如 "sha256")。有关支持的算法列表,请参见 hash_hmac_algos()

注意:

不允许使用非加密哈希函数。

password

用于派生的密码。

salt

用于派生的盐。此值应随机生成。

iterations

为派生执行的内部迭代次数。

length

输出字符串的长度。如果 binarytrue,则对应于派生密钥的字节长度;如果 binaryfalse,则对应于派生密钥字节长度的两倍(因为密钥的每个字节都以两个十六进制字符返回)。

如果传递 0,则使用提供的算法的全部输出。

binary

设置为 true 时,输出原始二进制数据。false 输出小写十六进制字符。

options

各种哈希算法的选项数组。目前,只有 "seed" 密钥受 MurmurHash 变体支持。

返回值

返回一个字符串,其中包含派生密钥作为小写十六进制字符,除非 binary 设置为 true,在这种情况下,返回派生密钥的原始二进制表示形式。

错误/异常

如果算法未知,iterations 参数小于或等于 0length 小于 0salt 太长(大于 INT_MAX - 4),则抛出 ValueError 异常。

变更日志

版本 描述
8.0.0 现在在发生错误时抛出 ValueError 异常。以前,返回 false 并发出 E_WARNING 消息。
7.2.0 禁用了非加密哈希函数(adler32、crc32、crc32b、fnv132、fnv1a32、fnv164、fnv1a64、joaat)的使用。

示例

示例 #1 hash_pbkdf2() 示例,基本用法

<?php
$password
= "password";
$iterations = 600000;

// 使用 random_bytes() 生成密码学安全的随机盐
$salt = random_bytes(16);

$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);
var_dump($hash);

// 对于原始二进制,$length 需要减半才能获得等效的结果
$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 10, true);
var_dump(bin2hex($hash));?>

以上示例的输出将类似于

string(20) "120fb6cffcf8b32c43e7"
string(20) "120fb6cffcf8b32c43e7"

备注

警告

PBKDF2 方法可用于对密码进行哈希处理以进行存储。但是,需要注意的是,password_hash()crypt()CRYPT_BLOWFISH 更适合密码存储。

参见

添加注释

用户贡献的注释 12 条注释

26
clarence.pchy(at)gmail.com
8 年前
请特别注意 **$length** 参数!它正是 **返回字符串的长度**,而不是原始二进制哈希结果的长度。

我遇到了一个关于这个问题的大问题 --
我以为 `hash_pbkdf2(...false)` 应该等于 `bin2hex(hash_pbkdf2(...true))`,就像 `md5($x)` 等于 `bin2hex(md5($x, true))` 一样。但是我错了

hash_pbkdf2('sha256', '123456', 'abc', 10000, 50, false); // 返回字符串(50) "584bc5b41005169f1fa15177edb78d75f9846afc466a4bae05"
hash_pbkdf2('sha256', '123456', 'abc', 10000, 50, true); // 返回字符串(50) "XKŴ��Qw�u��j�FjK���BFW�YpG �mp.g2�`;N�"
bin2hex(hash_pbkdf2('sha256', '123456', 'abc', 10000, 50, true)); // 返回字符串(100) "584bc5b41005169f1fa15177edb78d75f9846afc466a4bae05119c82424657c81b5970471f098a6d702e6732b7603b194efe"

所以我添加了这样的注释。希望它能像帮助我一样帮助其他人。
7
does dot not at matter dot org
11 年前
这段代码片段一年多前发布在荷兰 PHP 社区上:(参考/来源:http://www.phphulp.nl/php/script/beveiliging/pbkdf2-een-veilige-manier-om-wachtwoorden-op-te-slaan/1956/pbkdf2php/1757/)

<?php

/**
* @author Chris Horeweg
* @package Security_Tools
*/

function pbkdf2($password, $salt, $algorithm = 'sha512', $count = 20000, $key_length = 128, $raw_output = false)
{
if(!
in_array($algorithm, hash_algos(), true)) {
exit(
'pbkdf2: 系统未安装哈希算法。');
}

if(
$count <= 0 || $key_length <= 0) {
$count = 20000;
$key_length = 128;
}

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

$output = "";
for(
$i = 1; $i <= $block_count; $i++) {
$last = $salt . pack("N", $i);
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
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
base64_encode(substr($output, 0, $key_length));
}
}
8
Trevor Herselman
9年前
这是一个轻量级的PHP `hash_pbkdf2()` 函数替代品,用于兼容旧版本的PHP。
代码编写、格式化和测试均由本人完成,但使用了以下资源中的代码和思路:
https://defuse.ca/php-pbkdf2.htm
https://github.com/rchouinard/hash_pbkdf2-compat/blob/master/src/hash_pbkdf2.php
https://gist.github.com/rsky/5104756

我的主要目标:
1) 最大限度地兼容PHP `hash_pbkdf2()`,即作为直接替换函数。
2) 最小化代码大小/冗余。
3) 易于复制/粘贴。
4) 无需类,不封装在类中!如果一个简单的函数就能完成任务,为什么要编写一个类呢?
5) 消除对`sprintf()`的调用。(其他示例中用于错误报告)。
6) 无其他依赖项,即无需额外函数。

<?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__ . '(): 未知的哈希算法: ' . $algo, E_USER_WARNING);
if (!
is_numeric($count)) trigger_error(__FUNCTION__ . '(): 期望参数4为长整型,但实际为 ' . gettype($count) . ' 类型', E_USER_WARNING);
if (!
is_numeric($length)) trigger_error(__FUNCTION__ . '(): 期望参数5为长整型,但实际为 ' . gettype($length) . ' 类型', E_USER_WARNING);
if (
$count <= 0) trigger_error(__FUNCTION__ . '(): 迭代次数必须是正整数: ' . $count, E_USER_WARNING);
if (
$length < 0) trigger_error(__FUNCTION__ . '(): 长度必须大于等于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;
}
}
7
匿名用户
11 年前
不幸的是,此函数在PHP 5.5中添加,但许多Web服务器仅提供PHP 5.3。但是存在纯PHP实现(此处找到:https://defuse.ca/php-pbkdf2.htm)。
我采用了这个实现,将其放入一个带有PHPDoc注释的类中,并添加了一个开关,以便如果可用则使用原生的PHP函数。

随意使用!
http://pastebin.com/f5PDq735
(由于文本过长,已发布在pastebin.com上)
2
php . ober-mail . de
3年前
如果您想知道盐的要求是什么,请查看RFC[1]

"盐参数应为包含至少64位熵的随机字符串。这意味着当从像*mcrypt_create_iv*这样的函数生成时,长度至少为8字节。但是对于仅由*a-zA-Z0-9*组成的盐(或进行base_64编码),最小长度应至少为11个字符。对于每个哈希的密码,都应随机生成它,并与生成的密钥一起存储。"

[1] https://wiki.php.net/rfc/hash_pbkdf2
1
Yahe
五年前
如果发生错误,`hash_pbkdf2()` 函数不仅会抛出 E_WARNING 警告,还会返回 FALSE。
1
nimasdj [AT] yahoo [DOT] com
8 年前
Binod Kumar Luitel 提供的类中存在一个错误 (https://php.net/manual/en/function.hash-pbkdf2.php#113488)
这一行:
return bin2hex(substr($this->output, 0, $this->key_length));
必须更改为:
return substr(bin2hex($this->output), 0, $this->key_length);
1
Flimm
六年前
请注意,如果 `$raw_output` 为 false,则输出将使用小写十六进制编码。其他一些系统(例如 Django 2.0)使用 base64 编码。因此,如果您尝试生成与这些系统兼容的哈希字符串,可以使用 `base64_encode` 函数,如下所示:

<?php

echo base64_encode( hash_pbkdf2( "sha256", "example password", "BbirbJq1C1G7", 100000, 0, true ) );

?>
1
gfilippakis at sleed dot gr
五年前
这是一个非常基本的 Rfc2898DeriveBytes 类实现,只包含了它的两个构造函数,以防其他人觉得有用。

类 Rfc2898DeriveBytes
{
private $textToHash;
private $saltByteSize;

public $salt;

public function __construct($arg1, $arg2)
{
if (is_string($arg1) && is_integer($arg2)) {
$this->textToHash = $arg1;
$this->saltByteSize = $arg2;
$this->salt = substr(
hex2bin(sha1(uniqid('', true))),
0,
$this->saltByteSize
);
} elseif (is_string($arg1) && is_string($arg2)) {
$this->textToHash = $arg1;
$this->salt = $arg2;
}
}

public function getBytes($size)
{
return hash_pbkdf2(
"sha1",
$this->textToHash,
$this->salt,
1000,
$size,
true
);
}
}
-1
Binod Kumar Luitel
11 年前
想要纯 PHP 实现该函数的用户(即服务器上未安装 PHP 5.5 的用户)可以使用以下实现。该实现与参考文档 https://defuse.ca/php-pbkdf2.htm 中的实现相比没有修改,但面向对象编程爱好者可能会喜欢这种方式。
有关 PBKDF2 的更多信息,请参阅:http://en.wikipedia.org/wiki/PBKDF2

<?php
/**
* PBKDF2 密钥派生函数,如 RSA 的 PKCS #5 中所定义: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - 要使用的哈希算法。推荐:SHA256
* $password - 密码。
* $salt - 密码的唯一盐值。
* $count - 迭代次数。越高越好,但速度越慢。推荐:至少 1000。
* $key_length - 派生密钥的长度(以字节为单位)。
* $raw_output - 如果为 true,则密钥以原始二进制格式返回。否则为十六进制编码。
* 返回值:从密码和盐派生的 $key_length 字节密钥。
*/
if (!function_exists("hash_pbkdf2")) {
function
hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {

class
pbkdf2 {
public
$algorithm;
public
$password;
public
$salt;
public
$count;
public
$key_length;
public
$raw_output;

private
$hash_length;
private
$output = "";

public function
__construct($data = null)
{
if (
$data != null) {
$this->init($data);
}
}

public function
init($data)
{
$this->algorithm = $data["algorithm"];
$this->password = $data["password"];
$this->salt = $data["salt"];
$this->count = $data["count"];
$this->key_length = $data["key_length"];
$this->raw_output = $data["raw_output"];
}

public function
hash()
{
$this->algorithm = strtolower($this->algorithm);
if(!
in_array($this->algorithm, hash_algos(), true))
throw new
Exception('PBKDF2 ERROR: Invalid hash algorithm.');

if(
$this->count <= 0 || $this->key_length <= 0)
throw new
Exception('PBKDF2 ERROR: Invalid parameters.');

$this->hash_length = strlen(hash($this->algorithm, "", true));
$block_count = ceil($this->key_length / $this->hash_length);
for (
$i = 1; $i <= $block_count; $i++) {
// $i 编码为 4 个字节,大端序。
$last = $this->salt . pack("N", $i);
// 第一次迭代
$last = $xorsum = hash_hmac($this->algorithm, $last, $this->password, true);
// 执行其他 $this->count - 1 次迭代
for ($j = 1; $j < $this->count; $j++) {
$xorsum ^= ($last = hash_hmac($this->algorithm, $last, $this->password, true));
}
$this->output .= $xorsum;
if(
$this->raw_output)
return
substr($this->output, 0, $this->key_length);
else
return
bin2hex(substr($this->output, 0, $this->key_length));
}
}
}

$data = array('algorithm' => $algorithm, 'password' => $password, 'salt' => $salt, 'count' => $count, 'key_length' => $key_length, 'raw_output' => $raw_output);
try {
$pbkdf2 = new pbkdf2($data);
return
$pbkdf2->hash();
} catch (
Exception $e) {
throw
$e;
}
}
}
-2
php at ober-mail dot de
3年前
如果您想知道盐的要求是什么,请查看RFC[1]

"盐参数应为包含至少64位熵的随机字符串。这意味着当从像*mcrypt_create_iv*这样的函数生成时,长度至少为8字节。但是对于仅由*a-zA-Z0-9*组成的盐(或进行base_64编码),最小长度应至少为11个字符。对于每个哈希的密码,都应随机生成它,并与生成的密钥一起存储。"

[1] https://wiki.php.net/rfc/hash_pbkdf2
-3
php - ober-mail - de
3年前
如果您想知道盐的要求是什么,请查看RFC[1]

"盐参数应为包含至少64位熵的随机字符串。这意味着当从像*mcrypt_create_iv*这样的函数生成时,长度至少为8字节。但是对于仅由*a-zA-Z0-9*组成的盐(或进行base_64编码),最小长度应至少为11个字符。对于每个哈希的密码,都应随机生成它,并与生成的密钥一起存储。"

[1] https://wiki.php.net/rfc/hash_pbkdf2
To Top