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.
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);
This script, returns 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", message, atob(key), true); console.log( btoa("1614117737467;" + hmac) ); })();
Which returns MTYxNDExNzczNzQ2NztBeGxFRVJCTzVYWm5KN2ZHNCtoeWlxalJ0VmxZQmJpekNUSEwzcldMQVhzPQ==
Why are these seemingly identical scripts returning different results?
Advertisement
Answer
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.
Edit:
The error is in unescape(encodeURIComponent(str))
, just remove the functions and change to str
and it should work.