PHP 大会日本 2024

hash_hmac

(PHP 5 >= 5.1.2,PHP 7,PHP 8,PECL hash >= 1.1)

hash_hmac使用 HMAC 方法生成带密钥的哈希值

描述

hash_hmac(
    字符串 $algo,
    字符串 $data,
    #[\SensitiveParameter] 字符串 $key,
    布尔值 $binary = false
): 字符串

参数

algo

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

注意:

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

data

要进行哈希处理的消息。

key

用于生成消息摘要的 HMAC 变体的共享密钥。

binary

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

返回值

返回一个字符串,其中包含计算出的消息摘要,以小写十六进制数字表示,除非 binary 设置为 true,在这种情况下,将返回消息摘要的原始二进制表示形式。

错误/异常

如果 algo 未知或是非加密哈希函数,则抛出 ValueError 异常。

变更日志

版本 描述
8.0.0 现在如果 algo 未知或是非加密哈希函数,则抛出 ValueError 异常;以前,返回 false
7.2.0 禁用非加密哈希函数(adler32、crc32、crc32b、fnv132、fnv1a32、fnv164、fnv1a64、joaat)的使用。

示例

示例 #1 hash_hmac() 示例

<?php
echo hash_hmac('sha256', 'The quick brown fox jumped over the lazy dog.', 'secret');
?>

以上示例将输出

9c5c42422b03f0ee32949920649445e417b2c634050833c5165704b825c2a53b

参见

添加注释

用户贡献的注释 11 条注释

Korbendallas
6 年前
非常重要的提示,如果您将数组传递给 $data,php 将生成警告,返回 NULL 并继续您的应用程序。我认为这是一个严重的漏洞,因为此函数通常用于检查授权。

示例
<?php
var_dump
(hash_hmac('sha256', [], 'secret'));

WARNING hash_hmac() expects parameter 2 to be string, array given on line number 3
NULL
?>
当然,这并非记录在案的功能。
Michael
11 年前
在比较哈希值时请注意。在某些情况下,可以通过使用计时攻击泄露信息。它利用 == 运算符仅比较到找到两个字符串之间的差异为止。为了防止这种情况,您有两个选择。

选项 1:首先对两个哈希字符串进行哈希处理 - 这不会停止计时差异,但会使信息变得无用。

<?php
if (md5($hashed_value) === md5($hashed_expected)) {
echo
"hashes match!";
}
?>

选项 2:始终比较整个字符串。

<?php
if (hash_compare($hashed_value, $hashed_expected)) {
echo
"hashes match!";
}

function
hash_compare($a, $b) {
if (!
is_string($a) || !is_string($b)) {
return
false;
}

$len = strlen($a);
if (
$len !== strlen($b)) {
return
false;
}

$status = 0;
for (
$i = 0; $i < $len; $i++) {
$status |= ord($a[$i]) ^ ord($b[$i]);
}
return
$status === 0;
}
?>
Michiel Thalen, Thalent
8 年前
正如 Michael 建议的那样,我们应该注意不要使用 ==(或 ===)来比较哈希值。从 PHP 5.6 版本开始,我们现在可以使用 hash_equals()。

因此,示例将是

<?php
if (hash_equals($hashed_expected, $hashed_value) ) {
echo
"哈希值匹配!";
}
?>
pbuttelli at tutanota dot com
1 年前
在实现 TOTP 应用程序时,请注意 hash_hmac() 必须接收二进制数据,而不是十六进制字符串,才能在跨平台生成有效的 OTP。

此问题可以通过在传递给 hash_hmac() 之前将十六进制字符串转换为其二进制形式轻松解决。

<?php
$time
= hex2bin('0000000003523f77'); // 时间必须采用这种“十六进制并填充”的形式
$key = hex2bin('bb57d1...'); // 160 位 = 40 位十六进制 (4 位) = 32 位 base32 (5 位)

hash_hmac('sha1', $time, $key);
?>
KC Cloyd
15 年前
有时托管服务提供商不提供对 Hash 扩展的访问。以下是一个 hash_hmac 函数的克隆,您可以在需要 HMAC 生成器且 Hash 不可用时使用它。它仅适用于 MD5 和 SHA1 加密算法,但其输出与官方 hash_hmac 函数相同(至少到目前为止)。

<?php

function custom_hmac($algo, $data, $key, $raw_output = false)
{
$algo = strtolower($algo);
$pack = 'H'.strlen($algo('test'));
$size = 64;
$opad = str_repeat(chr(0x5C), $size);
$ipad = str_repeat(chr(0x36), $size);

if (
strlen($key) > $size) {
$key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
} else {
$key = str_pad($key, $size, chr(0x00));
}

for (
$i = 0; $i < strlen($key) - 1; $i++) {
$opad[$i] = $opad[$i] ^ $key[$i];
$ipad[$i] = $ipad[$i] ^ $key[$i];
}

$output = $algo($opad.pack($pack, $algo($ipad.$data)));

return (
$raw_output) ? pack($pack, $output) : $output;
}

?>

使用示例

<?php

custom_hmac
('sha1', 'Hello, world!', 'secret', true);

