<?php

/**
 * @param string $key
 * @param string $text
 * @param callable $hash_func callable(string, bool)
 * @param bool $raw
 * @return string
 *
 * HMAC用公式表示:
 * H(K XOR opad, H(K XOR ipad, text))
 * 其中
 *      H:hash算法,比如(MD5,SHA-1,SHA-256)
 *      B:块字节的长度,块是hash操作的基本单位。这里B=64。
 *      L:hash算法计算出来的字节长度。(L=16 for MD5, L=20 for SHA-1)。
 *      K:共享密钥,K的长度可以是任意的,但是为了安全考虑,还是推荐K的长度>B。
 *          当K长度大于B时候,会先在K上面执行hash算法,将得到的L长度结果作为新的共享密钥。
 *          如果K的长度<B, 那么会在K后面填充0x00一直到等于长度B。
 *      text: 要加密的内容
 *      opad:外部填充常量,是 0x5C 重复B次。
 *      ipad: 内部填充常量,是0x36 重复B次。
 *      XOR: 异或运算。
 * 计算步骤如下:
 * 1. 将0x00填充到K的后面,直到其长度等于B。
 * 2. 将步骤1的结果跟 ipad做异或。
 * 3. 将要加密的信息附在步骤2的结果后面。
 * 4. 调用H方法。
 * 5. 将步骤1的结果跟opad做异或。
 * 6. 将步骤4的结果附在步骤5的结果后面。
 * 7. 调用H方法。
 */
function my_hmac($key, $text, $hash_func, $raw = false)
{
    $b = 64;
    $o_pad = str_repeat("\x5c", $b);
    $i_pad = str_repeat("\x36", $b);

    if (strlen($key) > $b) {
        $key = $hash_func($key, true);
    }
    if (strlen($key) < $b) {
        $pad = str_repeat("\x00", $b - strlen($key));
        $key .= $pad;
    }

    $i_key_pad = $key ^ $i_pad;
    $o_key_pad = $key ^ $o_pad;

    $i_text = $i_key_pad . $text;
    $i_hash = $hash_func($i_text, true);

    $o_text = $o_key_pad . $i_hash;
    $o_hash = $hash_func($o_text, $raw);

    return $o_hash;
}
$key = str_repeat('key1', 2);
$data = 'data1';

foreach (['sha1', 'md5', 'sha256', "ripemd128", "ripemd256"] as $algo) {
    echo $algo . "\n";

    $myhash = my_hmac($key, $data, function ($data, $raw = false) use ($algo) {
        return hash($algo, $data, $raw);
    });
    $syshash = hash_hmac($algo, $data, $key);
    echo "\t" . $myhash  . "\n";
    echo "\t" . $syshash . "\n";
    if ($myhash != $syshash) {
        echo "错误\n";
    }
}