Search code examples
javanode.jspython-3.xhmacsha1

Java Hmac-Sha1 hexdigest different to Nodejs


I've been trying to get a basic HMAC-SHA1 request signing method working.

The server uses NodeJS to verify the signature. To check what I should be getting I wrote the below script to run locally (which I have tested with cURL to ensure it is generating the correct signature).

const body = JSON.stringify(require('./json-events/test-event.json'));
const crypto = require('crypto');

const digest = crypto.createHmac('sha1', secretKey)
    .update(body, 'utf8')
    .digest('hex');

console.log(digest);

Which prints out 70c244c06513c882bb8704c2d887a95a08d77f3a

The client's signing code is;

String xSignature = null;
final char[] hexArray = "0123456789abcdef".toCharArray();

try {
      SecretKeySpec key = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1");
      Mac mac = Mac.getInstance("HmacSHA1");
      mac.init(key);

      byte[] digest = mac.doFinal(body.getBytes("UTF-8"));
      char[] hexChars = new char[digest.length * 2];

      for (int j = 0; j < digest.length; ++j) {
          int v = digest[j] & 0xFF;
          hexChars[j * 2] = hexArray[v >>> 4];
          hexChars[j * 2 + 1] = hexArray[v & 0x0F];
      }

      xSignature = new String(hexChars);
  } catch (Exception e) {
    System.out.println(e.getMessage());
}

Which, when printing out xSignature it gives 5f855052675f135da151d6fa844a7678ede90afc.

To try and debug this, I used a quick python3 script to check which one isn't right.

import hashlib
import hmac
import json
from collections import OrderedDict

body = json.dumps(json.load(open("json-events/test-event.json"),
                object_pairs_hook=OrderedDict), indent=2).encode("utf-8")
sig = hmac.new(secret_key, msg=body, digestmod=hashlib.sha1).hexdigest()

print(sig)

Returns 5f855052675f135da151d6fa844a7678ede90afc, which is the same as the Java method.

Everywhere I look though, it seems that the NodeJS code is 100% correct. So I'm guessing there is a common flaw in my Java and Python code.

Any insight would be much appreciated.

Here are some of the other webpages I've looked at;

Python HMAC-SHA1 vs Java HMAC-SHA1 different results

Converting HMAC-SHA1 from node.js to Java

Preparing a string for HMAC

NodeJS Crypto Hmac class documentation

UPDATE: Using a simpler string "asdfghjkl" instead of a complex JSON string, generates the same signature. So it seems like there are some invisible characters that are changing the digest when comparing the Java/Python to the NodeJS.


Solution

  • I found out the resolution to the issue (and forgot to post the answer here). The signature is different when the order of the dictionary's key-value pairs differs. Such a simple oversight.

    So;

    {
        "key1": "Value1",
        "key2": "Value2"
    }
    

    Will produce a different digest string to;

    {
        "key2": "Value2",
        "key1": "Value1"
    }
    

    So make sure your key-values are ordered exactly the same way when calculating the HMAC-SHA1 between two languages!