15

I have code that is working in my PHP app. In the PHP I sign the url with the following code:

private static function __getHash($string)
{
    return hash_hmac('sha1', $string, self::$__secretKey, true);    
}

I am attempting to sign the URL in the same way in a Node.js application. This is what I'm trying:

S3.prototype.getHash = function(string){
    var key = this.secret_key; 
    var hmac = crypto.createHash('sha1', key);
    hmac.update(string); 
    return hmac.digest('binary'); 
}; 

However, I am getting the following error:

The request signature we calculated does not match the signature you provided. Check your key and signing method.

Do these pieces of code do the same thing? Am I missing something?

Sara Fuerst
  • 5,688
  • 8
  • 43
  • 86
  • Compare the outputs of the two hashes to see if they do the same thing. – Brody Apr 28 '16 at 21:32
  • For a start the `crypto.createHash` method in Node doesn't take a key because it calculates a hash not an HMAC. – Chris Apr 28 '16 at 23:13
  • @Chris, so theoretically, changing it to `createHmac` would fix that issue? – Sara Fuerst Apr 29 '16 at 12:17
  • @Brody, I'm trying to, however I'm using Eclipse for the PHP and netBeans for the node and while netbeans has no problem displaying the output, Eclipse is not a fan of all the special characters – Sara Fuerst Apr 29 '16 at 12:35

2 Answers2

20

This answer from Chris is good if you are porting hash_hmac with the last parameter being true. In this case, binary is produced, as is the case with Chris's javascript.

To add to that, this example:

 $sign = hash_hmac('sha512', $post_data, $secret);

Would be ported with a function like so in nodejs:

const crypto = require("crypto");

function signHmacSha512(key, str) {
  let hmac = crypto.createHmac("sha512", key);
  let signed = hmac.update(Buffer.from(str, 'utf-8')).digest("hex");
  return signed
}

The difference here being that when you leave off the last argument to hash_hmac (or set it to something not true), it behaves as defined in the PHP docs:

When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.

In order to do this with node.js we use digest('hex') as you can see in the snippet.

Felipe
  • 643
  • 8
  • 13
keyvan
  • 2,947
  • 1
  • 18
  • 13
7

The primary problem here is that you are using createHash which creates a hash, rather than createHmac which creates an HMAC.

Change createHash to createHmac and you should find it produces the same result.

This is the output you should expect:

chris /tmp/hmac $ cat node.js 
var crypto = require('crypto');
var key = 'abcd';
var data = 'wxyz';

function getHash(string){
    var hmac = crypto.createHmac('sha1', key);
    hmac.update(string); 
    return hmac.digest('binary'); 
};

process.stdout.write(getHash(data));

chris /tmp/hmac $ cat php.php 
<?php
$key = "abcd";
$data = "wxyz";
function __getHash($string)
{
    global $key;
    return hash_hmac('sha1', $string, $key, true); 
}

echo utf8_encode(__getHash($data));

chris /tmp/hmac $ node node.js | base64
WsOKw4xgw4jDlFHDl3jDuEPDuCfCmsOFwoDCrsK/w6ka
chris /tmp/hmac $ php php.php | base64
WsOKw4xgw4jDlFHDl3jDuEPDuCfCmsOFwoDCrsK/w6ka
Chris
  • 5,571
  • 2
  • 20
  • 32
  • I'm still getting that the Signature does not match. Any idea why? – Sara Fuerst Apr 29 '16 at 13:09
  • Well how are you comparing the binary data? If you compare them as strings you will need to make sure the encoding is correct. I've attached the output I get. Note I've converted the PHP output from latin-1 to UTF-8 and base64 encoded both outputs for easy comparison. – Chris Apr 29 '16 at 13:41
  • 1
    Sara: hash_hmac produces a hex digest by default – keyvan Jun 09 '17 at 03:55