인증 헤더 만들기

인증 헤더 만들기

Private API 요청 시 발급받은 Connect Key와 Secret Key를 이용하여 4개의 파라미터를 헤더에 추가하여 전송합니다.

요청 변수설명타입
api-client-typeApi-Sign 생성 시 사용하는 구분자 유형
"0" : Ascii Code 0 (기본값)
"1" : Ascii Code 1
"2" : ";" (semicolon)
String/선택
Api-Key사용자 API KeyString/필수
Api-Nonce현재의 시간을 밀리초(millisecond, ms)로 표현한 값
(예 : "1655280216476")
String/필수
Api-SignEnd Point + Request Parameter + Api-Nonce + 사용자 Secret Key를
조합하여 인코딩한 값
String/필수

생성된 인증 헤더의 페이로드 예시입니다.

{
    "api-client-type" : "2",
    "Api-Sign" : "OGU1NWYxYjRmYmJjMzRmZWIyM2FkMGFkghhvbDGVSDgweY2ZkYzE2MzIwY2I2OGZlZmViOGY2OWE1MDFlZmNiZWMyNTBjOWY5NWRhMTQyZDdkOTFlNzQzZGUwNTM1MjJjMDdjNTEGgvdw2EyOTMwMzVkNzc=",
    "Api-Nonce" : "1655282125050",
    "Api-Key" : "3095809fgv90t4hnf82fls9023rlasf023nl"
}

Api-Sign 생성 안내

  1. Request Parameter 조합

    • endpoint 파라미터 추가
      endpoint=/info/balance&order_currency=BTC&payment_currency=KRW
      
    • URL 인코딩
      endpoint=%2Finfo%2Fbalance&order_currency=BTC&payment_currency=KRW
      
  2. End Point + Request Parameter + Api-Nonce 조합

    • api-client-type에서 지정한 구분자를 이용해 3개의 값을 조합합니다.
      /info/balance;endpoint=%2Finfo%2Fbalance&order_currency=BTC&payment_currency=KRW;1655283111604
      
  3. HmacSha512 알고리즘으로 인코딩

    • 2번에서 조합한 값을 사용자의 Secret Key를 이용하여 인코딩합니다.
  4. Base64 인코딩

    • HmacSha512로 인코딩한 값을 다시 Base64로 인코딩합니다.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

import org.codehaus.jackson.map.ObjectMapper;

import java.net.URLEncoder;
import java.security.InvalidKeyException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;


@SuppressWarnings("unused")
public class Api_Client {

  protected String api_url = "https://api.bithumb.com";
  protected String api_key;
  protected String api_secret;

  public Api_Client(String api_key, String api_secret) {
    this.api_key = api_key;
    this.api_secret = api_secret;
  }

  private String usecTime() {
    return String.valueOf(System.currentTimeMillis());
  }

  private String request(String strHost, String strMemod, HashMap<String, String> rgParams,
      HashMap<String, String> httpHeaders) {
    String response = "";

    // SSL
    if (strHost.startsWith("https://")) {
      HttpRequest request = HttpRequest.get(strHost);
      // Accept all certificates
      request.trustAllCerts();
      // Accept all hostnames
      request.trustAllHosts();
    }

    if (strMemod.toUpperCase().equals("HEAD")) {
    } else {
      HttpRequest request = null;

      // POST/GET
      if (strMemod.toUpperCase().equals("POST")) {

        request = new HttpRequest(strHost, "POST");
        request.readTimeout(10000);

        System.out.println("POST ==> " + request.url());

        if (httpHeaders != null && !httpHeaders.isEmpty()) {
          httpHeaders.put("api-client-type", "2");
          request.headers(httpHeaders);
          System.out.println(httpHeaders.toString());
        }
        if (rgParams != null && !rgParams.isEmpty()) {
          request.form(rgParams);
          System.out.println(rgParams.toString());
        }
      } else {
        request = HttpRequest.get(strHost
            + Util.mapToQueryString(rgParams));
        request.readTimeout(10000);

        System.out.println("Response was: " + response);
      }

      if (request.ok()) {
        response = request.body();
      } else {
        response = "error : " + request.code() + ", message : "
            + request.body();
      }
      request.disconnect();
    }

    return response;
  }

  public static String encodeURIComponent(String s) {
    String result = null;

    try {
      result = URLEncoder.encode(s, "UTF-8")
          .replaceAll("\\+", "%20")
          .replaceAll("\\%21", "!")
          .replaceAll("\\%27", "'")
          .replaceAll("\\%28", "(")
          .replaceAll("\\%29", ")")
          .replaceAll("\\%26", "&")
          .replaceAll("\\%3D", "=")
          .replaceAll("\\%7E", "~");
    }

    // This exception should never occur.
    catch (UnsupportedEncodingException e) {
      result = s;
    }

    return result;
  }