?>
pete dot walker at NOSPAM dot me dot com
12 年前
一个实现了 RFC 6238 中概述的算法的函数 (http://tools.ietf.org/html/rfc6238)

<?php
/**
* 此函数实现了 RFC 6238 中概述的基于时间的的一次性密码算法
*
* @link http://tools.ietf.org/html/rfc6238
* @param string $key 用于 HMAC 密钥的字符串
* @param mixed $time 反映时间的值(示例中为 Unix 时间戳)
* @param int $digits 期望的 OTP 长度
* @param string $crypto 期望的 HMAC 加密算法
* @return string 生成的 OTP
*/
function oauth_totp($key, $time, $digits=8, $crypto='sha256')
{
$digits = intval($digits);
$result = null;

// 将计数器转换为二进制(64 位)
$data = pack('NNC*', $time >> 32, $time & 0xFFFFFFFF);

// 填充到 8 个字符(如有必要)
if (strlen ($data) < 8) {
$data = str_pad($data, 8, chr(0), STR_PAD_LEFT);
}

// 获取哈希值
$hash = hash_hmac($crypto, $data, $key);

// 获取偏移量
$offset = 2 * hexdec(substr($hash, strlen($hash) - 1, 1));

// 获取我们感兴趣的部分
$binary = hexdec(substr($hash, $offset, 8)) & 0x7fffffff;

// 求模
$result = $binary % pow(10, $digits);

// 填充(如有必要)
$result = str_pad($result, $digits, "0", STR_PAD_LEFT);

return
$result;
}
?>
havoc at NOSPAM defuse dot ca
12 年前
这是一个高效的 PBKDF2 实现

<?php
/*
* PBKDF2 密钥派生函数,如 RSA 的 PKCS #5 中所定义: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - 要使用的哈希算法。推荐:SHA256
* $password - 密码。
* $salt - 密码的唯一盐值。
* $count - 迭代次数。越高越好,但速度越慢。推荐:至少 1024。
* $key_length - 派生密钥的字节长度。
* $raw_output - 如果为真,则密钥以原始二进制格式返回。否则为十六进制编码。
* 返回值:从密码和盐值派生的 $key_length 字节密钥。
*
* 测试向量可以在此处找到: https://www.ietf.org/rfc/rfc6070.txt
*
* 此 PBKDF2 实现最初由 defuse.ca 创建
* 并由 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 错误:无效的哈希算法。');
if(
$count <= 0 || $key_length <= 0)
die(
'PBKDF2 错误:无效的参数。');

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

$output = "";
for(
$i = 1; $i <= $block_count; $i++) {
// $i 编码为 4 个字节,大端序。
$last = $salt . pack("N", $i);
// 第一次迭代
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// 执行其他 $count - 1 次迭代
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));
}
?>
Siann Beck
14 年前
对于签名 Amazon AWS 查询,请使用 base64 编码二进制值

<?php
$Sig
= base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
?>
Peter Terence Roux
13 年前
RFC 2898 中描述的 PBKDF2 密钥派生函数的实现不仅可以用于获取哈希后的密钥,还可以获取特定的 IV。

要使用它,可以按如下方式使用:-

<?php
$p
= str_hash_pbkdf2($pw, $salt, 10, 32, 'sha1');
$p = base64_encode($p);

$iv = str_hash_pbkdf2($pw, $salt, 10, 16, 'sha1', 32);
$iv = base64_encode($iv);
?>

该函数应为:-

<?php
// PBKDF2 实现(在 RFC 2898 中描述)
//
// @param string p 密码
// @param string s 盐值
// @param int c 迭代次数(使用 1000 或更高)
// @param int kl 派生密钥长度
// @param string a 哈希算法
// @param int st 结果的起始位置
//
// @return string 派生密钥
function str_hash_pbkdf2($p, $s, $c, $kl, $a = 'sha256', $st=0)
{
$kb = $start+$kl; // 要计算的密钥块
$dk = ''; // 派生密钥

// 创建密钥
for ($block=1; $block<=$kb; $block++)
{
// 此块的初始哈希
$ib = $h = hash_hmac($a, $s . pack('N', $block), $p, true);

// 执行块迭代
for ($i=1; $i<$c; $i++)
{
// 对每次迭代进行异或
$ib ^= ($h = hash_hmac($a, $h, $p, true));
}

$dk .= $ib; // 附加迭代后的块
}

// 返回正确长度的派生密钥
return substr($dk, $start, $kl);
}
?>
josefkoh at hotmail dot com
13 年前
HMAC SHA1 的简单实现

<?php

function hmac_sha1($key, $data)
{
// 将密钥调整为正好 64 个字节
if (strlen($key) > 64) {
$key = str_pad(sha1($key, true), 64, chr(0));
}
if (
strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}

// 外部和内部填充
$opad = str_repeat(chr(0x5C), 64);
$ipad = str_repeat(chr(0x36), 64);

// 将密钥与 opad 和 ipad 进行异或
for ($i = 0; $i < strlen($key); $i++) {
$opad[$i] = $opad[$i] ^ $key[$i];
$ipad[$i] = $ipad[$i] ^ $key[$i];
}

return
sha1($opad.sha1($ipad.$data, true));
}

Pawel M.
3 年前
适用于那些确实需要在 PHP>7.1 中使用 crc32 算法的人员的功能

<?php
function hash_hmac_crc32(string $key, string $data): string
{
$b = 4;
if (
strlen($key) > $b) {
$key = pack("H*", hash('crc32', $key));
}
$key = str_pad($key, $b, chr(0x00));
$ipad = str_pad('', $b, chr(0x36));
$opad = str_pad('', $b, chr(0x5c));
$k_ipad = $key ^ $ipad;
$k_opad = $key ^ $opad;
return
hash('crc32', $k_opad . hash('crc32', $k_ipad . $data, true));
}
?>
To Top