Python HMAC hmac.new() SHA-256

·DevOps Engineer & Python Automation Specialist·검토자Maria Santos·게시일

무료 HMAC Generator을 브라우저에서 직접 사용하세요 — 설치 불필요.

HMAC Generator 온라인으로 사용하기 →

모든 웹훅 콜백, 모든 서명된 API 요청, Stripe나 GitHub의 모든 이벤트 알림은 HMAC 서명을 사용하여 페이로드가 변조되지 않았음을 증명합니다. Python의 hmac 모듈은 단 하나의 함수 호출로 Python HMAC-SHA256을 처리합니다: hmac.new(key, msg, hashlib.sha256). pip install도, C 확장도, 서드파티 의존성도 필요하지 않습니다. 코드 작성 없이 빠르게 서명을 확인하고 싶다면 온라인 HMAC 생성기 에서 즉시 결과를 얻을 수 있습니다. 이 가이드에서는 hmac.new(), hmac.digest(), hmac.compare_digest(), Base64 인코딩, 웹훅 검증, API 요청 서명, SHA-1부터 SHA-512까지 모든 해시 알고리즘을 다룹니다. 모든 예제는 Python 3.7+를 대상으로 합니다.

  • hmac.new(key, msg, hashlib.sha256)가 표준 진입점 — key와 msg는 bytes여야 하며, digestmod는 Python 3.4부터 필수입니다.
  • hmac.digest(key, msg, "sha256")는 Python 3.7에 추가된 더 빠른 단일 호출 방식 — 중간 객체 없이 원시 bytes를 반환합니다.
  • 서명 검증에는 항상 hmac.compare_digest()를 사용하여 타이밍 공격을 방지하세요 — HMAC 비교에 ==를 절대 사용하지 마세요.
  • HTTP 헤더와 웹훅 서명을 위해 원시 .digest() 출력을 Base64로 인코딩하세요: base64.b64encode(h.digest()).
  • hmac 모듈은 sha1, sha256, sha384, sha512, md5, blake2b 등 모든 hashlib 알고리즘을 지원합니다.

HMAC이란?

HMAC(Hash-based Message Authentication Code)은 RFC 2104 에 정의된 구조로, 비밀 키와 해시 함수를 결합하여 고정 크기의 인증 태그를 생성합니다. 누구나 계산할 수 있는 일반 해시와 달리, HMAC은 비밀 키에 대한 지식이 있어야 합니다. 즉, 메시지의 무결성과 진위성을 모두 검증하는 데 사용할 수 있습니다. 메시지나 키의 단 한 바이트만 변경되어도 출력이 완전히 달라집니다. 이 구조는 두 개의 서로 다른 패딩 상수(ipad와 opad)로 XOR된 키를 해시하여 두 해시 연산 사이에 메시지를 감쌉니다. Python의 hmac 모듈은 이 RFC를 직접 구현합니다.

Before · Python
After · Python
# 일반 SHA-256 해시 — 비밀 키 없음, 누구나 계산 가능
hashlib.sha256(b"payment:9950:USD").hexdigest()
# "7a3b1c..."  (결정적, 공개)
# HMAC-SHA256 — 비밀 키가 있어야 생성 가능
hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest()
# "e4f2a8..."  (키 보유자만 계산 가능)

hmac.new() — 표준 라이브러리 진입점

hmac 모듈은 Python 표준 라이브러리에 포함되어 있습니다. 두 줄의 임포트만 하면 준비가 됩니다: import hmac, hashlib. 세 가지 주요 함수는 hmac.new() (HMAC 객체 생성), hmac.digest() (단일 호출, Python 3.7+), 그리고 hmac.compare_digest() (상수 시간 비교)입니다. pip install이 필요 없습니다.

hmac.new(key, msg, digestmod) 은 세 개의 인자를 받습니다. key msg 모두 bytes 유사 객체여야 합니다 ( bytes, bytearray, 또는 memoryview). digestmod 인자는 Python 3.4부터 필수이며 모든 hashlib 생성자(예: hashlib.sha256) 또는 "sha256"과 같은 문자열 이름을 받습니다.

Python 3.7+ — 최소한의 HMAC-SHA256 예제
import hmac
import hashlib

key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'

# HMAC 객체를 생성하고 16진수 서명을 가져옴
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"

HMAC 객체는 두 가지 출력 메서드를 제공합니다. .digest() 는 원시 bytes를 반환합니다(SHA-256의 경우 32바이트, SHA-512의 경우 64바이트). .hexdigest() 는 소문자 16진수 문자열을 반환합니다. 16진수 문자열은 일반 Python str 입니다 — 별도의 디코딩 단계가 필요 없습니다.

Python 3.7+ — .digest() 대 .hexdigest()
import hmac
import hashlib

key = b"service_auth_key"
msg = b"GET /api/v2/orders 2026-03-28T14:30:00Z"

