// HMAC-SHA-1 for PHP

Hash-based Message Authentication Codes (HMAC)1) are very useful. Especially HMAC-SHA-1 is used by more and more webservices (e.g. Amazon S3) to verify if a request comes from a valid user (by using a shared secret/key + submitting the result of HMAC-SHA-1($request)). The easiest way to generate them – hash_hmac() – is only available for PHP > 5.1.2 and I saw many system where the function is not available. I even saw people installing the whole PEAR system just to get PEAR::Crypt_HMAC running. :-O

If you need a simple function for creating SHA-1 based HMACs, you may be interested in the following:

hmac-sha1.php
<?php
 
/**
 * Returns the HMAC-SHA-1 of a string
 *
 * @param string The data to hash.
 * @param string The key to use. Use ASCII only for best compatibility.
 *        Otherwise, you have to take care about using the same encoding in
 *        every case.
 * @param bool (optional) TRUE leads to PHP warnings if a non-ASCII-string was
 *        submitted as key. FALSE will suppress this check. Default is TRUE.
 * @return string The HMAC-SHA1 of the data.
 * @author Andreas Haerter
 * @link http://en.wikipedia.org/wiki/HMAC
 * @link http://tools.ietf.org/html/rfc2104
 * @link http://blog.andreas-haerter.com/2010/09/30/hmac-sha-1-php
 * @license GPLv2 (http://www.gnu.org/licenses/gpl2.html)
 * @license New/3-clause BSD (http://opensource.org/licenses/bsd-license.php)
 */
function hmac_sha1($str, $key, $warn_nonasciikey = true)
{
	//check: key consists of ASCII chars only?
	//this should prevent unexpected (=not equal results) when mixing this
	//implementation and base64_encode(hash_hmac("sha1", $str, $key, true))
	//regarding different encodings etc.
	if (!empty($warn_nonasciikey)
	    //search for any bytes which are outside the ASCII range...
	    //note: the regex is *REALLY* fast. Even a "quickcheck" with ctype_alnum()
	    //      won't make the things faster but slower on *common* input!
	    && preg_match('/(?:[^\x00-\x7F])/u', $key) === 1) {  //ATTENTION: single quotes are needed here! Otherwise, PCRE is not able to find the ending delimiter!
		//inform developers
		trigger_error(//text
		              __FUNCTION__.":non-ASCII key may lead to unexpected results when switching encodings!",
		              //type
		              E_USER_WARNING);
	}
 
	//use PHP's built in functionality if available (~20% faster than the
	//following script implementation)
	if (function_exists("hash_hmac")) {
		return base64_encode(hash_hmac("sha1", $str, $key, true));
	}
	//create the secret based on the given key
	$key_lenght = strlen($key);
	//key is longer than 64 bytes, use the hash of it
	if ($key_lenght > 64) {
		$key        = sha1($key);
		$key_length = 40;
	}
	//pad secret with 0x0 to get a 64 byte secret?
	if ($key_lenght < 64) {
		$secret = $key.str_repeat(chr(0), (64 - $key_lenght));
	} else {
		//64 bytes long, we can use the key directly
		$secret = $key;
	}
	//hash and return it
	return base64_encode(sha1(//create the string we have to hash
	                          ($secret^str_repeat(chr(0x5c), 64)). //pad the key for inner digest
	                          //subhash
	                          sha1(//create substring we have to hash
	                               ($secret^str_repeat(chr(0x36), 64)). //pad the key for outer digest
	                               $str,
	                               //we need RAW output!
	                               true),
	                          //we need RAW output!
	                          true));
}
 
//example
echo hmac_sha1("this is the data to hash", "my secret key, ASCII only for best compatibility");
 
?>

The source code of this function is dual-licensed under GPLv2 and New/3-clause BSD. Have fun. :-)

1)
see RFC2104 for details
I'm no native speaker (English)
Please let me know if you find any errors (I want to improve my English skills). Thank you!
Recent Comments
QR Code: URL of current page
QR Code: URL of current page start (generated for current page)