Search code examples
javascriptjavasha256hmac

How to translate Java HMAC to Node/JavaScript?


I am trying to implement an HMAC signature in node from the vendor's provided Java example (their documentation was quite lacking, not detailing encodings, or requirements).

The vendor's provided Java code:

// encrypt strToSign by SHA algorithm
var signingKey = new SecretKeySpec(clientSecret.getBytes(), "HmacSHA256");
var mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
signedStr = bytesToHexString(mac.doFinal(strToSign.getBytes()));

function bytesToHexString(bytes) {
  if (bytes == null) {
    return null;
  }
  sb = "";
  for (var b of bytes) {
    var hv = String.format("%02x", b);
    sb += hv;
  }
  return sb.toLowerCase();
}

My JavaScript code:

var signedStr = crypto
  .createHmac('SHA256', Buffer.from(clientSecret, "utf8"))
  .update(strToSign, "utf8")
  .digest('hex');

Getting a an error: msg: 'Signature does not match' result from the server. I have tried almost every combination I can think of changing the encoding of the client secret and strToSign.

I usually can figure out how to get my problems fixed with a quick search. And I feel extremely close. However now I am at a nexus wherein I cannot quite determine what nuances between the provided Java code and my attempt at a Node translation. I feel that it is going to be related to encoding, off by one, or big endian type issue. Unfortunately with hash/signatures I cannot just brute force my way to an answer (I have tried).


Solution

  • If you inputs are already strings, you can encode them directly.

    import { Buffer } from "node:buffer";
    import crypto from "node:crypto";
    
    const encode = (secret, data) => {
      // Not needed, as string defaults to 'utf8'
      const encoding =
        typeof secret === "string"
          ? { encoding: "utf8" }
          : undefined;
      return crypto
        .createHmac("sha256", secret, encoding)
        .update(data) // If data is a string, default to 'utf8'
        .digest("hex");
    };
    
    // String
    const clientSecret = "client-secret";
    const strToSign = "string-to-sign";
    console.log(encode(clientSecret, strToSign));
    
    // Buffer
    const secretBuffer = Buffer.from(clientSecret, "utf8");
    const dataBuffer = Buffer.from(strToSign, "utf8");
    console.log(encode(secretBuffer, dataBuffer));
    

    Both calls log 7c13c6e89d4402cf331c7b0cb6a16b98f04144a247f796e6afaaae1cff64af0d