Hướng dẫn php hmac sha256 base64

I have to build an authorization hash from this string:

Show
kki98hkl-u5d0-w96i-62dp-xpmr6xlvfnjz:20151110171858:b2c13532-3416-47d9-8592-a541c208f755:hKSeRD98BHngrNa51Q2IgAXtoZ8oYebgY4vQHEYjlmzN9KSbAVTRvQkUPsjOGu4F

This secret is used for a HMAC hash function:

LRH9CAkNs-zoU3hxHbrtY0CUUcmqzibPeN7x6-vwNWQ=

The authorization hash I have to generate is this:

P-WgZ8CqV51aI-3TncZj5CpSZh98PjZTYxrvxkmQYmI=

There are some things to take care of:

  1. The signature have to be built with HMAC-SHA-256 as specified in RFC 2104.
  2. The signature have to be encoded with Base64 URL-compatible as specified in RFC 4648 Section 5 (Safe alphabet).

There is also some pseudo-code given for the generation:

Signatur(Request) = new String(encodeBase64URLCompatible(HMAC-SHA-256(getBytes(Z, "UTF-8"), decodeBase64URLCompatible(getBytes(S, "UTF-8")))), "UTF-8")

I tried various things in PHP but have not found the correct algorithm yet. This is the code I have now:

if(!function_exists('base64url_encode')){
    function base64url_encode($data) {
        $data = str_replace(array('+', '/'), array('-', '_'), base64_encode($data));
        return $data;
    }
}

$str = "kki98hkl-u5d0-w96i-62dp-xpmr6xlvfnjz:20151110171858:b2c13532-3416-47d9-8592-a541c208f755:hKSeRD98BHngrNa51Q2IgAXtoZ8oYebgY4vQHEYjlmzN9KSbAVTRvQkUPsjOGu4F";
$sec = "LRH9CAkNs-zoU3hxHbrtY0CUUcmqzibPeN7x6-vwNWQ=";
$signature = mhash(MHASH_SHA256, $str, $sec);
$signature = base64url_encode($signature);

if($signature != "P-WgZ8CqV51aI-3TncZj5CpSZh98PjZTYxrvxkmQYmI=")
    echo "wrong: $signature";
else
    echo "correct";

It gives this signature:

K9lw3V-k5gOedmVwmO5vC7cOn82JSEXsNguozCAOU2c=

As you can see, the length of 44 characters is correct. Please help me with finding the mistake, this simple problem takes me hours yet and there is no solution.

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

hash_hmacGenerate a keyed hash value using the HMAC method

Description

hash_hmac(
    string $algo,
    string $data,
    string $key,
    bool $binary = false
): string

Parameters

algo

Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4", etc..) See hash_hmac_algos() for a list of supported algorithms.

data

Message to be hashed.

key

Shared secret key used for generating the HMAC variant of the message digest.

binary

When set to true, outputs raw binary data. false outputs lowercase hexits.

Return Values

Returns a string containing the calculated message digest as lowercase hexits unless binary is set to true in which case the raw binary representation of the message digest is returned.

Changelog

VersionDescription
8.0.0 hash_hmac() now throws a ValueError exception if algo is unknown or is a non-cryptographic hash function; previously, false was returned instead.
7.2.0 Usage of non-cryptographic hash functions (adler32, crc32, crc32b, fnv132, fnv1a32, fnv164, fnv1a64, joaat) was disabled.

Examples

Example #1 hash_hmac() example

echo hash_hmac('ripemd160''The quick brown fox jumped over the lazy dog.''secret');
?>

The above example will output:

b8e7ae12510bdfb1812e463a7f086122cf37e4f7

See Also

  • hash() - Generate a hash value (message digest)
  • hash_hmac_algos() - Return a list of registered hashing algorithms suitable for hash_hmac
  • hash_init() - Initialize an incremental hashing context
  • hash_hmac_file() - Generate a keyed hash value using the HMAC method and the contents of a given file

Korbendallas

4 years ago

Very important notice, if you pass array to $data, php will generate a Warning, return a NULL and continue your application. Which I think is critical vulnerability as this function used to check authorisation typically.

Example:
var_dump(hash_hmac('sha256', [], 'secret'));WARNING hash_hmac() expects parameter 2 to be string, array given on line number 3
NULL
?>
Of course not documented feature.

Michiel Thalen, Thalent

5 years ago

As  Michael  uggests we should take care not to compare the hash using == (or ===). Since PHP version 5.6 we can now use hash_equals().

