Question
· Mar 21, 2022

HMAC authentication problem

Hi! I'm banging my head to the wall with HMAC authentication. I have tried to implement this various ways but nothing seems to work.

If someone could help on this it would be great!

Here is a code that I have tried and working Javascript example, tested on Postman. 

Set Appid = "itsasecretid"
Set Appkey = "itsasecretkey"

Set requestTimeStamp = $ZDATETIME($HOROLOG,-2)
Set nonce = ..getRandomString()
Set signatureRawdata = Appid_requestTimeStamp_nonce
    
Set keyUTF8 = $zconvert(Appkey,"O","UTF8")
Set signatureRawdataUTF8 = $zconvert(signatureRawdata,"O","UTF8")
    
Set tSigningKey = $SYSTEM.Encryption.HMACSHA(256, signatureRawdataUTF8, keyUTF8)
Set tSignature = ##class(%xsd.hexBinary).LogicalToXSD(tSigningKey)

//Set tSignatureBase64 = ##Class(%SYSTEM.Encryption).Base64Encode(tSigningKey)

Set to_hmac  = "hmac "_Appid_":"_tSignature_":"_nonce_":"_requestTimeStamp

Javascript:

var crypto = require('crypto-js');
var APPId = "itsasecretid";
var APIKey = "itsasecretkey";
var time = (new Date()).getTime();
var time_s = Math.floor(time / 1000);
pm.environment.set("timestamp", time_s);

var nonce = getRandomString();
pm.environment.set("nonce", nonce);

var rawData = APPId + time_s + nonce;

var signature = CryptoJS.enc.Utf8.parse(rawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature, secretByteArray)

var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
var to_hmac = "hmac " + APPId + ":" + requestSignatureBase64String + ":" + nonce + ":" + time_s;

Product version: IRIS 2021.2
Discussion (4)1
Log in or sign up to continue

Can you be more specific about how your implementation isn't working?  For example, do you have a known signature for known inputs that you are comparing against?  Or is there some external system you are trying to authenticate to?

When I ran your IRIS code (hardcoding values for requestTimeStamp and nonce), the tSignature value matched the output of this tool: https://www.freeformatter.com/hmac-generator.html#ad-output

("to_hmac" is not a valid variable name in ObjectScript, but I assume that's a copy-paste error.)

I wasn't familiar with the CryptoJS library before looking into this, but in the examples I was able to find, it looks like the 2nd arg to HmacSHA256() is typically passed in as a string, rather than a byte array: https://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-h...

One thing I noticed when I tried running your JS code is that if I replace:

var signatureBytes = CryptoJS.HmacSHA256(signature, secretByteArray)

With:

var signatureBytes = CryptoJS.HmacSHA256(signature, APIKey)

Then then value of requestSignatureBase64String in your JS example matches the value of tSignatureBase64 in your IRIS example.

So my guess is that you're getting the correct raw signature value in IRIS, however in your IRIS example you are using the hex representation of it while in your JS example you are using the base64 representation.  Also, if you are using your JS example for reference, you may want to look into the expected format of the 2nd arg to HmacSHA256().

Hi Jorge and thanks for the answer!

Yes there is external system where I try to authenticate and with Postman that javasript works just fine.

I can get correct tSecretByteArray with this
    Set tSecretByteArray = ..hex(##class(%SYSTEM.Encryption).Base64Decode(keyUTF8))

So do I have to get 32-bit wordArray like in JS var signature = CryptoJS.enc.Utf8.parse(rawData);

If do some hardcoding and set "signature" value from Postman, I won't get right values from 
    Set tSignature = ..hex($SYSTEM.Encryption.HMACSHA(256, wordArray, tSecretByteArray))
    Set tSignatureBase64 = ##class(%SYSTEM.Encryption).Base64Encode(tSignature)

And yes, to_hmac was copy-paste error ;)

As you can see I get same requestSignatureBase64String if I use string values in js and objectscript examples. Unfortunately web service is expecting  value QxYkJH+b0xo+pCLMBMXgKhMqjMSV+mqQbNvPf7kX2k4=

I get same signature and secretByteArray values in js and objectscript, but difference is that objectscript value is String and js value is 32-bit WordArray.

How do I get correct signature and secretByteArray values that I can pass to the $SYSTEM.Encryption.HMACSHA method?

Cryptojs example

var key = "secretkey"
var message = "messageinabottle"
var signature = CryptoJS.enc.Utf8.parse(message) //6d657373616765696e61626f74746c65
var secretByteArray = CryptoJS.enc.Base64.parse(key) //b1e72b7ad91e
var signatureBytes = CryptoJS.HmacSHA256(signature, secretByteArray) //431624247f9bd31a3ea422cc04c5e02a132a8cc495fa6a906cdbcf7fb917da4e
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes) //QxYkJH+b0xo+pCLMBMXgKhMqjMSV+mqQbNvPf7kX2k4=
var signatureBytes = CryptoJS.HmacSHA256(message, key) //3ab515868d9a93d73025789510f9fc4089d3fb48722fb909f7331cc7e13f6719
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes) //OrUVho2ak9cwJXiVEPn8QInT+0hyL7kJ9zMcx+E/Zxk=

ObjectScript example

Set key = "secretkey"
Set message = "messageinabottle"
Set signature = $ZCONVERT(##class(%xsd.hexBinary).LogicalToXSD(message),"L") //6d657373616765696e61626f74746c65
Set secretByteArray = $ZCONVERT(##class(%xsd.hexBinary).LogicalToXSD(##class(%SYSTEM.Encryption).Base64Decode(key)),"L") //b1e72b7ad91e
Set signatureBytes = $SYSTEM.Encryption.HMACSHA(256, signature, secretByteArray)
Set requestSignatureBase64String = ##class(%SYSTEM.Encryption).Base64Encode(signatureBytes) //kQeBc7Gu4SRUeicA7xVeN6V9sWX5b1bCLX9rUBK+lGA=
Set signatureBytes = $SYSTEM.Encryption.HMACSHA(256, message, key)
Set requestSignatureBase64String = ##class(%SYSTEM.Encryption).Base64Encode(signatureBytes) //OrUVho2ak9cwJXiVEPn8QInT+0hyL7kJ9zMcx+E/Zxk=