h = hmac.new(key, msg, hashlib.sha256)

raw_bytes = h.digest()
print(type(raw_bytes), len(raw_bytes))
# <class 'bytes'> 32

hex_string = h.hexdigest()
print(type(hex_string), len(hex_string))
# <class 'str'> 64

# 동일한 데이터를 나타냄 — hex는 bytes의 문자열 인코딩일 뿐
assert raw_bytes.hex() == hex_string

키나 메시지가 Python 문자열인 경우 hmac.new()에 전달하기 전에 .encode() 를 호출하여 bytes로 변환하세요. 처음에는 거의 모든 사람이 이 실수를 합니다 — Python 3 문자열은 유니코드이고, hmac 모듈은 문자열을 거부합니다.

Python 3.7+ — 문자열을 bytes로 인코딩
import hmac
import hashlib

# 문자열 키와 메시지 — .encode()로 UTF-8 bytes로 변환
api_key = "sk_live_9f3a2b7c4d8e"
request_body = '{"customer_id":"cust_4421","plan":"enterprise"}'

signature = hmac.new(
    api_key.encode(),
    request_body.encode(),
    hashlib.sha256
).hexdigest()

print(signature)
# "3a9f1b..."  — 일관된 16진수 문자열 출력
참고:digestmod 파라미터는 Python 3.4부터 기본값이 없습니다. 이 없이 hmac.new(key, msg)를 호출하면 TypeError가 발생합니다. 3.4 이전에는 MD5가 기본값이었는데, Python 유지 관리자들이 명시적이고 안전한 선택을 강제하기 위해 기본값을 제거했습니다.

HMAC-SHA256 Base64, SHA-1, SHA-512, MD5

hmac.new() 함수는 hashlib에서 사용 가능한 모든 해시 알고리즘과 함께 작동합니다. 대부분의 웹훅 제공업체와 API 게이트웨이는 HMAC-SHA256을 사용하지만, OAuth 1.0a에서는 SHA-1을, 이를 요구하는 프로토콜에서는 SHA-512를, 아직 업데이트되지 않은 레거시 시스템에서는 MD5를 접하게 됩니다.

Base64 출력을 사용하는 HMAC-SHA256

많은 웹훅 제공업체는 HTTP 헤더에 Base64로 인코딩된 문자열로 서명을 전송합니다. 같은 형식을 생성하려면 원시 .digest() bytes를 base64.b64encode()에 전달하세요.

Python 3.7+ — HMAC-SHA256 Base64 인코딩
import hmac
import hashlib
import base64

key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'

# 원시 다이제스트 → Base64 (Authorization 헤더와 웹훅 서명에 일반적으로 사용)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")

print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

# X-Signature 헤더와 비교할 값
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

HMAC-SHA1 — 레거시 프로토콜 호환성

SHA-1은 새로운 설계에서 취약한 것으로 간주되지만, HMAC-SHA1은 OAuth 1.0a와 일부 구형 웹훅 구현에서 여전히 필요합니다. 코드는 동일합니다 — 알고리즘만 바꾸면 됩니다.

Python 3.7+ — HMAC-SHA1
import hmac
import hashlib

consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"

# OAuth 1.0a는 consumer_secret&token_secret을 서명 키로 사용
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"

sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()

import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..."  — Authorization 헤더에 URL 인코딩하여 사용

HMAC-SHA512 — 더 긴 출력

Python 3.7+ — HMAC-SHA512
import hmac
import hashlib

key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'

h = hmac.new(key, msg, hashlib.sha512)

print(len(h.digest()))   # 64바이트 (512비트)
print(len(h.hexdigest()))  # 128개의 16진수 문자
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — 레거시 전용

Python 3.7+ — HMAC-MD5
import hmac
import hashlib

# MD5는 암호학적으로 취약 — 레거시 프로토콜 호환성에만 사용
key = b"legacy_api_key"
msg = b"action=charge&amount=1500&merchant=store_42"

sig = hmac.new(key, msg, hashlib.md5).hexdigest()
print(sig)  # 32자 16진수 문자열
# "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
경고:HMAC-MD5는 마이그레이션할 수 없는 시스템과의 하위 호환성에만 허용됩니다. 새로운 프로젝트에서는 최소한 HMAC-SHA256을 사용하세요. MD5는 알려진 충돌 취약점이 있으며, HMAC 모드에서는 직접적인 악용 가능성이 낮더라도 기본 선택으로는 좋지 않습니다.

hmac.new() 파라미터 참조

생성자 시그니처는 hmac.new(key, msg=None, digestmod)입니다. 모듈의 세 함수 모두 키와 알고리즘 인자에 동일한 패턴을 공유합니다.

hmac.new() 생성자

