Coldfusion CFHTTP с телом запроса REST, подписанным SHA512-hmac

Я пытаюсь сделать подписанный запрос к торговому API на bitfloor.com (это REST API)

Bitfloor дает мне:

1) Ключ API (например, 6bd2b780-00be-11e2-bde3-2837371c3c3a)

2) Секретный ключ (например, oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==)

Ниже приведены точные инструкции Bitfloor по выполнению запроса:

Запросы должны быть HTTPS POST-запросами на порт 443 (https). Каждый запрос должен содержать необходимые заголовки (перечислены ниже). Заголовки идентифицируют, проверяют и подтверждают ваш запрос, чтобы предотвратить несанкционированное вмешательство. заголовки

bitfloor-key Предоставляется Bitfloor для уникальной идентификации вашей учетной записи. (например, 6bd2b780-00be-11e2-bde3-2837371c3c3a)

bitfloor-sign Поле знака представляет собой sha512-hmac тела запроса с использованием секретного ключа, соответствующего вашему ключу API.

Чтобы подписать ваш запрос: base64 декодирует секретный ключ в необработанные байты (64 байта). Используйте эти байты для подписи sha512-hmac тела http-запроса. Base64 кодирует результат подписи и отправляет в этом поле заголовка.

bitfloor-passphrase Парольная фраза, которую вы указали при создании этого ключа API. Мы не сможем восстановить вашу парольную фразу, если вы ее забыли. Вам нужно будет создать новый ключ API.

bitfloor-version Версия API интересующего вас ресурса. Единственным допустимым значением в настоящее время является 1.


После полных восьми часов проб и ошибок и повторного поиска в Интернете любого рода идей или информации, следующий код настолько близок, насколько я могу прийти к тому, что, как я думаю, может быть где-то в направлении того, как правильно построить запрос, увы, что бы я ни пытался, я получаю «Неверную подпись», возвращаемую их API.

Вот что у меня пока...

ВО-первых, я нашел в Интернете эту функцию, которую кто-то написал для подписи SHA512:

<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>

Я понятия не имею, что он делает, но, похоже, он работает и делает это без ошибок.

Вот моя реализация этой функции и моя попытка сделать запрос: ПРИМЕЧАНИЕ. Значение «nonce» является обязательным параметром, который должен быть отправлен с запросом.

<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>

Я думаю, что большая часть моего замешательства происходит из-за того, что я точно не знаю, что такое «тело запроса». Мне кажется, что я подписываю не то, что нужно.

Я надеюсь, что есть программист Coldfusion, который знаком с подписанными запросами. Я в своем уме.

Пожалуйста помоги! Намасте


person Jay    schedule 16.10.2012    source источник


Ответы (1)


Я не использовал этот API, но я провел несколько тестов, и, похоже, он работает со следующими настройками:

  • Поскольку значение secretKey закодировано в base64, ваша функция подписи должна использовать binaryDecode для правильного извлечения байтов. Использование String.getBytes(...) приводит к совершенно другому (и неправильному) результату.

  • Ожидаемое значение тела запроса: nonce=#nonceValue# (без начального "?")

  • Кажется, требуется заголовок Content-Type=application/x-www-form-urlencoded, в противном случае он не сможет проанализировать содержимое, и ответ будет следующим: {"error":"нонс не указан"}

Код

 <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>
person Leigh    schedule 17.10.2012
comment
Большое спасибо Ли! Хотел бы я объяснить вам восторг, который я испытал, когда увидел, что реальные данные, наконец, возвращаются после сотен неудачных попыток. Казалось, что это никогда не сработает. Вы настоящий спасатель! Спасибо Спасибо спасибо! Я собираюсь остаться на этом веб-сайте и изо всех сил постараюсь заплатить вперед. Намасте мой друг - person Jay; 17.10.2012
comment
Рад, что это помогло! (Не забудьте сгенерировать новые API-ключи :) - person Leigh; 17.10.2012
comment
Я пришел сюда, чтобы дать вам баллы за ответ на такой сложный вопрос. - person K_Cruz; 19.10.2012
comment
@KRC - Спасибо. Жаль, что API часто недооценивают ценность нескольких конкретных примеров. Разработчикам намного легче выявлять проблемы и логические ошибки (например, с функцией hmacSHA512), когда они точно знают, как выглядит правильный ввод. - person Leigh; 20.10.2012