HMAC 생성기

비밀 키와 SHA-256, SHA-384, SHA-512, SHA-1 또는 MD5를 사용해 텍스트, 16진수 바이트, 파일의 HMAC을 계산하고, 웹훅이나 API 서명과 비교해 확인합니다. 어떤 데이터도 브라우저 밖으로 나가지 않습니다.

브라우저에서만 실행되며 어떤 데이터도 업로드되지 않습니다.

사용 방법

  1. ‘알고리즘’을 선택합니다. 기본값인 HMAC-SHA256은 대부분의 웹훅과 API가 사용하는 알고리즘입니다.
  2. ‘비밀 키’를 입력하고 ‘키 형식’을 ‘텍스트’(UTF-8로 읽음), ‘16진수’, ‘Base64’ 중에서 선택합니다. 키가 비밀번호 같은 문자열이 아니라 원시 바이트라면 ‘16진수’나 ‘Base64’를 사용하십시오.
  3. ‘메시지’를 입력하거나 붙여넣습니다. 원시 바이트는 ‘입력 형식’을 ‘16진수’로 바꾸고, 파일은 ‘파일 열기’로 엽니다. HMAC은 입력하는 즉시 갱신됩니다.
  4. ‘출력 형식’에서 ‘16진수(소문자)’, ‘16진수(대문자)’, ‘Base64’ 중 하나를 골라 결과를 복사하거나, 받은 서명을 ‘비교 대상’에 붙여넣어 ‘일치’인지 ‘불일치’인지 확인합니다.

HMAC의 작동 원리

HMAC(hash-based message authentication code, 해시 기반 메시지 인증 코드, RFC 2104)은 암호학적 해시와 비밀 키를 결합합니다. 키를 아는 사람만 메시지에 맞는 올바른 값을 만들 수 있으므로, HMAC이 일치하면 두 가지가 동시에 증명됩니다. 메시지가 변경되지 않았다는 것, 그리고 키를 가진 사람이 보냈다는 것입니다.

계산식은 H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ message))입니다. 키가 해시의 블록 크기(MD5, SHA-1, SHA-256은 64바이트, SHA-384와 SHA-512는 128바이트)보다 길면 먼저 해시하고, 그 크기까지 0으로 채운 다음, ipad(0x36 바이트의 반복)와 XOR해 메시지에 대한 내부 해시를 계산하고, opad(0x5c 바이트의 반복)와 XOR해 내부 결과에 대한 외부 해시를 계산합니다. 단순한 SHA-256(key ‖ message)에는 외부 해시가 없어서, 태그 하나를 본 사람은 누구나 키 없이 메시지에 데이터를 덧붙이고 유효한 새 태그를 계산할 수 있습니다(길이 확장 공격).

출력 길이는 기반 해시와 같습니다. HMAC-SHA256은 32바이트, 즉 16진수 64자입니다. RFC 2104는 최소한 이 길이 이상의 키를 권장하며, 무작위 Base64 생성기로 알맞은 키를 만들 수 있습니다.

HMAC 서명이 쓰이는 곳

  • GitHub 웹훅. X-Hub-Signature-256 헤더 값은 sha256= 뒤에 원시 요청 본문의 HMAC-SHA256을 16진수로 붙인 것이며, 키는 웹훅 비밀 키입니다. 헤더 값 전체를 ‘비교 대상’에 붙여넣어도 됩니다. sha256= 접두사는 무시됩니다.
  • Stripe 웹훅. Stripe-Signature 헤더에는 타임스탬프 t와 서명 v1이 들어 있습니다. 서명은 타임스탬프, 점, 원시 본문을 이어 붙인 값의 HMAC-SHA256을 16진수로 나타낸 것이며, 엔드포인트 비밀 키(whsec_...)를 텍스트 그대로 키로 사용합니다.
  • Shopify 웹훅. X-Shopify-Hmac-Sha256에는 원시 본문의 HMAC-SHA256이 Base64로 들어 있으며, 키는 앱의 클라이언트 비밀 키입니다. 따라서 ‘출력 형식’에서 ‘Base64’를 선택하거나 헤더 값을 ‘비교 대상’에 붙여넣으십시오.
  • AWS Signature Version 4. 서명 키는 HMAC-SHA256을 연쇄적으로 적용해 파생합니다. 먼저 AWS4에 비밀 액세스 키를 이어 붙인 값을 키로 삼아 날짜의 HMAC을 구하고, 이어서 리전, 서비스, aws4_request의 HMAC을 차례로 구하되 매번 직전 결과를 키로 씁니다. 한 단계의 출력을 다음 단계에 넣으려면 ‘키 형식’을 ‘16진수’로 설정하십시오.
  • JSON Web Token. HS256, HS384, HS512는 인코딩된 헤더, 점, 인코딩된 페이로드를 이어 붙인 값에 대한 HMAC-SHA256, -384, -512입니다. 토큰의 세 번째 부분이 이 HMAC을 URL 안전 Base64로 나타낸 것이며, ‘비교 대상’에 그대로 넣을 수 있습니다. 토큰 전체를 검증하려면 JWT 디코더를 사용하십시오.