파라미터
타입
기본값
설명
key
bytes | bytearray
(필수)
비밀 키 — 문자열이 아닌 bytes여야 함
msg
bytes | None
None
해시할 초기 메시지; .update()로 추가 데이터를 공급할 수 있음
digestmod
str | callable
(필수)
해시 알고리즘 — 예: hashlib.sha256 또는 문자열 "sha256"

hmac.digest() 단일 호출 (Python 3.7+)

파라미터
타입
설명
key
bytes
비밀 키
msg
bytes
인증할 메시지
digest
str | callable
해시 알고리즘 — hmac.new()의 digestmod와 동일

digestmod 파라미터는 callable(예: hashlib.sha256) 또는 문자열 이름(예: "sha256") 을 받습니다. callable 형태가 권장됩니다 — 임포트 시점에 검증되기 때문입니다. 문자열 형태의 오타는 런타임에만 실패합니다.

hmac.digest() — 빠른 단일 호출 HMAC (Python 3.7+)

Python 3.7은 hmac.digest(key, msg, digest) 를 모듈 수준 함수로 추가했습니다. 중간 HMAC 객체를 생성하지 않고 단일 호출로 HMAC을 계산합니다. 반환값은 원시 bytes입니다(객체에서 .digest()를 호출하는 것과 동일). 이 함수는 CPython에서 최적화된 C 구현을 사용하고 객체 할당 오버헤드를 피하므로, 반복 처리가 많은 코드에서 측정 가능한 속도 차이를 만듭니다.

Python 3.7+ — hmac.digest() 단일 호출
import hmac
import hashlib

key = b"batch_signing_key_2026"
messages = [
    b'{"order_id":"ord_001","total":4500}',
    b'{"order_id":"ord_002","total":8900}',
    b'{"order_id":"ord_003","total":2200}',
]

# 단일 호출 다이제스트 — 중간 HMAC 객체 없음
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]

# 출력을 위해 16진수로 변환
for msg, sig in zip(messages, signatures):
    print(f"{msg[:30]}... -> {sig.hex()[:24]}...")

한계: hmac.digest() 는 원시 bytes만 반환합니다. 16진수 문자열이 바로 필요하다면 여전히 hmac.new() .hexdigest() 메서드가 필요하거나, bytes 결과에 .hex() 를 연결해야 합니다.

참고:hmac.digest()는 점진적 .update() 호출을 지원하지 않습니다. 대용량 파일을 청크 단위로 읽어 HMAC을 계산해야 한다면 hmac.new()를 사용하고 루프에서 .update(chunk)를 호출하세요.

웹훅 및 API 응답의 HMAC 서명 검증

Python에서 HMAC의 가장 일반적인 용도는 웹훅 서명 검증입니다. 모든 주요 제공업체(Stripe, GitHub, Shopify, Twilio)는 HMAC-SHA256으로 페이로드에 서명하고 헤더로 서명을 전송합니다. 패턴은 항상 동일합니다: 원시 요청 본문에 대해 HMAC을 재계산한 후 hmac.compare_digest()로 비교합니다.

웹훅 서명 검증

Python 3.7+ — 웹훅 HMAC 검증 (Flask)
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"

@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
    # 원시 본문 가져오기 — 서명된 것과 정확히 일치해야 함
    raw_body = request.get_data()

    # 헤더에서 서명 가져오기
    received_sig = request.headers.get("X-Signature-256", "")

    # 원시 본문에 대해 HMAC 재계산
    expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    # 상수 시간 비교 — 타이밍 공격 방지
    if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
        abort(403, "Invalid signature")

    # 서명 검증 완료 — 이벤트 처리
    event = request.get_json()
    print(f"Verified event: {event['type']} for {event['data']['amount']}")
    return "", 200

hmac.compare_digest() 함수는 두 문자열 또는 바이트 시퀀스를 상수 시간으로 비교합니다. 일반 == 비교는 첫 번째 불일치 바이트에서 단락 평가를 합니다. 공격자는 여러 요청에 걸쳐 응답 시간을 측정하여 예상 서명을 바이트 단위로 점진적으로 재구성할 수 있습니다. 상수 시간 비교는 이 부채널을 제거합니다.

GitHub 웹훅 검증

GitHub의 웹훅 형식은 전체 패턴을 잘 보여줍니다. GitHub는 X-Hub-Signature-256 헤더를 통해 sha256= 뒤에 리포지토리 설정에서 구성한 웹훅 비밀로 서명된 원시 요청 본문의 HMAC-SHA256 16진수를 전송합니다. 일반 웹훅 검증과의 주요 차이점은 sha256= 접두사를 비교 전에 제거해야 하며, 요청 본문의 원시 bytes를 읽어야 한다는 것입니다 — JSON을 먼저 파싱하면 바이트 표현이 바뀌어 검증이 실패합니다.

Python 3.7+ — GitHub X-Hub-Signature-256 검증
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"

