Reputation: 697
I am working with Payeezy API to handle payment on a webapp, their API for purchase requires a HMAC of payload signed using api secret. An excerpt from the docs:
Construct the data param by appending the parameters below in the same order as shown. a. apikey - API key of the developer. b. nonce - secure random number. c. timestamp - epoch timestamp in milliseconds. d. token - Merchant Token. e. payload - Actual body content passed as post request. Compute HMAC SHA256 hash on the above data param using the key below f. apiSecret - Consumer Secret token for the given api key Calculate the base64 of the hash which would be our required Authorization header value.
I found a library called jshashes on NPM and I tried to use their library to hash my header params, my code looks like this:
const payload = {
"merchant_ref": "1-Sale",
"transaction_type": "purchase",
"method": "credit_card",
"amount": amount * 100,
"partial_redemption": "false",
"currency_code": "USD",
"credit_card": {
"type": type,
"cardholder_name": cardholder_name,
"card_number": card_number,
"exp_date": exp_date,
"cvv": cvv
}
}
const data = apikey + nounce + timestamp + token + JSON.stringify(payload)
const sha256 = new Hashes.SHA256()
const shaData = sha256.b64_hmac(apiSecret, data)
The outcome compared to the sample hashed value looks like this:
//mine
beWtpCGDv/iBoAUDAThGFXIge9eli/Xtl7JIBuR1bd4=
//payeezy sample
NmUzMTNmYWU0YjExM2UxMmM0NjllZGI1NThjY2M5MmUzMzE3NTFlZmQ1NDQxYzAzMTgwMmIwNDQ0MWVmYTdhMw==
from the looks of the character counts I could tell that my hashing process is not correct but I can't figure out where went wrong.
I've seen similar questions being asked here but none answered, any help is appreciated.
ADDITION, I tried crypto library on Node.js:
const data = apikey + nounce + timestamp + token + JSON.stringify(payload)
const hmac = crypto.createHmac('sha512', apiSecret)
hmac.on('readable', () => {
const data = hmac.read()
if (data) {
console.log(data.toString('base64'));
}
})
hmac.write(data)
hmac.end()
Same result, with only half the character length compared to the sample hashed value
UPDATE: After I used SHA512 on the data it finally returned a string that looks to have the same character length as the sample, but the validation is still not passing...
Upvotes: 0
Views: 5860
Reputation: 1
if you convert the base64 example from that site to a string
console.log(atob('NmUzMTNmYWU0YjExM2UxMmM0NjllZGI1NThjY2M5MmUzMzE3NTFlZmQ1NDQxYzAzMTgwMmIwNDQ0MWVmYTdhMw=='))
You get
6e313fae4b113e12c469edb558ccc92e331751efd5441c031802b04441efa7a3
This is a 64 character (256 bit) hex string
So my guess is that they get the hex string HMAC, and base64 encode that - which seems awfully stupid, hex is safe to send as is, why make it 4/3rds larger!!
if they simply used the base64 of the HMAC, it'd only be 45 characters long!!
Instead they get the 64 character hex string and base64 encode that to get 88 characters!! strange design decision!!
So, your code should do the same
like
Data = Buffer.from(sha256.hex_hmac(apiSecret, data), 'utf-8').toString('base64');
not sure if there's a better way in node to convert hex encoded string to base64, but that works
And finally (actually this bit is just to "match" how the authorization is calculated on the example page as linked by the OP https://developer.payeezy.com/payeezy-api/apis/post/transactions-3
) So, it's not necessary to make the payload larger for no reason)
Another point you need to know, the payload JSON needs to be in a specific format it seems ... 2 space indented ... again, this is a damned stupid waste of bandwidth .. {"key":1234}
takes 12 characters
{
"key": 1234
}
takes 17
So, anyway, you need to do this:
JSON.stringify(payload,null, 2)
This last piece of the puzzle should make your code as follows
const data = apikey + nonce + timestamp + token + JSON.stringify(payload,null, 2)
const sha256 = new Hashes.SHA256()
const shaData = Buffer.from(sha256.hex_hmac(secret, data), 'utf-8').toString('base64');
Upvotes: 2