数据防篡改之HMAC

前言

有一串不希望做任何修改的带参数的URL,一般做法是按key=value[&]拼接参数并散列函数加密,再将加密值附在URL上,服务器收到请求后对请求参数以同样的方式获得加密值并比对。但这样任然有可能被伪造。随之产生了HMAC(Hash-based Message Authentication Code)解决方案。

实现原理

HMAC算法使用一个加密的键值对明文进行双重散列处理

  1. 如果键值的长度小于64字节,就用\0把键值扩展到64字节;如果键值长度大于64字节,先用散列函数处理(一般散列函数处理后长度为64字节,若不足则\0填充)
  2. 构建 opad (用0x5C异或的64字节的键值)和 ipad (用0x36异或的64字节的键值)
  3. 散列函数处理ipad拼接明文
  4. 散列函数处理opad拼接第三部得到值

其公式如下:H(K XOR opad, H(K XOR ipad, text))

  • H:用到的散列函数(md5, sha1等等)
  • K:用零(0x0)扩展到64字节的键值
  • opad:64字节长度的0x5C
  • ipad:64字节长度的0x36
  • text:明文

PHP实现

1. 调用内置的函数hash_hmac('md5', $text, $K)

2. 根据上述原理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Crypt_HMAC {
private $_func, $_ipad, $_opad;

function __construct($key, $method="md5")
{
if (!in_array($method, ['sha1', 'md5'])) {
die("Unsupported hash function '$method'.");
}
$this->_func = $method;

// 填充关键字
if( strlen($key) > 64) {
$key = pack('H32', $method($key));
}

if( strlen($key) < 64 ) {
$key = str_pad($key, 64, chr(0));
}

// 计算填充的关键字, 并且保存他们
$this->_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$this->_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
}

function hash($data)
{
$func = $this->_func;
$inner = pack('H32', $func($this->_ipad . $data));
$digest = $func($this->_opad . $inner);

return $digest;
}
}

// 测试
// 键值K
$secretKey = 'secret';
// 明文text
$data = 'The quick brown fox jumped over the lazy dog.';

// 使用内置函数
echo hash_hmac('md5', $data, $secretKey);
echo PHP_EOL;

$h = new Crypt_HMAC($secretKey, 'md5');
$hash = $h->hash($data);
echo $hash;
echo PHP_EOL;

后记

使用HMAC可以有效的防止数据被伪造。本文参考了PHP5权威编程,感谢作者。