@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
    # GitHub 전송 형식: X-Hub-Signature-256: sha256=<hex_digest>
    sig_header = request.headers.get("X-Hub-Signature-256", "")

    if not sig_header.startswith("sha256="):
        abort(403, "Missing or malformed signature header")

    received_hex = sig_header[len("sha256="):]
    raw_body = request.get_data()  # 원시 bytes — JSON 파싱 전에 읽어야 함

    expected_hex = hmac.new(
        GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_hex, received_hex):
        abort(403, "Signature mismatch — payload may have been tampered with")

    event_type = request.headers.get("X-GitHub-Event", "unknown")
    payload = request.get_json()
    print(f"Verified GitHub {event_type} event: {payload.get('action', '')}")
    return "", 200

동일한 패턴이 Shopify(X-Shopify-Hmac-SHA256) 와 Twilio(X-Twilio-Signature) 에도 적용되며, 헤더 이름과 서명이 16진수인지 Base64인지만 다릅니다. 항상 제공업체 문서를 확인하여 인코딩 형식을 확인하세요 — 16진수와 Base64를 혼동하는 것이 서명 불일치 오류의 가장 흔한 원인입니다.

HMAC을 사용한 API 요청 인증

일부 API는 클라이언트가 HMAC으로 각 요청에 서명하도록 요구합니다. 서명 문자열에는 일반적으로 HTTP 메서드, 경로, 타임스탬프, 요청 본문이 포함됩니다. 다음은 내부 서비스 간 인증에 사용하는 패턴입니다.

Python 3.7+ — HMAC-SHA256으로 API 요청에 서명
import hmac
import hashlib
import time
import json

def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
    """API 요청을 위한 HMAC-SHA256 서명 생성."""
    timestamp = str(int(time.time()))

    # 서명 문자열 구성 — 메서드 + 경로 + 타임스탬프 + 본문
    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"

    signature = hmac.new(
        secret,
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    return {
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

# 사용 예시
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})

headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}

requests 라이브러리로 HTTP 요청 서명

Python 3.7+ — requests 라이브러리로 HMAC 서명된 요청
import hmac
import hashlib
import time
import json
import requests

API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"

def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"))  # 간결한 JSON, 결정적
    timestamp = str(int(time.time()))

    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
    signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()

    headers = {
        "Content-Type": "application/json",
        "X-Timestamp": timestamp,
        "X-Signature": f"hmac-sha256={signature}",
    }

    try:
        return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
    except requests.RequestException as e:
        raise RuntimeError(f"Signed request failed: {e}") from e

# 서명된 POST 요청 전송
resp = make_signed_request("POST", "/api/v2/invoices", {
    "customer_id": "cust_4421",
    "line_items": [
        {"description": "Pro plan - March 2026", "amount": 4900},
        {"description": "Extra seats (3)", "amount": 2100},
    ],
})
print(resp.status_code, resp.json())

본문을 서명할 때 separators=(",", ":") 를 사용하세요. 기본 json.dumps() 는 구분자 뒤에 공백을 추가하는데, 서버가 다르게 직렬화하면 바이트 표현이 달라져 서명 검증에 실패합니다. 간결한 JSON은 정규 형식을 보장합니다.

커맨드라인 HMAC 생성

스크립트 없이 HMAC을 계산해야 할 때가 있습니다. Python의 -c 플래그와 openssl 모두 터미널에서 처리할 수 있습니다.

bash — Python 한 줄짜리로 HMAC-SHA256
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# 출력: 64자 16진수 문자열
bash — openssl로 HMAC-SHA256 (비교용)
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret"
# SHA2-256(stdin)= 7d11...

# 파일을 openssl HMAC으로 처리
openssl dgst -sha256 -hmac "my_secret" < payload.json
bash — 환경 변수 키에서 HMAC
# 쉘 기록 노출 방지를 위해 키를 환경 변수에 저장
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"
참고:echo -n 플래그가 중요합니다 — 없으면 echo가 메시지에 개행 문자를 추가하여 HMAC 출력이 달라집니다. 터미널에서 디버깅할 때 서명이 일치하지 않는 가장 흔한 원인입니다.

고성능 대안 — cryptography 라이브러리

대부분의 애플리케이션에서 표준 hmac 모듈은 충분히 빠릅니다. TLS나 인증서 처리를 위해 이미 cryptography 라이브러리를 사용하고 있다면, OpenSSL 기반 HMAC도 제공합니다. 표준 라이브러리와의 주요 실용적 차이는 아래에 설명하는 예외 기반 .verify() API입니다 — 불일치 시 불리언 대신 예외를 발생시켜 검증 실패를 무심코 무시하기 어렵게 합니다.

bash — cryptography 설치
pip install cryptography
Python 3.7+ — cryptography 라이브러리로 HMAC
from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac

key = b"webhook_signing_key_2026"
message = b'{"event":"subscription.renewed","plan":"enterprise"}'

h = crypto_hmac.HMAC(key, hashes.SHA256())
h.update(message)
signature = h.finalize()  # 원시 bytes

print(signature.hex())
# "9c4e2a..."

# 검증 모드 — 불일치 시 InvalidSignature 발생
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature)  # 오류 시 cryptography.exceptions.InvalidSignature 발생

cryptography 라이브러리의 .verify() 메서드는 특히 유용합니다: 불일치 시 불리언 대신 예외를 발생시킵니다. 이렇게 하면 검증 실패를 실수로 무시하기 어렵습니다. 표준 라이브러리의 hmac.compare_digest() True/ False를 반환하는데, 개발자가 반환값 확인을 잊으면 조용히 무시될 수 있습니다.

터미널 출력과 구문 강조

터미널에서 HMAC 서명을 디버깅하면서 색상 출력을 원한다면 rich 가 이를 잘 처리합니다.

bash — rich 설치
pip install rich
Python 3.7+ — rich로 색상 HMAC 출력
import hmac
import hashlib
from rich.console import Console
from rich.table import Table

console = Console()

key = b"debug_signing_key"
messages = {
    "/api/v2/orders": b'{"status":"active"}',
    "/api/v2/invoices": b'{"status":"pending"}',
    "/api/v2/customers": b'{"status":"verified"}',
}

table = Table(title="HMAC-SHA256 Signatures")
table.add_column("Endpoint", style="cyan")
table.add_column("Signature (first 32 chars)", style="green")

for endpoint, body in messages.items():
    sig = hmac.new(key, body, hashlib.sha256).hexdigest()
    table.add_row(endpoint, sig[:32] + "...")

console.print(table)
참고:Rich는 터미널 표시 전용입니다. 파일, HTTP 헤더, 로그 집계 시스템에 HMAC 서명을 작성할 때는 사용하지 마세요 — ANSI 이스케이프 코드가 출력을 손상시킵니다.

대용량 파일 처리 — 점진적 HMAC

50MB 이상의 파일에서 HMAC을 계산하기 위해 모든 내용을 메모리에 올리는 것은 낭비입니다. HMAC 객체의 .update() 메서드를 사용하면 데이터를 청크 단위로 공급할 수 있습니다. 이렇게 하면 파일 크기에 관계없이 메모리 사용량이 일정하게 유지됩니다.

Python 3.7+ — 대용량 파일을 청크 단위로 HMAC 처리
import hmac
import hashlib

def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
    """파일 전체를 메모리에 올리지 않고 HMAC-SHA256 계산."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    try:
        with open(filepath, "rb") as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                h.update(chunk)
    except OSError as e:
        raise OSError(f"Cannot read file '{filepath}': {e}") from e

    return h.hexdigest()

# 2GB 데이터베이스 내보내기 파일에 서명
signing_key = b"backup_integrity_key_2026"
signature = hmac_file(signing_key, "/var/backups/db-export-2026-03.sql.gz")
print(f"HMAC-SHA256: {signature}")
Python 3.7+ — 다운로드된 파일의 HMAC 서명 검증
import hmac
import hashlib

def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
    """파일의 HMAC-SHA256 서명 검증."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)

    return hmac.compare_digest(h.hexdigest(), expected_hex)

# 다운로드된 아티팩트 검증
is_valid = verify_file_hmac(
    key=b"release_signing_key",
    filepath="/tmp/release-v3.2.0.tar.gz",
    expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"파일 무결성: {'유효' if is_valid else '손상됨'}")
참고:파일이 50-100MB를 초과하거나 메모리가 중요한 웹 서버에서 업로드를 처리할 때 청크 단위 HMAC으로 전환하세요. .update() 방식은 파일 크기에 관계없이 고정된 chunk_size만큼의 메모리만 사용합니다. 기본값으로 64KB 청크를 사용합니다 — 시스템 호출 오버헤드를 분산하기에 충분히 크고 대부분의 하드웨어에서 L2 캐시 내에 들어올 만큼 충분히 작습니다.

Python에서 암호학적으로 안전한 HMAC 키 생성

약하거나 예측 가능한 키는 전체 HMAC 구조를 무너뜨립니다. secrets 모듈(Python 3.6+)은 암호학적으로 강력한 무작위 bytes를 제공합니다. HMAC-SHA256에는 32바이트 키를, HMAC-SHA512에는 64바이트를 사용하세요. 이는 각 해시 알고리즘의 내부 블록 크기와 일치하며 RFC 2104의 최적 키 길이 권장 사항입니다.

Python 3.7+ — secrets로 HMAC 키 생성
import secrets

