Build hmac sha256 hashing algorithm in node.js - node.js

I am using Bold Commerce Webhooks to subscribe to subscription events in my store. They give documentation about how their request signatures are generated in PHP:
$now = time(); // current unix timestamp
$json = json_encode($payload, JSON_FORCE_OBJECT);
$signature = hash_hmac('sha256', $now.'.'.$json, $signingKey);
I'm trying to recreate the hash on my side in node.js. From my research I've figured out the following so far, which I believe is pretty close, but doesn't match yet:
const hash = request.header("X-Bold-Signature")!;
const SECRET = "my-secret-api-key";
const body = request.body;
const time = request.header("timestamp")!;
const mySignature = crypto.createHmac('sha256', SECRET).update(time + '.' + body).digest("hex");
if (mySignature !== request.header("X-Bold-Signature")!) {
//...
}
I've also tried using JSON.stringify(body) which changes the hash but still doesn't match.

It matched with this code.
const hash = crypto
.createHmac('sha256', secretKey)
.update(timestamp + '.' + JSON.stringify(body))
.digest('hex');
Note that unlike shopify, do not use rawbody.

Related

Can't verify webhook Node.js

I'm attempting to verify a webhook signature from Patreon using Node.js. Here is my code:
const crypto = require("crypto");
...
function validateJsonWebhook(request) {
const secret = SECRET_KEY_GIVEN_BY_PATREON;
const hash = crypto.createHmac("md5", secret)
.update(JSON.stringify(request.body))
.digest("hex");
if (request.header("x-patreon-signature") === hash) {
return true;
} else {
return false;
}
}
Patreon webhooks use MD5 - see https://docs.patreon.com/#webhooks.
I've verified the secret key multiple times so I know that's not the issue.
Both "request.header("x-patreon-signature")" and "hash" are returning the correct format (i.e. they're both a 32 digit letter-number combination) but they're just not matching.
Any idea about what's going on?
So #gaiazov's comment led me to do some Googling which led me to the first two comments on Stripe verify web-hook signature HMAC sha254 HAPI.js by Karl Reid which led me to https://github.com/stripe/stripe-node/issues/331#issuecomment-314917167.
For anyone who finds this in the future: DON'T use JSON.stringify(request.body) - use request.rawBody instead, as the signature is calculated based on the raw JSON. I feel like this should be emphasized in Patreon's documentation, as all the examples I found used the code I originally posted. My new, working code is as follows (I cleaned up the "if (request.header("x-patreon-signature") === hash)" part at the end):
const crypto = require("crypto");
...
function validateJsonWebhook(request) {
// Secret key given by Patreon.
const secret = patreonSecret;
const hash = crypto.createHmac("md5", secret)
.update(request.rawBody)
.digest("hex");
return (request.header("x-patreon-signature") === hash);
}

How do I form an SAS token for Microsoft Azure API Management's REST API in Node.js?

I am using Microsoft Azure API Management service and want to use the REST API service. In creating my SAS token, which is needed otherwise the API call doesn't authorize, I'm having difficulty forming a proper token. Microsoft's webpage about this SAS token for API Management only shows an example in C#. I want to know how to form an SAS token in Node.js, which is not shown. Below is my code that was working last week, but is not now for some unknown reason. The error I get is: 401 Authorization error, token invalid
If someone can help me formulate this token, I would appreciate it.
This is Microsoft's webpage regarding this authentication token: https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication
Here's my code:
const crypto = require('crypto');
const util = require('util');
const sign = () => {
const id = ${process.env.id}
const key = `${process.env.SASKey}`;
const date = new Date();
const newDate = new Date(date.setTime(date.getTime() + 8 * 86400000));
const expiry = `${newDate.getFullYear()}${
newDate.getMonth() < 10
? '' + newDate.getMonth() + 1
: newDate.getMonth() + 1
}${newDate.getDate()}${newDate.getHours()}${
newDate.getMinutes() < 10
? '0' + newDate.getMinutes()
: newDate.getMinutes()
}`;
const dataToSignString = '%s\n%s';
const dataToSign = util.format(dataToSignString, ${id}, expiry);
const hash = crypto
.createHmac('sha512', key)
.update(dataToSign)
.digest('base64');
const encodedToken = `SharedAccessSignature ${id}&${expiry}&${hash}`;
console.log(encodedToken);
return encodedToken;
};
Try the code:
protected getAPIManagementSAS(){
let utf8 = require("utf8")
let crypto= require("crypto")
let identifier = process.env.API_IDENTIFIER;
let key = process.env.API_KEY;
var now = new Date;
var utcDate = new Date(now.getUTCFullYear(),now.getUTCMonth(), now.getUTCDate() , now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds(), now.getUTCMilliseconds());
let expiry = addMinutes(utcDate,1,"yyyy-MM-ddThh:mm:ss") + '.0000000Z'
var dataToSign = identifier + "\n" + expiry;
var signatureUTF8 = utf8.encode(key);
var signature = crypto.createHmac('sha512', signatureUTF8).update(dataToSign).digest('base64');
var encodedToken = `SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}`;
return encodedToken
}
For more information, see here.
After a million tries, it seems like the only format acceptable right now is:
SharedAccessSignature uid=${identifier}&ex=${expiry}&sn=${signature}
If you are using the other format that has the "integration" parameter, that's a hit or a miss, mostly miss though. Set the uid as "integration" if that's your identifier and follow the above format as it works.

Issue creating password digest for Amadeus SOAP API

I followed the instructions in the docs and tried to do the same formula in Node, however, I'm not able to correctly authenticate with the server as I receive an 11|Session| response. I assume that error is in response to incorrectly set nonce and/or password digest.
The formula: Base64(SHA1($NONCE + $TIMESTAMP + SHA1($CLEARPASSWORD)))
The variables: $CLEARPASSWORD=AMADEUS, $TIMESTAMP=2015-09-30T14:12:15Z, $NONCE=c2VjcmV0bm9uY2UxMDExMQ==.
The expected hash: +LzcaRc+ndGAcZIXmq/N7xGes+k=
The code I tried was:
const crypto = require('crypto')
const hashedPassword = crypto.createHash('sha1').update(CLEARPASSWORD).digest() // Returns a buffer
crypto.createHash('sha1').update(NONCE + TIMESTAMP + hashedPassword).digest('base64') // Returns a Base64 String
However, this returns DDcZEaS5AtoVaZhsARy9MqV+Y34=. And if I change the nonce to its plain string secretnonce10111, then I get gHOoqyDb9YJBrk30iabSO8nKxio= which still isn't correct.
I'm not sure what could be the issue.
I finally figured out what was wrong. The problem was I was trying to concatenate a string with a Buffer, when I should have just made everything a buffer from the start.
So, instead of
const hashedPassword = crypto.createHash('sha1').update(CLEARPASSWORD).digest()
crypto.createHash('sha1').update(NONCE + TIMESTAMP + hashedPassword).digest('base64')
It should have been
const buffer = Buffer.concat([Buffer.from(NONCE), Buffer.from(TIMESTAMP), nodeCrypto.createHash('sha1').update(CLEARPASSWORD).digest()])
const hashedPassword = nodeCrypto.createHash("sha1").update(buffer).digest("base64")
you can use this php code to generate Digest Password
<?php
date_default_timezone_set('UTC');
$t = microtime(true);
$micro = sprintf("%03d",($t - floor($t)) * 1000);
$date = new DateTime( date('Y-m-d H:i:s.'.$micro) );
echo $timestamp = $date->format("Y-m-d\TH:i:s").$micro . 'Z';
$nonce = mt_rand(10000000, 99999999);
echo $nounce = base64_encode($nonce);//we have to decode the nonce and then apply the formula on it and in xml we have to send the encoded nonce
$password = "AMADEUS"; //clear password
echo $passSHA = base64_encode(sha1($nonce . $timestamp . sha1($password, true), true));
?>

How to use 'script' in nodejs

I have this kind of api example and I want to use this in nodejs.
/*
https://code.google.com/archive/p/crypto-js/
https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/crypto-js/CryptoJS%20v3.1.2.zip
*/
<script type="text/javascript" src="./CryptoJS/rollups/hmac-sha256.js"></script>
<script type="text/javascript" src="./CryptoJS/components/enc-base64.js"></script>
function makeSignature() {
var space = " "; // one space
var newLine = "\n"; // new line
var method = "GET"; // method
var url = "/photos/puppy.jpg?query1=&query2"; // url (include query string)
var timestamp = "{timestamp}"; // current timestamp (epoch)
var accessKey = "{accessKey}"; // access key id (from portal or Sub Account)
var secretKey = "{secretKey}"; // secret key (from portal or Sub Account)
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secretKey);
hmac.update(method);
hmac.update(space);
hmac.update(url);
hmac.update(newLine);
hmac.update(timestamp);
hmac.update(newLine);
hmac.update(accessKey);
var hash = hmac.finalize();
return hash.toString(CryptoJS.enc.Base64);
}
But the problem is when I use this in Nodejs, I don't know how to require those CryptoJS.
For example, I downloaded CryptoJS file by google. and it is reading by require.
Even though it is read, I don't know which should I read correctly.
Could you help how to solve this problem?
const CryptoJS = require('./CryptoJS v3.1.2/components/enc-base64');
In NodeJS (latest version), you don't even need to download an external library or install from NPM.
Nodejs has crypto built-in library.
const crypto = require('crypto');
var space = " ";
var newLine = "\n";
var method = "GET";
var url = "/photos/puppy.jpg?query1=&query2";
var timestamp = "{timestamp}";
var accessKey = "{accessKey}";
var secretKey = "{secretKey}";
const hash = crypto.createHmac('sha256', secretKey)
.update(method)
.update(space)
.update(url)
.update(newLine)
.update(timestamp)
.update(newLine)
.update(accessKey)
.digest('hex');
console.log(hash);
First of all I dont know why you download file from google? There is very useful npm library. find it here and use it. https://www.npmjs.com/package/crypto-js

Amazon S3 Signature

How do you create the hex encoded signature in crypto in a nodejs environment?
Do you just do this like so?
const secret = 'mysecret';
const date = yyyymmdd;
const dateKey = crypto.createHmac('sha256', 'AWS4' + secret + ',' + date);
const dateRegionKey = crypto('sha256', dateKey + ',' + 'myregion')
const DateRegionServiceKey = crypto('sha256', dateRegionKey + ',' + 'someservice');
const signingKey = crypto('sha256', DateRegionServiceKey + ',' + 'aws4_request');
const signature = crypo('sha256', signingKey + base64Policy);
High level it looks ok. I hope you know that secret is not just a string, it is the secretKey of your IAM User accessKey/secretKey pair.
Any particular reason you want to do it yourself and not use the AWS SDK?
Also look at below for a sample signing implementation (in Java) which works for AWS ElasticSearch. Look at getSignatureKey and calculateSignature methods.
https://github.com/dy10/aws-elasticsearch-query-java/blob/master/src/main/java/dy/aws/es/AWSV4Auth.java
you should use this library https://github.com/mhart/aws4
you need to use result = aws4.sign({}, { cred from AWS })
and it returns result.headers that are the headers you need to send in your request

Resources