Using PEAR::Crypt_HMAC in PHP

By: Andi, Stig and Derick Viewed: 153249 times  Printer Friendly Format    


The Crypt_HMAC class implements the algorithm as described in RFC 2104 and can be installed with pear install crypt_hmac. Let’s look at it:

class Crypt_HMAC {

/**

* Constructor
* Pass method as first parameter
*
* @param string method - Hash function used for the calculation
* @return void
* @access public

*/

function Crypt_HMAC($key, $method = 'md5')

{

if (!in_array($method, array('sha1', 'md5'))) {

die("Unsupported hash function '$method'.");

}

$this->_func = $method;

/* Pad the key as the RFC wishes (step 1) */

if (strlen($key) > 64) {

$key = pack('H32', $method($key));

}

if (strlen($key) < 64) {

$key = str_pad($key, 64, chr(0));

}

/* Calculate the padded keys and save them (step 2 & 3) */

$this->_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36),64);

$this->_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C),64);

}

First, we make sure that the requested underlying hash function is actually supported (for now, only the built-in PHP functions md5() and sha1() are supported). Then, we create a key. Finally, in the constructor, we pre-pad and XOR the key so that the hash() method can be used several times without losing performance by padding the key every time a hash is requested:

/**

* Hashing function
*
* @param string data - string that will hashed (step 4)
* @return string
* @access public

*/

function hash($data)

{

$func = $this->_func;

$inner = pack('H32', $func($this->_ipad . $data));

$digest = $func($this->_opad . $inner);

return $digest;

}

}

?>

In the hash function, we use the pre-padded key. First, we hash the inner result. Then, we hash the outer result, which is the digest (a different name for hash) that we return.

Back to our original problem. We want to verify that no one tampered with our precious $_GET variables. Here is the second, more secure, version of our create_parameters() function:

<?php

require_once('Crypt/HMAC.php');

/* The RFC recommends a key size larger than the output hash

* for the hash function you use (16 for md5() and 20 for sha1()). */

define ('SECRET_KEY', 'Professional PHP 5 Programming Example');

function create_parameters($array)

{

$data = '';

$ret = array();

/* Construct the string with our key/value pairs */

foreach ($array as $key => $value) {

$data .= $key . $value;

$ret[] = "$key=$value";

}

$h = new Crypt_HMAC(SECRET_KEY, 'md5');

$hash = $h->hash($data);

$ret[] = "hash=$hash";

return join ('&amp;', $ret);

}

echo '<a href="script.php?'.

create_parameters(array('cause' => 'vars')).'">err!</a>';

?>

The output is

<a href="script.php?cause=vars&hash=6a0af635f1bbfb100297202ccd6dce53">err!</a>

To verify the parameters passed to the script, we can use this script:

<?php

require_once('Crypt/HMAC.php');

define ('SECRET_KEY', 'Professional PHP 5 Programming Example');

function verify_parameters($array)

{

$data = '';

$ret = array();

/* Store the hash in a separate variable and unset the hash from

* the array itself (as it was not used in constructing the hash

*/

$hash = $array['hash'];

unset ($array['hash']);

/* Construct the string with our key/value pairs */

foreach ($array as $key => $value) {

$data .= $key . $value;

$ret[] = "$key=$value";

}

$h = new Crypt_HMAC(SECRET_KEY, 'md5');

if ($hash != $h->hash($data)) {

return FALSE;

} else {

return TRUE;

}

}

/* We use a static array here, but in real life you would be using

* $array = $_GET or similar. */

$array = array(

'cause' => 'vars',

'hash' => '6a0af635f1bbfb100297202ccd6dce53'

);

if (!verify_parameters($array)) {

die("Dweep! Somebody tampered with our parameters.\n");

} else {

echo "Good guys, they didn't touch our stuff!!";

}

?>

The SHA1 hash method gives you more cryptographic strength, but both MD5 and SHA1 are adequate enough for the purpose of checking the validity of your parameters.



Most Viewed Articles (in PHP )

Latest Articles (in PHP)

Comment on this tutorial