Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using JavaScript to properly sign a string using HmacSHA256

In the Houndify API Docs for Authentication, you have the following block of content:


An Example of Authenticating a Request

Let's assume we have the following information:

UserID: ae06fcd3-6447-4356-afaa-813aa4f2ba41
    RequestID: 70aa7c25-c74f-48be-8ca8-cbf73627c05f
    Timestamp: 1418068667   
    ClientID: KFvH6Rpy3tUimL-pCUFpPg==
    ClientKey: KgMLuq-k1oCUv5bzTlKAJf_mGo0T07jTogbi6apcqLa114CCPH3rlK4c0RktY30xLEQ49MZ-C2bMyFOVQO4PyA==
  1. Concatenate the UserID string, RequestID string, and TimeStamp string in the following format: {user_id};{request_id}{timestamp}

  2. With the values from the example, the expected output would be in this case: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f1418068667

  3. Sign the message with the decoded ClientKey. The result is a 32-byte binary string (which we can’t represent visually). After base-64 encoding, however, the signature is: myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=

  4. The client then generates two authentication headers Hound-Request-Authentication and Hound-Client-Authentication.

  5. The Hound-Request-Authentication header is composed by concatenating the UserID and RequestID in the following format: {user-id};{request-id}. Continuing the example above, the value for this header would be: Hound-Request-Authentication: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f

  6. The Hound-Client-Authentication header is composed by concatening the ClientID, the TimeStamp string and the signature in the following format: {client-id};{timestamp};{signature}. Continuing the example above, the value for this header would be: Hound-Client-Authentication: KFvH6Rpy3tUimL-pCUFpPg==;1418068667;myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=


For Number 3, it says "Sign the message with the decoded ClientKey". The "message" and "ClientKey" are two distinct strings.

My question(s): How do you sign one string with another string i.e. what exactly does that mean? And how would you do that in JavaScript?

var message = 'my_message';
var key = 'signing_key';

//??what next??

I'm trying to figure all this out so I can create a pre-request script in Postman to do a proper HmacSHA256 hash.

like image 835
ObiHill Avatar asked Feb 05 '16 15:02

ObiHill


People also ask

What is CryptoJS HMACSHA256?

HmacSHA256(CryptoJS. enc. Hex. parse(mess), key)) generates an HMAC using the SHA256 digest. Thereby the message is hex decoded and the key UTF8 encoded.

What is HMACSHA256 algorithm?

HMACSHA256 is a type of keyed hash algorithm that is constructed from the SHA-256 hash function and used as a Hash-based Message Authentication Code (HMAC).


2 Answers

According to the documentation, if you're using one of their SDKs, it will automatically authenticate your requests:

SDKs already handle authentication for you. You just have to provide the SDK with the Client ID and Client Key that was generated for your client when it was created. If you are not using an SDK, use the code example to the right to generate your own HTTP headers to authenticate your request.

However, if you want to do it manually, I believe you need to compute the HMAC value of the string they describe in the link in your question and then send it base64 encoded as part of the Hound-Client-Authentication header in your requests. They provide an example for node.js:

var uuid = require('node-uuid');
var crypto = require('crypto');

function generateAuthHeaders (clientId, clientKey, userId, requestId) {

    if (!clientId || !clientKey) {
        throw new Error('Must provide a Client ID and a Client Key');
    }

    // Generate a unique UserId and RequestId.
    userId      = userId || uuid.v1();

    // keep track of this requestId, you will need it for the RequestInfo Object
    requestId   = requestId || uuid.v1();

    var requestData = userId + ';' + requestId;

    // keep track of this timestamp, you will need it for the RequestInfo Object
    var timestamp   = Math.floor(Date.now() / 1000),  

        unescapeBase64Url = function (key) {
            return key.replace(/-/g, '+').replace(/_/g, '/');
        },

        escapeBase64Url = function (key) {
            return key.replace(/\+/g, '-').replace(/\//g, '_');
        },

        signKey = function (clientKey, message) {
            var key = new Buffer(unescapeBase64Url(clientKey), 'base64');
            var hash = crypto.createHmac('sha256', key).update(message).digest('base64');
            return escapeBase64Url(hash);

        },

        encodedData = signKey(clientKey, requestData + timestamp),
        headers = {
            'Hound-Request-Authentication': requestData,
            'Hound-Client-Authentication': clientId + ';' + timestamp + ';' + encodedData
        };

    return headers;
};
like image 110
Sam Avatar answered Sep 20 '22 01:09

Sam


So basically, signing [in this specific case] simply means to create a hash of the string using a hash algorithm in addition to a key, as opposed to a keyless hash [like MD5]. For example:

var message = 'my_message';
var key = 'signing_key';
var hashed_message = hash_func(message, key);

where hash_func is a hashing algorithm like HmacSHA256 (the hashing algorithm in question).

The reason I was trying to figure this out was to test authentication for the Houndify API using Postman. Fortunately, Postman has a nice feature called pre-request scripts [complete with hashing algorithms] that helps if you need to pre-generate values that need to be sent along with your request.

After much fiddling around, I managed to create a pre-request script that properly authenticates to this API (see code below).

var unescapeBase64Url = function (key) {
            return key.replace(/-/g, '+').replace(/_/g, '/');
        },

        escapeBase64Url = function (key) {
            return key.replace(/\+/g, '-').replace(/\//g, '_');
        },
        guid = function() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
        s4() + '-' + s4() + s4() + s4();
    };

var client_id_str = environment["client-id"];
var client_key_str = environment["client-key"];
var user_id_str = environment["user-id"];
var request_id_str = guid();
var timestamp_str = Math.floor(Date.now() / 1000);

var client_key_dec_str = CryptoJS.enc.Base64.parse(unescapeBase64Url(client_key_str));

var message_str = user_id_str+";"+request_id_str+timestamp_str;
var signature_hash_obj = CryptoJS.HmacSHA256(message_str, client_key_dec_str);
var signature_str = signature_hash_obj.toString(CryptoJS.enc.Base64);

var hound_request_str = user_id_str+";"+request_id_str;
var hound_client_str = client_id_str+";"+timestamp_str+";"+escapeBase64Url(signature_str);

postman.setEnvironmentVariable("hound-request-authentication", hound_request_str);
postman.setEnvironmentVariable("hound-client-authentication", hound_client_str);

Note that you are going to have to create environment variables in Postman for client-id, client-key, and user-id, as well as for the header variables hound-request-authentication and hound-client-authentication to hold the final values that will be referenced when defining headers.

Hope it helps.

like image 27
ObiHill Avatar answered Sep 18 '22 01:09

ObiHill