So the example will be:

    if (hash_equals($hashed_expected, $hashed_value) ) {
        echo
"hashes match!";
    }
?>

Michael

9 years ago

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.

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

Option 2: always compare the whole string.

    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;
    }
?>

KC Cloyd

13 years ago

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).

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:

custom_hmac

('sha1', 'Hello, world!', 'secret', true);?>

pete dot walker at NOSPAM dot me dot com

9 years ago

A function implementing the algorithm outlined in RFC 6238 (http://tools.ietf.org/html/rfc6238)

/**
* This function implements the algorithm outlined
* in RFC 6238 for Time-Based One-Time Passwords
*
* @link http://tools.ietf.org/html/rfc6238
* @param string $key    the string to use for the HMAC key
* @param mixed  $time   a value that reflects a time (unix
*                       time in the example)
* @param int    $digits the desired length of the OTP
* @param string $crypto the desired HMAC crypto algorithm
* @return string the generated OTP
*/
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;
}
?>

Siann Beck

11 years ago

For signing an Amazon AWS query, base64-encode the binary value:

  $Sig = base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
?>

havoc at NOSPAM defuse dot ca

10 years ago

Here is an efficient PBDKF2 implementation:

/*
* 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));
}
?>

Pawel M.

1 year ago

Function for those, who really need to use crc32 algorithm in PHP>7.1

    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));
    }
?>

Peter Terence Roux

11 years ago

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:-

  $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:-

  // 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);
  }
?>

josefkoh at hotmail dot com

11 years ago

Simple implementation of hmac sha1

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));
}

relevant at celsius dot ee

10 years ago

/* Here is a solution for those who used hash_hmac
   with Tiger algorithm in PHP 5.1 - 5.3
   and want to upgrade to PHP 5.4 (or newer?).

      The problem occured because the order of bytes for Tiger
   was changed to big endian since PHP 5.4.0.

      The two functions below assert $algo is one of Tiger algorithms,
   for example tiger160,4.
   */

    # 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), "
"
;
echo
hash_hmac_tiger_rev($algo, $pwd, $key), "
"
;
echo
"
"
;
echo
hash_hmac_old($algo, $pwd, $key), "
"
;
echo
hash_hmac_new($algo, $pwd, $key), "
"
;/* With PHP 5.4 output would be
   590546d9f425188da35e5dfa53306ba3953571cc
   bd6664330ed96b9b39ee063241b62e43f546a49d

   bd6664330ed96b9b39ee063241b62e43f546a49d
   590546d9f425188da35e5dfa53306ba3953571cc

       With PHP 5.3
   bd6664330ed96b9b39ee063241b62e43f546a49d
   590546d9f425188da35e5dfa53306ba3953571cc

      bd6664330ed96b9b39ee063241b62e43f546a49d
   590546d9f425188da35e5dfa53306ba3953571cc  
*/

?>

droidlabour at gmail dot com

6 years ago

For signing an Amazon AWS query, base64-encode the binary value:

echo base64_encode(hash_hmac("sha1", $Request, $AmazonSecretKey, true));
?>

brent at thebrent dot net

13 years ago

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:

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
"
";
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";
}
?>

Carlos Averett(caverett*@*corecodec,net)

14 years ago

Generating OATH-compliant OTP (one time passwords) results in 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);
}
?>

omidbahrami1990 at gmail dot com

4 years ago

This Is The Most Secure Way To Hash Your Data,
It Will Be Almost Impossible To Retrieve Your Data.
--------------------------------------------------------
--- Create Two Random Keys And Save Them In Your Configuration File ---
// Create A Random Key
echo base64_encode(openssl_random_pseudo_bytes(64));
?>
--------------------------------------------------------
// Save The Keys In Your Configuration File
define('FIRSTKEY','TNYazlbZ1Mq3HDMiEFDLrRMZBftFqpU2Ipytgytsc+jmQysE8lmigKtmGK+exB337ZOcAgwPpWmoPHL5niO3jA==');
define('SECONDKEY','z5hh/Kax4+HKZ8exOlvGlrHev/6ZynOEn904yiiIcWo/qLXWSfLkzm4NSJiGXu4uR7xxUowOkO26VqAi2p2DYQ==');
?>
--------------------------------------------------------
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;
}
?>

martin dot papik at ipsec dot info

10 years ago

Yet another OATH HOTP function. Has a 64 bit counter and is a lot shorter. Enjoy.

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;
}
?>

torben dot egmose at gmail dot com

13 years ago

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
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

"
";
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";