  private HashMap<String, String> getHttpHeaders(String endpoint, HashMap<String, String> rgData,
      String apiKey, String apiSecret) {

    String strData = Util.mapToQueryString(rgData).replace("?", "");
    String nNonce = usecTime();

    strData = strData.substring(0, strData.length() - 1);

    System.out.println("1 : " + strData);

    strData = encodeURIComponent(strData);
    System.out.println("2 : " + strData);
    HashMap<String, String> array = new HashMap<String, String>();

    String str = endpoint + ";" + strData + ";" + nNonce;
    String encoded = asHex(hmacSha512(str, apiSecret));

    System.out.println("strData was: " + str);
    System.out.println("apiSecret was: " + apiSecret);
    array.put("Api-Key", apiKey);
    array.put("Api-Sign", encoded);
    array.put("Api-Nonce", String.valueOf(nNonce));

    return array;

  }

  private static final String DEFAULT_ENCODING = "UTF-8";
  private static final String HMAC_SHA512 = "HmacSHA512";

  public static byte[] hmacSha512(String value, String key) {
    try {
      SecretKeySpec keySpec = new SecretKeySpec(
          key.getBytes(DEFAULT_ENCODING),
          HMAC_SHA512);

      Mac mac = Mac.getInstance(HMAC_SHA512);
      mac.init(keySpec);

      final byte[] macData = mac.doFinal(value.getBytes());
      byte[] hex = new Hex().encode(macData);

      return hex;

    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
    } catch (InvalidKeyException e) {
      throw new RuntimeException(e);
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  public static String asHex(byte[] bytes) {
    return new String(Base64.encodeBase64(bytes));
  }

  @SuppressWarnings("unchecked")
  public String callApi(String endpoint, HashMap<String, String> params) {
    String rgResultDecode = "";
    HashMap<String, String> rgParams = new HashMap<String, String>();
    rgParams.put("endpoint", endpoint);

    if (params != null) {
      rgParams.putAll(params);
    }

    String api_host = api_url + endpoint;
    HashMap<String, String> httpHeaders = getHttpHeaders(endpoint, rgParams, api_key, api_secret);

    rgResultDecode = request(api_host, "POST", rgParams, httpHeaders);

    if (!rgResultDecode.startsWith("error")) {
      // json
      HashMap<String, String> result;
      try {
        result = new ObjectMapper().readValue(rgResultDecode,
            HashMap.class);

        System.out.println(result.get("status"));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return rgResultDecode;
  }
}

import time
import math
import base64
import hmac, hashlib
import urllib.parse
import requests

class XCoinAPI:
	api_url = "https://api.bithumb.com";
	api_key = "";
	api_secret = "";

	def __init__(self, api_key, api_secret):
		self.api_key = api_key;
		self.api_secret = api_secret;

	def body_callback(self, buf):
		self.contents = buf;

	def microtime(self, get_as_float = False):
		if get_as_float:
			return time.time()
		else:
			return '%f %d' % math.modf(time.time())

	def usecTime(self) :
		mt = self.microtime(False)
		mt_array = mt.split(" ")[:2];
		return mt_array[1] + mt_array[0][2:5];

	def xcoinApiCall(self, endpoint, rgParams):
		# 1. Api-Sign and Api-Nonce information generation.
		# 2. Request related information from the Bithumb API server.
		#
		# - nonce: it is an arbitrary number that may only be used once.
		# - api_sign: API signature information created in various combinations values.

		endpoint_item_array = {
			"endpoint" : endpoint
		}

		uri_array = dict(endpoint_item_array, **rgParams) # Concatenate the two arrays.

		str_data = urllib.parse.urlencode(uri_array)

		nonce = self.usecTime()

		data = endpoint + chr(0) + str_data + chr(0) + nonce
		utf8_data = data.encode('utf-8')

		key = self.api_secret
		utf8_key = key.encode('utf-8')

		h = hmac.new(bytes(utf8_key), utf8_data, hashlib.sha512)
		hex_output = h.hexdigest()
		utf8_hex_output = hex_output.encode('utf-8')

		api_sign = base64.b64encode(utf8_hex_output)
		utf8_api_sign = api_sign.decode('utf-8')

		headers = {
			"Accept": "application/json",
			"Content-Type": "application/x-www-form-urlencoded",
			"Api-Key": self.api_key,
			"Api-Nonce": nonce,
			"Api-Sign": utf8_api_sign
		}

		url = self.api_url + endpoint

		r = requests.post(url, headers=headers, data=rgParams)
		return r.json()

const request = require("request");
const hmacSHA512 = require("crypto-js/hmac-sha512");

const XCoinAPI = class {
  constructor(api_key, api_secret) {
    this.apiUrl = "https://api.bithumb.com";
    this.api_key = api_key;
    this.api_secret = api_secret;
  }
  xcoinApiCall(endPoint, params) {
    let rgParams = {
      endPoint: endPoint,
    };

    if (params) {
      for (let o in params) {
        rgParams[o] = params[o];
      }
    }

    const api_host = this.apiUrl + endPoint;
    const httpHeaders = this._getHttpHeaders(
      endPoint,
      rgParams,
      this.api_key,
      this.api_secret
    );

    const options = {
      method: "POST",
      url: api_host,
      headers: httpHeaders,
      form: rgParams,
    };
    return new Promise(function (resolve, reject) {
      request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
          resolve(response);
        } else {
          reject(error);
        }
      });
    });
  }
  _getHttpHeaders(endPoint, rgParams, api_key, api_secret) {
    let strData = http_build_query(rgParams);
    let nNonce = this.usecTime();
    return {
      "Api-Key": api_key,
      "Api-Sign": base64_encode(
        hmacSHA512(
          endPoint + chr(0) + strData + chr(0) + nNonce,
          api_secret
        ).toString()
      ),
      "Api-Nonce": nNonce,
    };
  }
  usecTime() {
    let rgMicrotime = microtime().split(" "),
      usec = rgMicrotime[0],
      sec = rgMicrotime[1];

    usec = usec.substr(2, 3);
    return Number(String(sec) + String(usec));
  }
};

const microtime = (get_as_float) => {
  //  discuss at: http://phpjs.org/functions/microtime/
  //	original by: Paulo Freitas
  //  example 1: timeStamp = microtime(true);
  //  example 1: timeStamp > 1000000000 && timeStamp < 2000000000
  //  returns 1: true
  const now = new Date().getTime() / 1000;
  const s = parseInt(now, 10);

  return get_as_float ? now : Math.round((now - s) * 1000) / 1000 + " " + s;
};

const http_build_query = (obj) => {
  let output_string = [];
  Object.keys(obj).forEach((val) => {
    let key = val;
    key = encodeURIComponent(key.replace(/[!'()*]/g, escape));

    if (typeof obj[val] === "object") {
      let query = build_query(obj[val], null, key);
      output_string.push(query);
    } else {
      let value = encodeURIComponent(obj[val].replace(/[!'()*]/g, escape));
      output_string.push(key + "=" + value);
    }
  });

  return output_string.join("&");
};

const base64_encode = (data) => {
  // discuss at: http://phpjs.org/functions/base64_encode/
  // original by: Tyler Akins (http://rumkin.com)
  // improved by: Bayron Guevara
  // improved by: Thunder.m
  // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // improved by: Rafał Kukawski (http://kukawski.pl)
  // bugfixed by: Pellentesque Malesuada
  // example 1: base64_encode('Kevin van Zonneveld');
  // returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
  // example 2: base64_encode('a');
  // returns 2: 'YQ=='

  const b64 =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  let o1,
    o2,
    o3,
    h1,
    h2,
    h3,
    h4,
    bits,
    i = 0,
    ac = 0,
    enc = "",
    tmp_arr = [];

  if (!data) {
    return data;
  }

  do {
    // pack three octets into four hexets
    o1 = data.charCodeAt(i++);
    o2 = data.charCodeAt(i++);
    o3 = data.charCodeAt(i++);

    bits = (o1 << 16) | (o2 << 8) | o3;

    h1 = (bits >> 18) & 0x3f;
    h2 = (bits >> 12) & 0x3f;
    h3 = (bits >> 6) & 0x3f;
    h4 = bits & 0x3f;

    // use hexets to index into b64, and append result to encoded string
    tmp_arr[ac++] =
      b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
  } while (i < data.length);

  enc = tmp_arr.join("");

  const r = data.length % 3;

  return (r ? enc.slice(0, r - 3) : enc) + "===".slice(r || 3);
};

const chr = (codePt) => {
  //  discuss at: http://phpjs.org/functions/chr/
  // original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // improved by: Brett Zamir (http://brett-zamir.me)
  //   example 1: chr(75) === 'K';
  //   example 1: chr(65536) === '\uD800\uDC00';
  //   returns 1: true
  //   returns 1: true

  if (codePt > 0xffff) {
    // Create a four-byte string (length 2) since this code point is high
    //   enough for the UTF-16 encoding (JavaScript internal use), to
    //   require representation with two surrogates (reserved non-characters
    //   used for building other characters; the first is "high" and the next "low")
    codePt -= 0x10000;
    return String.fromCharCode(
      0xd800 + (codePt >> 10),
      0xdc00 + (codePt & 0x3ff)
    );
  }
  return String.fromCharCode(codePt);
};

module.exports.XCoinAPI = XCoinAPI;

샘플 소스 전체 다운로드