• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • hash_hmac()

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

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

    说明

    hash_hmac(string $algo,string $data,string $key[,bool $raw_output=FALSE]): string

    参数

    $algo

    要使用的哈希算法名称,例如:"md5","sha256","haval160,4"等。如何获取受支持的算法清单,请参见hash_hmac_algos()函数。

    $data

    要进行哈希运算的消息。

    $key

    使用 HMAC 生成信息摘要时所使用的密钥。

    $raw_output

    设置为TRUE输出原始二进制数据,设置为FALSE输出小写 16 进制字符串。

    返回值

    如果$raw_output设置为TRUE,则返回原始二进制数据表示的信息摘要,否则返回 16 进制小写字符串格式表示的信息摘要。如果$algo参数指定的不是受支持的算法,返回FALSE

    更新日志

    版本说明
    7.2.0不再支持非加密的哈希函数(adler32,crc32,crc32b,fnv132,fnv1a32,fnv164,fnv1a64,joaat)。

    范例

    Example #1hash_hmac()例程

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

    以上例程会输出:

    b8e7ae12510bdfb1812e463a7f086122cf37e4f7
    

    参见

    • hash() 生成哈希值(消息摘要)
    • hash_hmac_algos()Return a list of registered hashing algorithms suitable for hash_hmac
    • hash_init() 初始化增量哈希运算上下文
    • hash_hmac_file() 使用 HMAC 方法和给定文件的内容生成带密钥的哈希值
    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:
    <?php
    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.
    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;
      }
    ?>
    
    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:
    <?php
      if (hash_equals($hashed_expected, $hashed_value) ) {
        echo "hashes match!";
      }
    ?>
    
    Here is an efficient PBDKF2 implementation:
    <?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));
    }
    ?>
    
    For signing an Amazon AWS query, base64-encode the binary value:
    <?php
     $Sig = base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
    ?>
    
    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);
    ?>
    
    A function implementing the algorithm outlined in RFC 6238 (http://tools.ietf.org/html/rfc6238)
    <?php
    /**
     * 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;
    }
    ?>
    
    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));
    }
    For signing an Amazon AWS query, base64-encode the binary value:
    <?php
    echo base64_encode(hash_hmac("sha1", $Request, $AmazonSecretKey, true));
    ?>
    
    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);
     }
    ?>
    
    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";
    }
    ?>
    
    <?php 
    /* 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), "<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>";
    /* With PHP 5.4 output would be 
      590546d9f425188da35e5dfa53306ba3953571cc
      bd6664330ed96b9b39ee063241b62e43f546a49d
      bd6664330ed96b9b39ee063241b62e43f546a49d
      590546d9f425188da35e5dfa53306ba3953571cc  
      
      With PHP 5.3 
      bd6664330ed96b9b39ee063241b62e43f546a49d
      590546d9f425188da35e5dfa53306ba3953571cc
      
      bd6664330ed96b9b39ee063241b62e43f546a49d
      590546d9f425188da35e5dfa53306ba3953571cc  
    */
    ?>
    
    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 ---
    <?php
    // Create A Random Key
    echo base64_encode(openssl_random_pseudo_bytes(64));
    ?>
    --------------------------------------------------------
    <?php
    // Save The Keys In Your Configuration File
    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;
    }
    ?>
    
    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);
    }
    ?>
    
    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;
    }
    ?>
    
    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";

    上篇:hash_hmac_file()

    下篇:hash_init()