©
This document uses PHP Chinese website manual Release
(PHP 5 >= 5.1.2, PECL hash >= 1.1)
hash_hmac — 使用 HMAC 方法生成带有密钥的哈希值
$algo
, string $data
, string $key
[, bool $raw_output
= false
] )
algo
要使用的哈希算法名称,例如:"md5","sha256","haval160,4" 等。 如何获取受支持的算法清单,请参见 hash_algos() 。
data
要进行哈希运算的消息。
key
使用 HMAC 生成信息摘要时所使用的密钥。
raw_output
设置为 TRUE
输出原始二进制数据,
设置为 FALSE
输出小写 16 进制字符串。
如果 raw_output
设置为 TRUE
, 则返回原始二进制数据表示的信息摘要,
否则返回 16 进制小写字符串格式表示的信息摘要。
如果 algo
参数指定的不是受支持的算法,返回 FALSE
。
Example #1 hash_hmac() 例程
<?php
echo hash_hmac ( 'ripemd160' , 'The quick brown fox jumped over the lazy dog.' , 'secret' );
?>
以上例程会输出:
b8e7ae12510bdfb1812e463a7f086122cf37e4f7
[#1] Michael [2013-02-19 20:52:21]
Please be careful when comparing hashes. In certain cases, information can be leaked by using a timing attack. It takes advantage of the == operator only comparing until it finds a difference in the two strings. To prevent it, you have two options.
Option 1: hash both hashed strings first - this doesn't stop the timing difference, but it makes the information useless.
<?php
if (md5($hashed_value) === md5($hashed_expected)) {
echo "hashes match!";
}
?>
Option 2: always compare the whole string.
<?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;
}
?>
[#2] pete dot walker at NOSPAM dot me dot com [2012-10-08 14:13:00]
A function implementing the algorithm outlined in RFC 6238 (http://tools.ietf.org/html/rfc6238)
<?php
function oauth_totp($key, $time, $digits=8, $crypto='sha256')
{
$digits = intval($digits);
$result = null;
// Convert counter to binary (64-bit)
$data = pack('NNC*', $time >> 32, $time & 0xFFFFFFFF);
// Pad to 8 chars (if necessary)
if (strlen ($data) < 8) {
$data = str_pad($data, 8, chr(0), STR_PAD_LEFT);
}
// Get the hash
$hash = hash_hmac($crypto, $data, $key);
// Grab the offset
$offset = 2 * hexdec(substr($hash, strlen($hash) - 1, 1));
// Grab the portion we're interested in
$binary = hexdec(substr($hash, $offset, 8)) & 0x7fffffff;
// Modulus
$result = $binary % pow(10, $digits);
// Pad (if necessary)
$result = str_pad($result, $digits, "0", STR_PAD_LEFT);
return $result;
}
?>
[#3] relevant at celsius dot ee [2012-07-14 12:52:23]
<?php
# replaces hash('tiger...
function hash_tiger_rev($algo, $data, $raw_output = false) {
$len = intval(substr($algo, 5, 3)); # 128, 160 or 192 bits
$times = substr($algo, 9, 1); # 3 or 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);
}
# replaces 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);
}
# always the new version of 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);
}
# always the old version of 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);
}
# let's test it
$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>";
?>
[#4] havoc at NOSPAM defuse dot ca [2012-06-30 16:30:00]
Here is an efficient PBDKF2 implementation:
<?php
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));
}
?>
[#5] martin dot papik at ipsec dot info [2012-06-09 07:38:58]
Yet another OATH HOTP function. Has a 64 bit counter and is a lot shorter. Enjoy.
<?php
function oath_hotp ($secret, $ctr, $len=6) {
$binctr = pack ('NNC*', $ctr>>32, $ctr & 0xFFFFFFFF);
$hash = hash_hmac ("sha1", $binctr, $secret);
// This is where hashing stops and truncation begins
$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;
}
?>
[#6] josefkoh at hotmail dot com [2011-07-27 10:24:07]
Simple implementation of hmac sha1
<?php
function hmac_sha1($key, $data)
{
// Adjust key to exactly 64 bytes
if (strlen($key) > 64) {
$key = str_pad(sha1($key, true), 64, chr(0));
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
// Outter and Inner pad
$opad = str_repeat(chr(0x5C), 64);
$ipad = str_repeat(chr(0x36), 64);
// Xor key with 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));
}
[#7] Peter Terence Roux [2010-12-23 03:44:48]
The Implementation of the PBKDF2 key derivation function as described in RFC 2898 can be used to not only get the hashed KEY but also a specific IV.
To use, one would use it as follows:-
<?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);
?>
The function should be:-
<?php
// PBKDF2 Implementation (described in RFC 2898)
//
// @param string p password
// @param string s salt
// @param int c iteration count (use 1000 or higher)
// @param int kl derived key length
// @param string a hash algorithm
// @param int st start position of result
//
// @return string derived key
function str_hash_pbkdf2($p, $s, $c, $kl, $a = 'sha256', $st=0)
{
$kb = $start+$kl; // Key blocks to compute
$dk = ''; // Derived key
// Create key
for ($block=1; $block<=$kb; $block++)
{
// Initial hash for this block
$ib = $h = hash_hmac($a, $s . pack('N', $block), $p, true);
// Perform block iterations
for ($i=1; $i<$c; $i++)
{
// XOR each iterate
$ib ^= ($h = hash_hmac($a, $h, $p, true));
}
$dk .= $ib; // Append iterated block
}
// Return derived key of correct length
return substr($dk, $start, $kl);
}
?>
[#8] Siann Beck [2010-10-10 09:48:32]
For signing an Amazon AWS query, base64-encode the binary value:
<?php
$Sig = base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
?>
[#9] KC Cloyd [2009-09-09 23:16:30]
Sometimes a hosting provider doesn't provide access to the Hash extension. Here is a clone of the hash_hmac function you can use in the event you need an HMAC generator and Hash is not available. It's only usable with MD5 and SHA1 encryption algorithms, but its output is identical to the official hash_hmac function (so far at least).
<?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;
}
?>
Example Use:
<?php
custom_hmac('sha1', 'Hello, world!', 'secret', true);
?>
[#10] brent at thebrent dot net [2009-05-21 08:17:56]
The hotp algorithms above work with counter values less than 256, but since the counter can be larger, it's necessary to iterate through all the bytes of the counter:
<?php
function oath_hotp ($key, $counter)
{
// Counter
//the counter value can be more than one byte long, so we need to go multiple times
$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);
// Pad to 8 chars
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)
{
// Convert to dec
foreach(str_split($hash,2) as $hex)
{
$hmac_result[]=hexdec($hex);
}
// Find offset
$offset = $hmac_result[19] & 0xf;
// Algorithm from 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";
}
?>
[#11] torben dot egmose at gmail dot com [2009-03-22 11:40:43]
HOTP Algorithm that works according to the RCF http://tools.ietf.org/html/draft-mraihi-oath-hmac-otp-04
The test cases from the RCF document the ASCII string as "123456787901234567890".
But the hex decoded to a string is "12345678901234567890".
Secret="12345678901234567890";
Count:
0 755224
1 287082
<?php
function oath_hotp($key,$counter) {
// Convert to padded binary string
$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) {
// Convert to dec
foreach(str_split($hash,2) as $hex) {
$hmac_result[]=hexdec($hex);
}
// Find offset
$offset = $hmac_result[19] & 0xf;
// Algorithm from 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<10;$i++)
print $i."\t".($a=oath_hotp("12345678901234567890",$i))
print "\t".oath_truncate($a)."\n";
[#12] Carlos Averett(caverett*@*corecodec,net) [2008-07-03 15:54:52]
Generating OATH-compliant OTP (one time passwords) results in PHP:
<?php
$otp = oath_truncate (oath_hotp ($key, $counter), $length);
function oath_hotp ($key, $counter) {
// Counter
$bin_counter = pack ('C*', $counter);
// Pad to 8 chars
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) {
// The last byte is used as an offset
$offset = hexdec (substr ($hash, 38)) & 0xf;
// Extract the relevant part, and clear the first bit
$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);
}
?>