blog

πŸ” κ°„λ‹¨ν•˜λ©΄μ„œλ„ μ•ˆμ „ν•œ 인증 토큰 λ§Œλ“€κΈ° (Python, HMAC, ID/SECRET 기반)

λ‚ μ§œ: 2025-06-07

λͺ©λ‘μœΌλ‘œ

API μ„œλ²„ κ°„μ˜ μ•ˆμ „ν•œ ν†΅μ‹ μ΄λ‚˜ μž„μ‹œ 인증이 ν•„μš”ν•  λ•Œ μ‚¬μš©ν•  수 μžˆλŠ” HMAC 기반의 κ°„λ‹¨ν•œ 토큰 생성 λ‘œμ§μ„ μ†Œκ°œν•©λ‹ˆλ‹€.

이 방식은 ID + SECRET + TIMESTAMP μ‘°ν•©μœΌλ‘œ μƒμ„±λœ 토큰을 μ΄μš©ν•˜μ—¬, μˆ˜μ‹  μΈ‘μ—μ„œλŠ” λ™μΌν•œ μ•Œκ³ λ¦¬μ¦˜μœΌλ‘œ 토큰을 검증할 수 μžˆμŠ΅λ‹ˆλ‹€.

βœ… HMAC μΈμ¦μ΄λž€?

🧩 토큰 생성 및 검증 λͺ¨λ“ˆ (auth_token.py)

# auth_token.py
import hmac
import hashlib
import time

class AuthToken:
    def __init__(self, client_id: str, client_secret: str, hash_algo: str = 'sha256', valid_window: int = 3):
        """
        인증 토큰 생성기 μ΄ˆκΈ°ν™”

        Args:
            client_id (str): ν΄λΌμ΄μ–ΈνŠΈ μ‹λ³„μž (예: 'my-client')
            client_secret (str): ν΄λΌμ΄μ–ΈνŠΈ λΉ„λ°€ ν‚€ (HMAC μ„œλͺ…μš©)
            hash_algo (str): μ‚¬μš©ν•  ν•΄μ‹œ μ•Œκ³ λ¦¬μ¦˜ (κΈ°λ³Έκ°’: 'sha256')
            valid_window (int): 토큰 유효 μ‹œκ°„(초 λ‹¨μœ„, κΈ°λ³Έκ°’: 3초)
        """
        self.client_id = client_id
        self.secret_key = client_secret.encode()
        self.hash_algo = getattr(hashlib, hash_algo)
        self.valid_window = valid_window

    def generate(self, timestamp: int = None) -> str:
        if timestamp is None:
            timestamp = int(time.time())
        raw = f"{self.client_id}.{timestamp}"
        signature = hmac.new(self.secret_key, raw.encode(), self.hash_algo).hexdigest()
        return f"{self.client_id}:{timestamp}:{signature}"

    def verify(self, token: str) -> bool:
        try:
            parts = token.split(":")
            if len(parts) != 3:
                return False

            token_id, token_ts, token_sig = parts
            if token_id != self.client_id:
                return False

            token_ts = int(token_ts)
            now = int(time.time())
            if abs(now - token_ts) > self.valid_window:
                return False

            expected_token = self.generate(token_ts)
            return hmac.compare_digest(token, expected_token)
        except Exception:
            return False

πŸ§ͺ μ‚¬μš© μ˜ˆμ‹œ

from auth_token import AuthToken

auth = AuthToken(client_id="example-client", client_secret="shared_secret")

# 1. μ„œλ²„μ—μ„œ 토큰 생성
token = auth.generate()
print("πŸ” Generated:", token)

# 2. λ‹€λ₯Έ μ„œλ²„ λ˜λŠ” μˆ˜μ‹  μΈ‘μ—μ„œ 토큰 검증
is_valid = auth.verify(token)
print("βœ… Valid:", is_valid)

πŸ“Œ νŠΉμ§• μš”μ•½