# 해시 알고리즘의 블록 크기에 맞는 키 생성
sha256_key = secrets.token_bytes(32)   # 256비트 — HMAC-SHA256용
sha512_key = secrets.token_bytes(64)   # 512비트 — HMAC-SHA512용

# 16진수 표현 — 설정 파일과 환경 변수에 안전
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# 예: "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"

print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# 128자 16진수 문자열

# URL 안전 Base64 — 간결하며 HTTP 헤더에 안전
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# 예: "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="
경고:HMAC 키에 random.random()이나 random.randbytes()를 절대 사용하지 마세요. random 모듈은 Mersenne Twister PRNG를 사용하는데, 624개의 출력을 관찰하면 예측 가능합니다. 보안에 민감한 무작위성에는 항상 secrets.token_bytes()를 사용하세요.

키 길이와 RFC 2104 요구사항

RFC 2104는 HMAC 키가 어떤 길이도 될 수 있지만, 최소 L바이트를 권장합니다 — L은 기반 해시 함수의 출력 길이입니다. HMAC-SHA256의 경우 32바이트(256비트)입니다. L비트보다 짧은 키는 보안 여유도를 비례적으로 줄입니다. 해시의 블록 크기(SHA-256의 경우 64바이트, SHA-512의 경우 128바이트)보다 긴 키는 먼저 블록 크기로 해시되므로, 블록 크기보다 긴 키를 사용해도 이점이 없습니다. HMAC-SHA256에는 32바이트, HMAC-SHA512에는 64바이트를 사용하세요.

안전한 키 저장 및 교체

소스 코드에 HMAC 키를 하드코딩하지 마세요. 프로덕션 환경의 표준 방법은 시작 시 환경 변수에서 키를 로드하는 것입니다: os.environ["HMAC_SECRET"].encode(). 보안이 더 중요한 환경에서는 AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager 등의 비밀 관리 시스템에 키를 저장하고 런타임에 가져오세요. 이러한 시스템은 코드 배포 없이 감사 로그, 접근 제어, 자동 교체를 제공합니다.

처음부터 키 교체를 계획하세요. 키가 교체될 때 이미 전송 중인 요청은 이전 키로 서명되어 새 키에 대한 검증에 실패하는 창이 있습니다. 표준 완화 방법은 짧은 중복 기간입니다: 짧은 시간(분에서 시간) 동안 이전 키와 새 키 모두에서 서명을 수락한 후 이전 키를 폐기합니다. 키가 노출된 경우 — 로그에 노출되거나 git 커밋을 통해 유출되거나 사고로 공개된 경우 — 즉시 교체하고 노출된 키로 생성된 모든 서명을 신뢰할 수 없는 것으로 처리하세요. 캐시된 검증 결과를 재검증하고 키 변경을 하위 소비자에게 알리세요.

hmac.new()에서 bytearray와 memoryview 사용

hmac.new() 함수는 키와 msg 파라미터 모두에서 bytes 유사 객체를 허용합니다. 즉, 복사나 변환 없이 bytes, bytearray, 또는 memoryview 를 직접 전달할 수 있습니다. 이는 두 가지 시나리오에서 특히 중요합니다:socket.recv_into()가 미리 할당된 bytearray 버퍼에 데이터를 쓰는 네트워크 프로토콜 구현, 그리고 중간 복사 제거가 GC 부담을 줄이는 고처리량 시스템입니다. memoryview 슬라이스는 새 메모리를 할당하지 않고 원래 버퍼에 대한 뷰를 노출하는 제로 복사입니다. 초당 수만 개의 메시지를 처리할 때 이러한 할당 제거는 지연 시간과 처리량에 측정 가능한 차이를 만듭니다.

Python 3.7+ — HMAC에서 bytearray와 memoryview 사용
import hmac
import hashlib

# bytearray — 바이너리 프로토콜 버퍼에 유용한 가변 bytes
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')

sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Frame signature: {sig[:32]}...")

# memoryview — 더 큰 버퍼의 제로 복사 슬라이스
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"

# 복사 없이 처음 20바이트만 HMAC 처리
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")

일반적인 실수

처음 두 가지 실수는 웹훅 핸들러가 포함된 거의 모든 코드 리뷰에서 발견됩니다. 시간 압박 속에서 쉽게 발생하고 무엇을 찾아야 하는지 알지 못하면 발견하기 어렵습니다.

hmac.compare_digest() 대신 ==로 HMAC 서명 비교

문제: == 연산자는 첫 번째 바이트 불일치에서 단락 평가를 하여 타이밍 정보를 유출하고, 공격자가 예상 서명을 점진적으로 재구성할 수 있게 합니다.

해결책: 서명 비교에는 항상 hmac.compare_digest()를 사용하세요 — 불일치가 어디서 발생하든 상수 시간에 실행됩니다.

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if received_sig == expected_sig:  # 타이밍 공격에 취약
    process_webhook(body)
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if hmac.compare_digest(received_sig, expected_sig):  # 상수 시간
    process_webhook(body)
