Получение данных из API с помощью HMAC SHA512 — Swift

Я пытаюсь использовать API, описанный здесь. Он использует авторизацию HMAC SHA512 на основе секретного ключа.

Вот пример реализации на PHP:

function bitmarket_api($method, $params = array())
{
  $key = "klucz_jawny";
  $secret = "klucz_tajny";

  $params["method"] = $method;
  $params["tonce"] = time();

  $post = http_build_query($params, "", "&");
  $sign = hash_hmac("sha512", $post, $secret);
  $headers = array(
      "API-Key: " . $key,
      "API-Hash: " . $sign,
  );

  $curl = curl_init();
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_URL, "https://www.bitmarket.pl/api2/");
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  $ret = curl_exec($curl);

  return json_decode($ret);
}

Затем я пытался реализовать это в Swift:

import Alamofire
import CryptoSwift

func getRawJSON(method: String, params: [String]) -> String {
    let publicKey = "publicKeyHere"
    let secretKey = "secretKeyHere"
    let APIURL = "https://www.bitmarket.pl/api2/"

    var params = [
        "method": method,
        "tonce:": NSDate().timeIntervalSince1970
        ] as [String : Any]

    let hmac: Array<UInt8> = try! HMAC(key: secretKey.utf8.map({$0}), variant: .sha512).authenticate(params)

    var headers = [
        "API-Key": publicKey,
        "API-Hash": hmac
    ] as [String : Any]
}

Как вы могли заметить, Alamofire пока не используется для получения данных, потому что у меня возникла проблема с подготовкой данных для отправки. Я имею в виду, что я что-то напутал с CryptoSwift, потому что я получаю эту ошибку: Cannot convert value of type '[String : Any]' to expected argument type 'Array<UInt8>', когда я пытаюсь объявить переменную hmac.

Как это решить? Вероятно, мне нужно каким-то образом преобразовать массив params в Array<UInt8, но я не знаю, как это сделать. Я тоже не уверен, что все правильно.

Изменить: благодаря Мартину Р. фактический код выглядит так:

func getRawJSON(method: String, paramether: String) {
    let publicKey = "publicKeyHere"
    let secretKey = "secretKeyHere"
    let APIURL = "https://www.bitmarket.pl/api2/"

    let query = NSURLComponents()
    query.queryItems = [NSURLQueryItem(name: "method", value: method) as URLQueryItem,
                        NSURLQueryItem(name: "tonce", value: String(Int(NSDate().timeIntervalSince1970))) as URLQueryItem]

    let requestString = query.query!
    let requestData = Array(requestString.utf8)

    let params = [
        "method": method,
        "tonce:": String(Int(NSDate().timeIntervalSince1970))
        ] as [String : Any]

    let hmac: Array<UInt8> = try! HMAC(key: secretKey.utf8.map({$0}), variant: .sha512).authenticate(requestData)

    let hmacData = Data(bytes: hmac)
    let hmacString = hmacData.base64EncodedString()

    let headers = [
        "API-Key": publicKey,
        "API-Hash": hmacString
    ] as [String : String]

    Alamofire.request(APIURL, withMethod: .post, parameters: params, encoding: .url, headers: headers)
        .responseJSON { response in
        print(response)
    }
}

К сожалению, после вызова функции (getRawJSON(method: "info", paramether: "")) я получаю JSON с ошибкой:

{
error = 502;
errorMsg = "Invalid message hash";
time = 1472910139;
}

Что не так с моим хешем?


person kkarol    schedule 03.09.2016    source источник


Ответы (1)


В вашем коде Swift отсутствует то, что

$post = http_build_query($params, "", "&");

делает в версии PHP: создает строку запроса из заданных параметров. Вы можете либо "вручную" создать эту строку, либо использовать NSURLComponents:

let comps = NSURLComponents()
comps.queryItems = [ NSURLQueryItem(name: "method",
                                    value: "YOUR_METHOD"),
                     NSURLQueryItem(name: "tonce",
                                    value: String(Int(NSDate().timeIntervalSince1970))) ]
let requestString = comps.query!
print(requestString) // method=YOUR_METHOD&tonce=1472893376

Наконец, преобразуйте эту строку в массив [UInt8] для функции HMAC:

let requestData = Array(requestString.utf8)
print(requestData) // [109, 101, 116, ..., 54]
person Martin R    schedule 03.09.2016
comment
Огромное спасибо, очень помогли. Теперь у меня возникла проблема с хэшем, отредактировал свой вопрос, указав более подробную информацию. - person kkarol; 03.09.2016
comment
@kkarol: сравните значения $post и $sign из кода PHP с requestString и hmac из кода Swift. - person Martin R; 03.09.2016
comment
requestString равно $post, но $sign отличается от hmac и hmacString. hmacString - это base64, а $sign содержит, например. 450043310da274122369bfc0ca621e47fb0b03857746c096a944748585fcd3280744ee4633711f98bbfe3bf0c199deec329a64b723799e597304cfd683ac40e6. Не знаю, как преобразовать Array<UInt8> в правильный формат. - person kkarol; 03.09.2016
comment
@kkarol: вам нужно преобразовать данные в шестнадцатеричную строку, а не в строку в кодировке Base64. См., например, stackoverflow.com/a/38131414/1187415. - person Martin R; 09.09.2016