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