hmac.new()에 bytes 대신 문자열 전달

문제: hmac.new()는 bytes 유사 객체를 요구합니다. Python str을 전달하면 TypeError: "key: expected bytes or bytearray, but got 'str'"가 발생합니다.

해결책: hmac.new()에 전달하기 전에 문자열 키와 메시지에 .encode()를 호출하세요.

Before · Python
After · Python
key = "my_api_secret"  # str, bytes가 아님
msg = '{"event":"test"}'  # str, bytes가 아님
sig = hmac.new(key, msg, hashlib.sha256)  # TypeError!
key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)
digestmod 누락 (Python 3.4+)

문제: 세 번째 인자 없이 hmac.new(key, msg)를 호출하면 TypeError가 발생합니다. Python 3.4 이전에는 MD5가 기본값이었지만 보안상의 이유로 기본값이 제거되었습니다.

해결책: 항상 알고리즘을 명시적으로 전달하세요: hashlib.sha256, hashlib.sha512, 또는 프로토콜이 요구하는 알고리즘.

Before · Python
After · Python
# digestmod 누락 — Python 3.4+에서 TypeError 발생
sig = hmac.new(key, msg).hexdigest()
# 항상 해시 알고리즘을 명시
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
제공업체가 Base64를 기대하는데 .hexdigest() 사용

문제: 많은 웹훅 제공업체(Stripe, Shopify)는 16진수가 아닌 Base64로 인코딩된 서명을 전송합니다. 16진수 문자열을 Base64 값과 비교하면 항상 실패하여 모든 웹훅이 거부됩니다.

해결책: 서명 형식을 제공업체 문서에서 확인하세요. Base64를 사용한다면 .hexdigest() 문자열이 아닌 원시 .digest() bytes를 인코딩하세요.

Before · Python
After · Python
# 제공업체는 Base64를 전송하지만 16진수를 계산 — 절대 일치하지 않음
expected = hmac.new(key, body, hashlib.sha256).hexdigest()
# "a3f1b9c0..."  vs  "o/G5wE59..."  — 항상 불일치
import base64
# 제공업체 형식에 맞추기: 원시 bytes → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — 헤더와 일치

stdlib hmac 대 cryptography — 빠른 비교

방법
알고리즘
출력
스트리밍
커스텀 타입
설치 필요
hmac.new() + hexdigest()
hashlib 모두
16진수 문자열
✓ .update() 사용
해당 없음
없음 (표준 라이브러리)
hmac.new() + digest()
hashlib 모두
원시 bytes
✓ .update() 사용
해당 없음
없음 (표준 라이브러리)
hmac.digest()
hashlib 모두
원시 bytes
✗ (단일 호출)
해당 없음
없음 (표준 라이브러리, 3.7+)
hashlib.sha256 (일반 해시)
SHA-256 전용
16진수 또는 bytes
✓ .update() 사용
해당 없음
없음 (표준 라이브러리)
cryptography (HMAC)
모두
원시 bytes
✓ .update() 사용
해당 없음
pip install 필요
pyca/cryptography + CMAC
AES-CMAC
원시 bytes
✓ .update() 사용
해당 없음
pip install 필요

웹훅 검증, API 서명, 일반 HMAC 작업에는 표준 라이브러리 hmac 모듈을 사용하세요 — 의존성이 없으며 모든 표준 알고리즘을 지원합니다. 단일 호출 속도가 중요한 일괄 처리에는 hmac.digest() 를 사용하세요. 다른 작업(TLS, X.509, 대칭 암호화)을 위해 이미 cryptography 에 의존하고 있고 예외 기반 .verify() API를 원할 때만 이 라이브러리를 선택하세요. Python 코드 없이 빠르게 서명을 확인하려면 HMAC 생성기 도구 에 키와 메시지를 붙여넣어 즉시 결과를 얻으세요.

자주 묻는 질문

Python에서 hmac.new()와 hmac.digest()의 차이점은 무엇인가요?

hmac.new()는 점진적 .update() 호출을 지원하고 .digest()(원시 bytes)와 .hexdigest()(16진수 문자열)를 모두 제공하는 HMAC 객체를 반환합니다. hmac.digest()는 Python 3.7에 추가된 단일 호출 함수로, 중간 객체를 생성하지 않고 직접 원시 bytes를 반환합니다. 완전한 메시지가 있고 결과만 필요할 때는 hmac.digest()를 사용하세요. 데이터를 청크 단위로 공급하거나 16진수 출력이 필요할 때는 hmac.new()를 사용하세요.

Python
import hmac, hashlib

key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'

# 단일 호출 — 원시 bytes 반환
raw = hmac.digest(key, body, hashlib.sha256)

# 객체 방식 — 점진적 업데이트와 16진수 출력 지원
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()

