How to get HMAC with Crypto Web API

17k Views Asked by At

How can I get HMAC-SHA512(key, data) in the browser using Crypto Web API (window.crypto)?

Currently I am using CryptoJS library and it is pretty simple:

CryptoJS.HmacSHA512("myawesomedata", "mysecretkey").toString();

Result is 91c14b8d3bcd48be0488bfb8d96d52db6e5f07e5fc677ced2c12916dc87580961f422f9543c786eebfb5797bc3febf796b929efac5c83b4ec69228927f21a03a.

I want to get rid of extra dependencies and start using Crypto Web API instead. How can I get the same result with it?

3

There are 3 best solutions below

1
Stepan Snigirev On BEST ANSWER

Answering my own question. The code below returns the same result as CryptoJS.HmacSHA512("myawesomedata", "mysecretkey").toString();

There are promises everywhere as WebCrypto is asynchronous:

// encoder to convert string to Uint8Array
var enc = new TextEncoder("utf-8");

window.crypto.subtle.importKey(
    "raw", // raw format of the key - should be Uint8Array
    enc.encode("mysecretkey"),
    { // algorithm details
        name: "HMAC",
        hash: {name: "SHA-512"}
    },
    false, // export = false
    ["sign", "verify"] // what this key can do
).then( key => {
    window.crypto.subtle.sign(
        "HMAC",
        key,
        enc.encode("myawesomedata")
    ).then(signature => {
        var b = new Uint8Array(signature);
        var str = Array.prototype.map.call(b, x => x.toString(16).padStart(2, '0')).join("")
        console.log(str);
    });
});
0
Stephen Blum On

Async/Await Crypto Subtle HMAC SHA-256/512 with Base64 Digest

The following is a copy of the ✅ answer. This time we are using async/await for clean syntax. This approach also offers a base64 encoded digest.

  • secret is the secret key that will be used to sign the body.
  • body is the string-to-sign.
  • enc is a text encoder that converts the UTF-8 to JavaScript byte arrays.
  • algorithm is a JS object which is used to identify the signature methods.
  • key is a CryptoKey.
  • signature is the byte array hash.
  • digest is the base64 encoded signature.

The JavaScript code follows:

(async ()=>{
'use strict';

let secret = "sec-demo"; // the secret key
let enc = new TextEncoder("utf-8");
let body = "GET\npub-demo\n/v2/auth/grant/sub-key/sub-demo\nauth=myAuthKey&g=1&target-uuid=user-1&timestamp=1595619509&ttl=300";
let algorithm = { name: "HMAC", hash: "SHA-256" };

let key = await crypto.subtle.importKey("raw", enc.encode(secret), algorithm, false, ["sign", "verify"]);
let signature = await crypto.subtle.sign(algorithm.name, key, enc.encode(body));
let digest = btoa(String.fromCharCode(...new Uint8Array(signature)));

console.log(digest);

})();

The original answer on this page was helpful in a debugging effort earlier today. We're using it to help identify a bug in our documentation for creating signatures for granting access tokens to use APIs with read/write permissions.

6
ikhvjs On

Somehow @StephenBlum's answer doesn't work for me.

I rewrite @StepanSnigirev' answer as async below instead.

"use strict";
(async () => {
    const secret = "mysecretkey";
    const enc = new TextEncoder();
    const body = "myawesomedata";
    const algorithm = { name: "HMAC", hash: "SHA-512" };

    const key = await crypto.subtle.importKey(
        "raw",
        enc.encode(secret),
        algorithm,
        false,
        ["sign", "verify"]
    );

    const signature = await crypto.subtle.sign(
        algorithm.name,
        key,
        enc.encode(body)
    );

    // convert buffer to byte array
    const hashArray = Array.from(new Uint8Array(signature));

    // convert bytes to hex string
    const digest = hashArray
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");

    console.log(digest);
})();

Note: We cannot use new Uint8Array(arrayBuffer).map(...). Although Uint8Array implements the ArrayLike interface, its map method will return another Uint8Array which cannot contain strings (hex octets in our case), hence the Array.from hack

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array#instance_properties