API 호출 시 401 Unauthorized 또는 signature mismatch 오류로 밤새 머리를 쥐어짠 경험이 있으신가요? 저는 지난 3년간 Binance, Bybit, OKX 등 주요 거래소의 API를 연동하며 수십 번의 서명 관련 버그를 수정했습니다. 이 튜토리얼에서는 HMAC-SHA256 서명 알고리즘의 원리부터 실제 거래소 API 연동까지 상세히 다룹니다.

HMAC-SHA256 서명이 필요한 이유

암호화폐 거래소 API는 다음 세 가지 보안을 위해 HMAC-SHA256 서명을 사용합니다:

단순히 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