skip to Main Content

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

2

Answers


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

    Login or Signup to reply.
  2. The issue you’re facing is likely related to how you’re handling the encoding of the strToSign and clientSecret in your JavaScript code compared to the Java code provided by the vendor. HMAC signatures are sensitive to the input encoding, so it’s crucial to ensure that both the key and the data being hashed are encoded in the same way. Here’s how you can translate the Java HMAC code to Node.js:

    const crypto = require('crypto');
    
    // Assuming clientSecret and strToSign are strings
    const clientSecret = 'your-client-secret';
    const strToSign = 'your-string-to-sign';
    
    // Encode the clientSecret and strToSign as UTF-8 buffers
    const secretBuffer = Buffer.from(clientSecret, 'utf8');
    const dataBuffer = Buffer.from(strToSign, 'utf8');
    
    // Create an HMAC instance with SHA-256
    const hmac = crypto.createHmac('sha256', secretBuffer);
    
    // Update the HMAC with the data to be signed
    hmac.update(dataBuffer);
    
    // Get the hexadecimal representation of the HMAC signature
    const signedStr = hmac.digest('hex');
    
    console.log(signedStr);
    

    In this code:

    1. We use Buffer.from to convert the clientSecret and strToSign strings into UTF-8 encoded buffers. This ensures that the encoding matches the Java code.

    2. We create an HMAC instance using SHA-256 and initialize it with the clientSecret buffer as the key.

    3. We update the HMAC instance with the dataBuffer, which contains the string you want to sign.

    4. Finally, we obtain the hexadecimal representation of the HMAC signature using .digest('hex').

    Ensure that you are using the correct clientSecret and strToSign values, and that they match the Java code. This should resolve the issue of the signature not matching on the server side.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search