Skip to content

WebCrypto JS SHA256 HMAC Mismatch

I have two scripts that generate a SHA256 HMAC, with a plaintext message and Base64 encoded key. One is written in PHP and the other in JavaScript. The PHP script returns the correct HMAC, but for some reason the JS version does not. What is causing this?

Here are the code samples, with a redacted (still similar in nature) key.



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);

This script, returns MTYxNDExNzczNzQ2NztFdXcwQ1l0bTBTMkdIdnZ2ZnN2ZGFkTEFDMGVPbVlJeHFzZk9PQWExS1BzPQ==


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( 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", message, atob(key), true);

        btoa("1614117737467;" + hmac)

Which returns MTYxNDExNzczNzQ2NztBeGxFRVJCTzVYWm5KN2ZHNCtoeWlxalJ0VmxZQmJpekNUSEwzcldMQVhzPQ==

Why are these seemingly identical scripts returning different results?



It have to do with the differences in handling binary arrays or strings in php/javascript. If you change base64_decode($key) to $key (php) and atob(key) to key (javascript) it works fine.


The error is in unescape(encodeURIComponent(str)), just remove the functions and change to str and it should work.

User contributions licensed under: CC BY-SA
5 People found this is helpful