API 호출 시 401 Unauthorized 또는 signature mismatch 오류로 밤새 머리를 쥐어짠 경험이 있으신가요? 저는 지난 3년간 Binance, Bybit, OKX 등 주요 거래소의 API를 연동하며 수십 번의 서명 관련 버그를 수정했습니다. 이 튜토리얼에서는 HMAC-SHA256 서명 알고리즘의 원리부터 실제 거래소 API 연동까지 상세히 다룹니다.
HMAC-SHA256 서명이 필요한 이유
암호화폐 거래소 API는 다음 세 가지 보안을 위해 HMAC-SHA256 서명을 사용합니다:
- 인증(Authentication): API 키와 서명으로 요청자의 신원 확인
- 무결성(Integrity): 전송 중 데이터 변조 감지
- 재연攻击 방지: timestamp 기반으로 동일한 요청의 재사용 차단
단순히 API 키를 헤더에 포함하는 것만으로는 제3자가 요청을 가로채 재현할 수 있습니다. HMAC-SHA256은 비밀 키를 사용하여 요청 본문의 해시를 생성함으로써 이를 방지합니다.
HMAC-SHA256 동작 원리
┌─────────────────────────────────────────────────────────────┐
│ HMAC-SHA256 흐름 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 요청 파라미터 구성 │
│ { │
│ "symbol": "BTCUSDT", │
│ "side": "BUY", │
│ "type": "LIMIT", │
│ "quantity": "0.001", │
│ "timestamp": 1718001234567, │
│ "recvWindow": 5000 │
│ } │
│ │
│ 2. 알파벳 순서 정렬 및 URL 인코딩 │
│ → symbol=BTCUSDT&side=BUY&type=LIMIT&... │
│ │
│ 3. 비밀 키(SECRET_KEY)로 HMAC-SHA256 서명 │
│ → HMAC-SHA256(정렬된 문자열, SECRET_KEY) │
│ → 64자리 16진수 해시 생성 │
│ │
│ 4. 최종 요청 구성 │
│ Headers: X-MBX-APIKEY: {API_KEY} │
│ Params: 정렬된 문자열 + &signature={해시값} │
│ │
└─────────────────────────────────────────────────────────────┘
Python实战: Binance API 서명 구현
import hmac
import hashlib
import time
import requests
from urllib.parse import urlencode
class BinanceAPIClient:
"""Binance 거래소 API 클라이언트 - HMAC-SHA256 서명 포함"""
BASE_URL = "https://api.binance.com"
def __init__(self, api_key: str, secret_key: str):
self.api_key = api_key
self.secret_key = secret_key
def _generate_signature(self, params: dict) -> str:
"""
HMAC-SHA256 서명 생성
Args:
params: 정렬된 요청 파라미터 딕셔너리
Returns:
64자리 16진수 HMAC-SHA256 해시
"""
# 파라미터를 알파벳 순으로 정렬
query_string = urlencode(sorted(params.items()))
# HMAC-SHA256으로 서명 생성
signature = hmac.new(
self.secret_key.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def _create_request(self, endpoint: str, params: dict, signed: bool = False) -> dict:
"""서명된/미서명 요청 생성"""
# 타임스탬프 추가
params['timestamp'] = int(time.time() * 1000)
params['recvWindow'] = 5000
if signed:
params['signature'] = self._generate_signature(params)
headers = {
'X-MBX-APIKEY': self.api_key,
'Content-Type': 'application/x-www-form-urlencoded'
}
url = f"{self.BASE_URL}{endpoint}"
response = requests.get(url, headers=headers, params=params)
return response.json()
def get_account_info(self) -> dict:
"""계정 정보 조회 (서명 필요)"""
return self._create_request('/api/v3/account', {}, signed=True)
def get_server_time(self) -> dict:
"""서버 시간 조회 (서명 불필요)"""
return self._create_request('/api/v3/time', {}, signed=False)
===== 사용 예시 =====
if __name__ == "__main__":
# ⚠️ 실제 API 키로 교체
client = BinanceAPIClient(
api_key="YOUR_BINANCE_API_KEY",
secret_key="YOUR_BINANCE_SECRET_KEY"
)
# 서버 시간 동기화 확인
server_time = client.get_server_time()
print(f"서버 시간: {server_time}")
# 계정 정보 조회
try:
account = client.get_account_info()
print(f"잔액 조회 성공: {account.get('balances', [])[:3]}")
except Exception as e:
print(f"오류 발생: {e}")
Node.js实战: Bybit API 서명 구현
const crypto = require('crypto');
class BybitAPIClient {
constructor(apiKey, secretKey, testnet = false) {
this.apiKey = apiKey;
this.secretKey = secretKey;
this.baseURL = testnet
? 'https://api-testnet.bybit.com'
: 'https://api.bybit.com';
}
/**
* HMAC-SHA256 서명 생성 - Bybit 방식
*
* @param {Object} params - 요청 파라미터
* @returns {string} HMAC-SHA256 16진수 해시
*/
generateSignature(params) {
// 1. 파라미터를 키순 정렬
const sortedKeys = Object.keys(params).sort();
// 2. key1=value