HMAC은 항상 받은 그대로의 원시 본문으로 계산하십시오. JSON을 파싱한 뒤 다시 직렬화하면 공백이나 키 순서가 바뀌며, 줄 바꿈 문자 하나만 더해져도 HMAC은 완전히 달라집니다.

코드로 HMAC 계산하기

# Python
import hmac, hashlib
sig = hmac.new(b'key', b'message', hashlib.sha256).hexdigest()
hmac.compare_digest(sig, received)        # 상수 시간 비교

// Node.js
const crypto = require('crypto');
const sig = crypto.createHmac('sha256', 'key').update('message').digest('hex');
// timingSafeEqual은 길이가 다르면 예외를 던지므로 먼저 길이를 확인합니다
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(received));

// PHP: 메시지가 키보다 먼저 옵니다
$sig = hash_hmac('sha256', 'message', 'key');
hash_equals($sig, $received);

# OpenSSL: 텍스트 키, 그리고 같은 키를 16진수로 지정한 경우
printf '%s' 'message' | openssl dgst -sha256 -hmac 'key'
printf '%s' 'message' | openssl dgst -sha256 -mac HMAC -macopt hexkey:6b6579

# 테스트 벡터: 키 "key", 메시지 "The quick brown fox jumps over the lazy dog"
# HMAC-SHA256 = f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8

직접 작성한 코드에서 서명을 검증할 때는 위와 같은 상수 시간 비교를 사용하십시오. 단순한 == 비교는 처음으로 다른 문자가 나오는 곳에서 멈추며, 이 시간 차이를 이용하면 공격자가 유효한 서명을 한 바이트씩 알아낼 수 있습니다. Stripe처럼 타임스탬프를 쓰는 방식에서는 타임스탬프가 몇 분 이상 지난 요청도 거부하십시오. 그러면 재전송 공격을 막을 수 있습니다.

자주 묻는 질문

HMAC-MD5나 HMAC-SHA1은 아직 안전합니까?

충돌 공격은 단순 해시로서의 MD5와 SHA-1을 무너뜨리지만, HMAC을 위조하는 실용적인 방법은 되지 못합니다. HMAC의 안전성은 해시의 다른 성질과 비밀 키에 달려 있기 때문입니다(HMAC-MD5에 관해서는 RFC 6151 참고). HMAC-SHA1 기반 TOTP 코드처럼 이를 요구하는 연동은 그대로 유지해도 됩니다. 새로 설계할 때는 HMAC-SHA256을 사용하십시오.

계산한 HMAC이 웹훅 서명과 일치하지 않는 이유는 무엇입니까?

흔한 원인은 원시 본문 대신 파싱 후 다시 직렬화한 JSON을 해시한 경우, 메시지 끝에 줄 바꿈 문자가 붙은 경우, 비밀 키나 키 형식이 잘못된 경우(Base64나 16진수 비밀 키를 ‘텍스트’로 입력했거나 그 반대), 알고리즘이 잘못된 경우입니다. 16진수와 Base64의 차이는 여기서 문제가 되지 않습니다. ‘비교 대상’은 대문자·소문자 16진수, 표준 Base64, URL 안전 Base64를 모두 받아들입니다.

HMAC과 일반 해시는 어떻게 다릅니까?
SHA-256 같은 일반 해시는 누구나 계산할 수 있으므로 데이터가 우연히 바뀌지 않았다는 것만 보여 줍니다. HMAC은 비밀 키가 있어야만 계산할 수 있으므로, 유효한 HMAC은 그 키를 가진 사람이 만들었다는 것까지 보여 줍니다.
HMAC은 디지털 서명과 같은 것입니까?

아니요. HMAC은 양쪽이 하나의 비밀 키를 공유하므로, 태그를 검증할 수 있는 사람은 누구나 태그를 만들 수도 있습니다. RSA나 ECDSA 같은 서명(JWT의 RS256, ES256)은 개인 키로 만들고 공개 키로 확인하므로, 서명을 검증하는 쪽에서 위조할 수 없습니다.

비밀 키가 서버로 전송됩니까?

아니요. HMAC-SHA1과 SHA-2 계열은 브라우저의 Web Crypto API로, HMAC-MD5는 페이지의 JavaScript로 계산합니다. 키와 메시지는 사용자의 기기를 벗어나지 않습니다. 그래도 운영 환경의 비밀 키는 신중하게 다루고, 노출되었을 가능성이 있다면 키를 교체하십시오.