2
votes

Using Nodejs, I'm attempting to generate a HMAC SHA256, base64-encoded signature of an XML message. I currently have the signature generation working using PHP.

The process seems fairly straightforward, and I'm able to generate base64-encoded value with Nodejs but, for some reason, the value does not match and is much shorter than what I get using PHP.

Below, I've included an example PHP script and result as well as the Nodejs implementation and result. With Nodejs, I'm using the native crypto module.

// PHP implementation

$xml = <<<EOD
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <record>
    <id>1</id>
    <first_name>Carlos</first_name>
    <last_name>Ruiz</last_name>
    <email>[email protected]</email>
    <gender>Male</gender>
    <ip_address>156.225.191.154</ip_address>
  </record>
</dataset>
EOD;

$secret = 'secret';
$sig = base64_encode(hash_hmac('sha256', $xml, $secret));

echo $sig;

Result: ODhkYTc1YmQzNzc0NWUyNDJlNjY3YTY1NzZhYzFhZGYwOTJlMTIxODdjNzYxOWYyNGQxNGExOGVkYTIyZDQ0ZQ==

// Nodejs implementation

var crypto = require('crypto');

fs.readFile('example.xml', 'utf-8', function(err, data) {
  function sig(str, key) {
    return crypto.createHmac('sha256', key)
      .update(str)
      .digest('base64');
  }
  console.log(sig(data, 'secret'));
});

Result: iNp1vTd0XiQuZnpldqwa3wkuEhh8dhnyTRShjtoi1E4=

I've spent the day trying to figure this out and after a year+ of using Stack Overflow, this is my first question.

Any assistance would be greatly appreciated!

1
I notice in your first example your xml file is not a file, but a string. So.... what's the actual xml in example.xml? That said, a sha256 print is 256 bits, or 32 bytes, which base64 conversion makes about 4/3rd larger, so we should expect the base64 HMAC print to be around 42 character long. As such, the result from Node.js certainly looks the right length (43 characters with a padding = to align to 4 bytes), whereas the PHP string absolutely does not. It's waaaaay too long.Mike 'Pomax' Kamermans
Thanks for the reply, from mscdex's answer I was able to figure out what I needed to do. As he explained, in my example, hash_hmac was returning a hex-encoded string which was then base64-encoded. Using that information I was able to get my nodejs code working by using 'hex' as the digest, converting to a string and encoding that string as base64.oudelitonalituridalp
as long as you are extremely aware that you are not generating base64 encoded HMAC values, but a twice-asciified string, which is an excellent sleeper bug in your software right there. Making the consuming code expect a base64-encoded true SHA-256 HMAC value is an infinitely better solution here.Mike 'Pomax' Kamermans
The big picture of this post is: a PHP server (which I do not control) sends an XML POST with a header value that is equal to the base64-encoded HMAC value, using a shared key, and the body of the POST. I believe that, while taking into account what you have stated, all should be OK. Thanks again for your comments!oudelitonalituridalp

1 Answers

2
votes

The problem here is that PHP's hash_hmac() returns a hex-encoded string by default (see the $raw_output parameter here), so you are base64-encoding a hex string instead of the actual of the raw, binary result.

So change this:

$sig = base64_encode(hash_hmac('sha256', $xml, $secret));

to:

$sig = base64_encode(hash_hmac('sha256', $xml, $secret, true));