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

参见

添加笔记

用户贡献笔记 18 条笔记

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
7 年前
正如 Michael 建议的那样,我们应该注意不要使用 ==(或 ===)比较哈希。从 PHP 5.6 版开始,我们现在可以使用 hash_equals()。

因此,示例将是

<?php
if (hash_equals($hashed_expected, $hashed_value) ) {
echo
"hashes match!";
}
?>
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
14 年前
有时托管提供商不提供对 Hash 扩展的访问。以下是一个 hash_hmac 函数的克隆,您可以在需要 HMAC 生成器且 Hash 不可用时使用它。它只能用于 MD5 和 SHA1 加密算法,但其输出与官方 hash_hmac 函数相同(至少到目前为止)。

<?php

函数 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);

如果 (
strlen($key) > $size) {
$key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
} 否则 {
$key = str_pad($key, $size, chr(0x00));
}

对于 (
$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)));

返回 (
$raw_output) ? pack($pack, $output) : $output;
}

?>

示例用法

<?php

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

?>
pete dot walker at NOSPAM dot me dot com
11 年前
实现 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
*/
函数 oauth_totp($key, $time, $digits=8, $crypto='sha256')
{
$digits = intval($digits);
$result = null;

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

// 填充到 8 个字符(如果需要)
如果 (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);

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

<?php
/*
* RSA 的 PKCS #5 定义的 PBKDF2 密钥派生函数:https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - 要使用的哈希算法。推荐:SHA256
* $password - 密码。
* $salt - 密码的唯一盐。
* $count - 迭代次数。次数越高越好,但速度越慢。推荐:至少 1024 次。
* $key_length - 派生密钥的字节长度。
* $raw_output - 如果为 true,则密钥以原始二进制格式返回。否则为十六进制编码。
* 返回值:从密码和盐派生的 $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
13 年前
为了签署 Amazon AWS 查询,请对二进制值进行 Base64 编码

<?php
$Sig
= base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
?>
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));
}
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);
}
?>
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));
}
?>
droidlabour at gmail dot com
8 年前
为了签署 Amazon AWS 查询,请对二进制值进行 Base64 编码

<?php
echo base64_encode(hash_hmac("sha1", $Request, $AmazonSecretKey, true));
?>
brent at thebrent dot net
15 年前
上面的 hotp 算法适用于小于 256 的计数器值,但由于计数器可能更大,因此需要遍历计数器的所有字节。

<?php
function oath_hotp ($key, $counter)
{
// 计数器
// 计数器值可能超过一个字节,所以我们需要多次循环
$cur_counter = array(0,0,0,0,0,0,0,0);
for(
$i=7;$i>=0;$i--)
{
$cur_counter[$i] = pack ('C*', $counter);
$counter = $counter >> 8;
}
$bin_counter = implode($cur_counter);
// 填充到 8 个字符
if (strlen ($bin_counter) < 8)
{
$bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
}

// HMAC
$hash = hash_hmac ('sha1', $bin_counter, $key);
return
$hash;
}

function
oath_truncate($hash, $length = 6)
{
// 转换为十进制
foreach(str_split($hash,2) as $hex)
{
$hmac_result[]=hexdec($hex);
}

// 查找偏移量
$offset = $hmac_result[19] & 0xf;

// 算法来自 RFC
return
(
((
$hmac_result[$offset+0] & 0x7f) << 24 ) |
((
$hmac_result[$offset+1] & 0xff) << 16 ) |
((
$hmac_result[$offset+2] & 0xff) << 8 ) |
(
$hmac_result[$offset+3] & 0xff)
) %
pow(10,$length);
}
print
"<pre>";
print
"Compare results with:";
print
" http://tools.ietf.org/html/draft-mraihi-oath-hmac-otp-04\n";
print
"Count\tHash\t\t\t\t\t\tPin\n";
for(
$i=0;$i<=1024;$i=$i+128)
{
print
$i."\t".($a=oath_hotp("12345678901234567890",$i));
print
"\t".oath_truncate($a)."\n";
}
?>
relevant at celsius dot ee
12 年前
<?php
/* 以下提供了一种解决方案,用于解决在 PHP 5.1 - 5.3 中使用 Tiger 算法的 hash_hmac 函数,
并在升级到 PHP 5.4(或更高版本)后遇到的问题。

问题在于,从 PHP 5.4.0 开始,Tiger 算法的字节顺序被更改为大端序。

以下两个函数断言 $algo 是 Tiger 算法之一,例如 tiger160,4。
*/

# 替换 hash('tiger...
function hash_tiger_rev($algo, $data, $raw_output = false) {
$len = intval(substr($algo, 5, 3)); # 128, 160 或 192 位
$times = substr($algo, 9, 1); # 3 或 4
$revhash = implode("", array_map("strrev",
str_split(hash('tiger192,'.$times, $data, true), 8)));
if (
$len < 192) $revhash = substr($revhash, 0, $len >> 3);
return
$raw_output? $revhash: bin2hex($revhash);
}

# 替换 hash_hmac('tiger...
function hash_hmac_tiger_rev($algo, $data, $key, $raw_output = false) {
if (
strlen($key) > 64) $key = hash_tiger_rev($algo, $key);
$key = str_pad($key, 64, chr(0));
$o_pad = str_repeat("\\", 64) ^ $key; # "\" = chr(0x5C)
$i_pad = str_repeat("6", 64) ^ $key; # "6" = chr(0x36)
return hash_tiger_rev($algo, $o_pad .
hash_tiger_rev($algo, $i_pad . $data, true), $raw_output);
}

# 始终使用新的 Tiger 版本
function hash_hmac_new($algo, $data, $key, $raw_output = false) {
if (
phpversion() > '5.4' || !preg_match('/^tiger(128|160|192),(3|4)$/', $algo))
return
hash_hmac($algo, $data, $key, $raw_output);
else
return
hash_hmac_tiger_rev($algo, $data, $key, $raw_output);
}

# 始终使用旧的 Tiger 版本
function hash_hmac_old($algo, $data, $key, $raw_output = false) {
if (
phpversion() < '5.4' || !preg_match('/^tiger(128|160|192),(3|4)$/', $algo))
return
hash_hmac($algo, $data, $key, $raw_output);
else
return
hash_hmac_tiger_rev($algo, $data, $key, $raw_output);
}

# 进行测试
$algo = 'tiger160,4'; $pwd = 'foo'; $key = 'bar';
echo
hash_hmac($algo, $pwd, $key), "<br>";
echo
hash_hmac_tiger_rev($algo, $pwd, $key), "<br>";
echo
"<br>";
echo
hash_hmac_old($algo, $pwd, $key), "<br>";
echo
hash_hmac_new($algo, $pwd, $key), "<br>";

/* 使用 PHP 5.4 的输出将是
590546d9f425188da35e5dfa53306ba3953571cc
bd6664330ed96b9b39ee063241b62e43f546a49d

bd6664330ed96b9b39ee063241b62e43f546a49d
590546d9f425188da35e5dfa53306ba3953571cc

使用 PHP 5.3 的输出将是
bd6664330ed96b9b39ee063241b62e43f546a49d
590546d9f425188da35e5dfa53306ba3953571cc

bd6664330ed96b9b39ee063241b62e43f546a49d
590546d9f425188da35e5dfa53306ba3953571cc
*/

?>
omidbahrami1990 at gmail dot com
6 年前
这是对您的数据进行哈希处理最安全的方式,
几乎不可能检索您的数据。
--------------------------------------------------------
--- 创建两个随机密钥并将其保存在您的配置文件中 ---
<?php
// 创建一个随机密钥
echo base64_encode(openssl_random_pseudo_bytes(64));
?>
--------------------------------------------------------
<?php
// 将密钥保存在您的配置文件中
define('FIRSTKEY','TNYazlbZ1Mq3HDMiEFDLrRMZBftFqpU2Ipytgytsc+jmQysE8lmigKtmGK+exB337ZOcAgwPpWmoPHL5niO3jA==');
define('SECONDKEY','z5hh/Kax4+HKZ8exOlvGlrHev/6ZynOEn904yiiIcWo/qLXWSfLkzm4NSJiGXu4uR7xxUowOkO26VqAi2p2DYQ==');
?>
--------------------------------------------------------
<?php
function secured_hash($data)
{
$first_key = base64_decode(FIRSTKEY);
$second_key = base64_decode(SECONDKEY);

$first_hashed = hash_hmac('sha3-512', $data, $first_key, TRUE);
$second_hashed = hash_hmac('sha3-512', $first_hashed, $second_key);

return
$second_hashed;
}
?>
Carlos Averett(caverett*@*corecodec,net)
16 年前
在 PHP 中生成符合 OATH 标准的 OTP(一次性密码)

<?php
$otp
= oath_truncate (oath_hotp ($key, $counter), $length);
function
oath_hotp ($key, $counter) {
// 计数器
$bin_counter = pack ('C*', $counter);

// 填充到 8 个字符
if (strlen ($bin_counter) < 8) {
$bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
}

// HMAC
$hash = hash_hmac ('sha1', $bin_counter, $key);
return
$hash;
}

function
oath_truncate ($hash, $length = 6) {
// 最后一个字节用作偏移量
$offset = hexdec (substr ($hash, 38)) & 0xf;

// 提取相关部分,并清除第一个位
$hex_truncated = substr ($hash, $offset * 2, 8);
$bin_truncated = decbin (hexdec ($hex_truncated));
$bin_truncated[0] = '0';
$dec_truncated = bindec ($bin_truncated);

return
substr ($dec_truncated, 0 - $length);
}
?>
torben dot egmose at gmail dot com
15 年前
根据 RCF http://tools.ietf.org/html/draft-mraihi-oath-hmac-otp-04 工作的 HOTP 算法
RCF 文档中的测试用例将 ASCII 字符串定义为“123456787901234567890”。
但十六进制解码后的字符串为“12345678901234567890”。
Secret="12345678901234567890";
计数
0 755224
1 287082
<?php
function oath_hotp($key,$counter) {

// 转换为填充的二进制字符串
$data = pack ('C*', $counter);
$data = str_pad($data,8,chr(0),STR_PAD_LEFT);

// HMAC
return hash_hmac('sha1',$data,$key);
}

function
oath_truncate($hash, $length = 6) {

// 转换为十进制
foreach(str_split($hash,2) as $hex) {
$hmac_result[]=hexdec($hex);
}

// 查找偏移量
$offset = $hmac_result[19] & 0xf;

// 来自 RFC 的算法
return (
((
$hmac_result[$offset+0] & 0x7f) << 24 ) |
((
$hmac_result[$offset+1] & 0xff) << 16 ) |
((
$hmac_result[$offset+2] & 0xff) << 8 ) |
(
$hmac_result[$offset+3] & 0xff)
) %
pow(10,$length);
}

print
"<pre>";
print
"与以下结果进行比较:"
print " http://tools.ietf.org/html/draft-mraihi-oath-hmac-otp-04\n";
print
"计数\t哈希值\t\t\t\t\t\tPin\n";
for(
$i=0;$i<10;$i++)
print
$i."\t".($a=oath_hotp("12345678901234567890",$i))
print
"\t".oath_truncate($a)."\n";
martin dot papik at ipsec dot info
12 年前
另一个 OATH HOTP 函数。拥有 64 位计数器,并且代码更短。请享用。

<?php

function oath_hotp ($secret, $ctr, $len=6) {
$binctr = pack ('NNC*', $ctr>>32, $ctr & 0xFFFFFFFF);
$hash = hash_hmac ("sha1", $binctr, $secret);
// 这是哈希结束和截断开始的地方
$ofs = 2*hexdec (substr ($hash, 39, 1));
$int = hexdec (substr ($hash, $ofs, 8)) & 0x7FFFFFFF;
$pin = substr ($int, -$len);
$pin = str_pad ($pin, $len, "0", STR_PAD_LEFT);
return
$pin;
}

?>
To Top