// 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!
QR Code: URL of current page
QR Code: URL of current page start (generated for current page)