🧠 ν™œμš© 팁

βœ… HMAC 인증 ν† ν°μ˜ μ‹€μ œ μ‚¬μš© 사둀

  1. μ„œλ²„ κ°„ 톡신 (μ„œλ²„ β†’ μ„œλ²„)

    • μ„œλ‘œ μ•Œκ³  μžˆλŠ” client_id + secret_key 기반으둜 토큰을 μƒμ„±ν•˜κ³  검증.
    • 예: λ°±μ˜€ν”ΌμŠ€ β†’ API μ„œλ²„, λͺ¨λ°”일 ν‘Έμ‹œ μ„œλ²„ β†’ 인증 μ„œλ²„
  2. λ‚΄λΆ€ API 인증 (κ³΅κ°œλ˜μ§€ μ•ŠλŠ” λ‚΄λΆ€ μ‹œμŠ€ν…œ κ°„)

    • VPNμ΄λ‚˜ λ‚΄λΆ€ λ„€νŠΈμ›Œν¬μ—μ„œ μ‹€ν–‰λ˜λŠ” λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ κ°„ 톡신.
  3. 3rd Party API 인증 (κ°„λ‹¨ν•œ API Key + μ„œλͺ… 방식)

    • 예: 금육 API (κΈˆμœ΅κ²°μ œμ›, μΉ΄λ“œμ‚¬ λ“±), λ¬Όλ₯˜ API (택배사 λ“±)
    • timestamp + path + paramsλ₯Ό HMAC으둜 μ„œλͺ… ν›„ 헀더에 포함
  4. Webhook 인증

    • μ™ΈλΆ€ μ‹œμŠ€ν…œμ—μ„œ μ „μ†‘ν•œ μ›Ήν›… μš”μ²­μ΄ μœ„μ‘°λ˜μ§€ μ•Šμ•˜λŠ”μ§€ HMAC μ„œλͺ…μœΌλ‘œ 검증

⚠️ HMAC 기반 인증 λ°©μ‹μ˜ 취약점과 ν•œκ³„

1. ν΄λΌμ΄μ–ΈνŠΈ μ‹œν¬λ¦Ώ 유좜 μ‹œ λ³΄μ•ˆ μ™„μ „ λΆ•κ΄΄

2. 토큰 μž¬μ‚¬μš© (Replay Attack)

3. HTTPS λ―Έμ‚¬μš© μ‹œ μ€‘κ°„μž 곡격

4. νƒˆμ€‘μ•™ 인증이 어렀움

βœ… κ²°λ‘  μš”μ•½

ν•­λͺ© HMAC 인증 λ°©μ‹μ˜ νŠΉμ§•
μž₯점 κ΅¬ν˜„ 간단, μ˜μ‘΄μ„± μ—†μŒ, 빠름, 토큰 μœ„μ‘° λ°©μ§€
단점 secret 유좜 μ‹œ μœ„ν—˜, νƒˆμ·¨ λ°©μ§€ ν•„μš”, μœ„μž„ 인증 어렀움
μ ν•©ν•œ 경우 μ„œλ²„ κ°„ 톡신, λ‚΄λΆ€ API, μ›Ήν›… λ³΄μ•ˆ λ“± μ œν•œλœ μ‹ λ’° ν™˜κ²½

κ°„λ‹¨ν•˜μ§€λ§Œ μ•ˆμ „ν•œ 인증 ꡬ쑰가 ν•„μš”ν•  λ•Œ μœ„ HMAC 기반 토큰 방식을 적극 ν™œμš©ν•΄ λ³΄μ„Έμš”. μΆ”ν›„ ν•„μš” μ‹œ, IP μ œν•œ, μš”μ²­ URL 포함, Nonce μΆ”κ°€ λ“±μœΌλ‘œ ν™•μž₯도 κ°€λŠ₯ν•©λ‹ˆλ‹€.

λͺ©λ‘μœΌλ‘œ