PHP Conference Japan 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'));

警告:hash_hmac() 預期參數 2 為字串,給定陣列,在第 3 行
NULL
?>
當然,這不是文件中記載的功能。
Michael
11 年前
比較雜湊值時請務必小心。在某些情況下,使用計時攻擊可能會洩漏資訊。這是利用 == 運算子只比較到發現兩個字串之間存在差異為止的特性。為了防止這種情況,您有兩個選項。

選項 1:先將兩個雜湊值再次雜湊 - 這不會阻止計時差異,但會使洩漏的資訊變得無用。

<?php
if (md5($hashed_value) === md5($hashed_expected)) {
echo
"雜湊值相符!";
}
?>

選項 2:總是比較整個字串。

<?php
if (hash_compare($hashed_value, $hashed_expected)) {
echo
"雜湊值相符!";
}

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 擴充功能的存取權限。如果您需要 HMAC 產生器,但 Hash 功能無法使用,這裡提供一個 hash_hmac 函式的複製版本。它僅適用於 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 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 1024.
* $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 defuse.ca
* With improvements by 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));
}
?>
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 = $st+$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++)
{
// 對每次迭代結果進行 XOR 運算
$ib ^= ($h = hash_hmac($a, $h, $p, true));
}

$dk .= $ib; // 附加迭代後的區塊
}

// 返回正確長度的衍生金鑰
return substr($dk, $st, $kl);
}
?>
josefkoh at hotmail dot com
13 年前
hmac sha1 的簡單實作

<?php

函式 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 進行 XOR 運算
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
函數 hash_hmac_crc32(字串 $key, 字串 $data): 字串
{
$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