Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Coldfusion CFHTTP with SHA512-hmac signed REST request body

I am trying to make a signed request to the trading API at bitfloor.com (it's a REST API)

Bitfloor gives me:

1) API Key (i.e. 6bd2b780-00be-11e2-bde3-2837371c3c3a)

2) Secret Key (i.e. oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==)

The following is Bitfloor's exact instructions for making the request:

Requests must be HTTPS POST requests on port 443 (https). Each request must contain the required headers (listed below). The headers identify, verify, and validate your request to prevent tampering. headers

bitfloor-key This is the provided by bitfloor to uniquely identify your account. (i.e. 6bd2b780-00be-11e2-bde3-2837371c3c3a)

bitfloor-sign The sign field is a sha512-hmac of the request body using the secret key which corresponds to your api key.

To sign your request: base64 decode the secret key into the raw bytes (64 bytes). Use those bytes for your sha512-hmac signing of the http request body. Base64 encode the signing result and send in this header field.

bitfloor-passphrase The passphrase you specified when creating this api key. We cannot recover your passphrase if forgotten. You will need to create a new API key.

bitfloor-version The api version of the resource you are interested in. The only valid value currently is 1


After a full eight hours of trial and error and searching the internet repeatedly for any sort of insight or information, the following code is as close as I can come to what I think might be somewhere in the direction of how to construct the request properly, alas, no matter what I attmept I get "Invalid Signature" returned by their API.

Here is what I have so far...

FIRST, I found this function on the web that someone wrote to do the SHA512 signing:

<cffunction name="HMAC_SHA512" returntype="binary" access="public" output="false">
    <cfargument name="signKey" type="string" required="true">
    <cfargument name="signMessage" type="string" required="true">

    <cfset var jMsg = JavaCast("string",arguments.signMessage).getBytes("iso-8859-1")>
    <cfset var jKey = JavaCast("string",arguments.signKey).getBytes("iso-8859-1")>
    <cfset var key  = createObject("java","javax.crypto.spec.SecretKeySpec")>
    <cfset var mac  = createObject("java","javax.crypto.Mac")>
    <cfset key  = key.init(jKey,"HmacSHA512")>
    <cfset mac  = mac.getInstance(key.getAlgorithm())>
    <cfset mac.init(key)>
    <cfset mac.update(jMsg)>
    <cfreturn mac.doFinal()>
</cffunction>

I have no idea what it does, but it seems to work and does so without error.

Here is my implementation of this function and my attempt at making the request: NOTE: The "nonce" value is a required param that must be sent with the request.

<cffunction name="myorders">
    <cfset nonce        = dateDiff("s",createDateTime(2012,01,01,0,0,0),now())>
    <cfset requestbody  = "?nonce=#nonce#">
    <cfset key      = "oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==">
    <cfset sign     = HMAC_SHA512(key,requestbody)>
    <cfset signed       = binaryEncode(sign,"Base64")>

    <!--- HTTP REQUEST --->
    <cfhttp url = "https://api.bitfloor.com/orders#requestbody#"
        method  = "post"
        result  = "bitfloor">

    <!--- HEADERS --->
    <cfhttpparam
        type    = "body"
        value   = requestbody>
    <cfhttpparam
        type    = "header"
        name    = "bitfloor-key"
        value   = "6bd2b780-00be-11e2-bde3-2837371c3c3a">
    <cfhttpparam
        type    = "header"
        name    = "bitfloor-sign"
        value   = signed>
    <cfhttpparam
        type    = "header"
        name    = "bitfloor-passphrase"
        value   = "mysecretpassphrase">
    <cfhttpparam
        type    = "header"
        name    = "bitfloor-version"
        value   = "1">
    </cfhttp>
</cffunction>

I think most of my confusion comes from not knowing exactly what the "request body" is. I feel like I'm not signing the right thing perhaps.

I hope there is a Coldfusion programmer out there who is familiar with signed requests. I'm at my wit's end.

Please help! Namaste

like image 518
Jay Avatar asked Oct 16 '12 18:10

Jay


1 Answers

I have not used that api, but I ran some tests and it seems to work with the following tweaks:

  • Since the secretKey value is base64 encoded, your signing function needs to use binaryDecode to properly extract the bytes. Using String.getBytes(...) produces a completely different (and wrong) result.

  • The expected request body value is just: nonce=#nonceValue# (without the leading "?")

  • It seems to require the Content-Type=application/x-www-form-urlencoded header, otherwise it fails to parse the content and the response is: {"error":"no nonce specified"}

Code

 <cfset apiKey = "6bd2b780-00be-11e2-bde3-2837371c3c3a">
 <cfset secretKey = "oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==">
 <cfset passphrase = "your secret phrase">

 <cfset requestBody  = "nonce="& now().getTime()>
 <cfset signBytes    = HMAC_SHA512(secretKey, requestbody)>
 <cfset signBase64   = binaryEncode(signBytes, "base64")>

 <cfhttp url="https://api.bitfloor.com/orders" method="post" port="443" result="bitfloor">
    <cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
    <cfhttpparam type="header" name="bitfloor-key" value="#apiKey#">
    <cfhttpparam type="header" name="bitfloor-sign" value="#signBase64#">
    <cfhttpparam type="header" name="bitfloor-passphrase" value="#passphrase#">
    <cfhttpparam type="header" name="bitfloor-version" value="1">
    <cfhttpparam type="body" value="#requestBody#">
 </cfhttp>

 <cfdump var="#bitfloor#" label="Response">

<cffunction name="HMAC_SHA512" returntype="binary" access="public" output="false">
    <cfargument name="base64Key" type="string" required="true">
    <cfargument name="signMessage" type="string" required="true">
    <cfargument name="encoding" type="string" default="UTF-8">

     <cfset var messageBytes = JavaCast("string",arguments.signMessage).getBytes(arguments.encoding)>
     <cfset var keyBytes = binaryDecode(arguments.base64Key, "base64")>
     <cfset var key  = createObject("java","javax.crypto.spec.SecretKeySpec")>
     <cfset var mac  = createObject("java","javax.crypto.Mac")>
     <cfset key  = key.init(keyBytes,"HmacSHA512")>
     <cfset mac  = mac.getInstance(key.getAlgorithm())>
     <cfset mac.init(key)>
     <cfset mac.update(messageBytes)>

     <cfreturn mac.doFinal()>
</cffunction>
like image 187
Leigh Avatar answered Oct 16 '22 08:10

Leigh