Python에서 HMAC 서명을 검증하는 방법은?

공유 비밀 키로 원본 메시지에 대한 HMAC을 재계산한 후 hmac.compare_digest()로 비교합니다. 서명 비교에 ==를 절대 사용하지 마세요. == 연산자는 첫 번째 불일치 바이트에서 단락 평가를 하므로 예상 서명의 길이와 내용에 대한 정보를 유출하는 타이밍 공격에 취약합니다.

Python
import hmac, hashlib

def verify_signature(secret: bytes, message: bytes, received_sig: str) -> bool:
    expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

Python hmac SHA-256이 hashlib.sha256으로 해시하는 것과 같은가요?

아닙니다. hashlib.sha256은 누구나 재현할 수 있는 일반 SHA-256 해시를 계산합니다. HMAC-SHA256은 RFC 2104에 따라 비밀 키를 해시 계산에 혼합하므로, 올바른 출력을 생성하거나 검증하려면 키를 알아야 합니다. 일반 해시는 데이터 무결성을 증명합니다. HMAC은 무결성과 진위성을 모두 증명합니다.

Python
import hmac, hashlib

msg = b"transfer:9950:USD"
key = b"api_secret_k8x2"

plain_hash = hashlib.sha256(msg).hexdigest()  # 누구나 계산 가능
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()  # 키가 있어야 계산 가능

Python 3에서 HMAC-SHA1을 사용할 수 있나요?

digestmod 인자로 hashlib.sha1을 전달하면 됩니다. HMAC-SHA1은 Python 3에서 여전히 잘 동작하며 hmac 모듈에 이에 대한 지원 중단 경고가 없습니다. 다만 SHA-1은 새로운 설계에서 취약한 것으로 간주됩니다 — 충돌 저항성이 80비트 미만이며 NIST는 2015년에 대부분의 디지털 서명 용도에서 이를 폐지했습니다. 오늘날 HMAC-SHA1을 사용하는 주된 이유는 OAuth 1.0a나 일부 레거시 웹훅 시스템과 같이 이를 요구하는 기존 프로토콜과의 하위 호환성 때문입니다. 양쪽 통합을 직접 제어하는 경우 새로운 모든 작업에는 HMAC-SHA256 또는 HMAC-SHA512를 사용하세요.

Python
import hmac, hashlib

key = b"oauth_consumer_secret"
base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123"
sig = hmac.new(key, base_string, hashlib.sha1).digest()

Python에서 보안 HMAC 키를 생성하는 방법은?

표준 라이브러리의 secrets.token_bytes()를 사용하세요. HMAC-SHA256의 경우 해시 블록 크기와 일치하는 32바이트 키가 표준 권장 사항입니다. HMAC-SHA512의 경우 64바이트를 사용하세요. 키 생성에 random.random()이나 os.urandom()을 사용하지 마세요 — secrets가 Python 3.6부터 보안에 민감한 무작위성을 위한 올바른 모듈입니다.

Python
import secrets

hmac_sha256_key = secrets.token_bytes(32)  # 256비트
hmac_sha512_key = secrets.token_bytes(64)  # 512비트

# 설정 파일용 16진수로 저장
print(hmac_sha256_key.hex())
# 예: "a3f1b9c04e..."

Python 3에서 hmac.new()에 digestmod가 필요한 이유는?

Python 3.4 이전에는 digestmod가 MD5로 기본 설정되어 있었는데, MD5는 암호학적으로 취약합니다 — 알려진 충돌 공격이 존재하므로 새로운 보안 민감 코드에 절대 사용해서는 안 됩니다. Python 유지 관리자들은 개발자들이 자신도 모르게 MD5 기반 MAC을 배포하는 것을 방지하기 위해 기본값을 제거하고 명시적인 알고리즘 선택을 강제했습니다. digestmod 없이 hmac.new(key, msg)를 호출하면 TypeError가 발생합니다. 항상 알고리즘을 명시적으로 전달하세요: hashlib.sha256, hashlib.sha512, 또는 다른 hashlib 생성자. 알고리즘이 확실하지 않다면 hashlib.sha256이 안전한 기본값입니다 — 알려진 취약점이 없으며 실용적인 모든 작업에서 충분히 빠릅니다.

Python
import hmac, hashlib

key = b"secret"
msg = b"data"

# Python 3.4+에서 TypeError 발생
# hmac.new(key, msg)  # TypeError: missing required argument: 'digestmod'

# 항상 알고리즘을 명시
h = hmac.new(key, msg, hashlib.sha256)

Python 스크립트 없이 빠르게 HMAC을 확인하려면 온라인 HMAC 생성기 에 키와 메시지를 붙여넣으세요 — SHA-256, SHA-384, SHA-512를 즉시 지원합니다.

관련 도구

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria Santos기술 검토자

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.