WebCrypto JS SHA256 HMAC 不匹配

问题描述

我有两个脚本可以生成 SHA256 HMAC,带有纯文本消息和 Base64 编码密钥。一个是用 PHP 编写的,另一个是用 JavaScript 编写的。 PHP 脚本返回正确的 HMAC,但由于某种原因 JS 版本没有。这是什么原因造成的?

以下是代码示例,其中包含一个经过编辑(本质上仍然相似)的键。

PHP

<?PHP

header("content-type: text/plain");

$key = "YdkQZp9Pq0OsKT5TlFzrgry7j1nw0XEmbNFm86zNU3+XFemm/I+WxrAZE7yjFAD3iWJTQ10VN2+JK3fz4b3Viw==";
$message = "1614117737467myJSON.json" . '{"json_data": "to-be-encoded"}';

$hmac = base64_encode(hash_hmac('sha256',$message,base64_decode($key),true));

// to base64
echo base64_encode("1614117737467;" . $hmac);

这个脚本,返回MTYxNDExNzczNzQ2NztFdxcwQ1l0bTBTMkdIdnZ2ZnN2ZGFkTEFDMGVPbVlJeHFzZk9PQWExS1BzPQ==

JS

async function hash_hmac(type,message,key,base64) {
    const getUtf8Bytes = str =>
        new Uint8Array(
        [...unescape(encodeURIComponent(str))].map(c => c.charCodeAt(0))
    );

    const keyBytes = getUtf8Bytes(key);
    const messageBytes = getUtf8Bytes(message);

    const cryptoKey = await crypto.subtle.importKey(
        "raw",keyBytes,{ name: "HMAC",hash: type },true,["sign"]
    );

    const sig = await crypto.subtle.sign("HMAC",cryptoKey,messageBytes);

    const data = String.fromCharCode(...new Uint8Array(sig));

    return base64 ? btoa(data) : data;
}

(async function() {
    let key = "YdkQZp9Pq0OsKT5TlFzrgry7j1nw0XEmbNFm86zNU3+XFemm/I+WxrAZE7yjFAD3iWJTQ10VN2+JK3fz4b3Viw==";
    let message = "1614117737467myJSON.json" + '{"json_data": "to-be-encoded"}';
    
    let hmac = await hash_hmac("SHA-256",atob(key),true);

    console.log(
        btoa("1614117737467;" + hmac)
    );
})();

返回 MTYxNDExNzczNzQ2NztBeGxFRVJCTzVYWm5KN2ZHNCtoeWlxalJ0VmxZQmJpekNUSEwzcldMQVhzPQ==

为什么这些看似相同的脚本返回不同的结果?

解决方法

这与在 php/javascript 中处理二进制数组或字符串的差异有关。如果您将 base64_decode($key) 更改为 $key (php) 并将 atob(key) 更改为 key (javascript),它可以正常工作。

编辑:

错误在 unescape(encodeURIComponent(str)) 中,只需删除函数并更改为 str 即可。