HMAC으로 메시지 무결성과 인증을 검증하는 방법

HMAC의 중첩 해시 구조와 동작 원리, 일반 해시와의 차이, API·토큰 인증 환경에서의 구현 고려사항을 정리합니다.

2026-08-14 · 최초 발행 2025-06-15

메시지에 비밀 키를 결합하는 인증 코드

HMAC(Hash-based Message Authentication Code)은 메시지의 무결성과 신원 인증을 함께 확인하기 위한 암호화 기법이다. 단방향 해시 함수에 비밀 키를 결합해, 메시지가 전송 도중 바뀌지 않았는지와 올바른 키를 가진 주체가 값을 생성했는지를 검증한다.

해시 함수만으로는 일반 해시값을 누구나 계산할 수 있다. HMAC은 이 지점에 비밀 키를 더해 이런 공격 가능성을 줄인다. RFC 2104로 표준화되어 있으며, 현재 여러 보안 프로토콜에서 사용된다.

안쪽과 바깥쪽을 나누는 해시 구조

HMAC은 패딩된 키를 두 방식으로 사용하는 중첩 해시 구조다. 기본 수식은 다음과 같다.

HMAC(K,m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))

  • K: 비밀 키
  • m: 메시지
  • H: 해시 함수(SHA-1, SHA-256, MD5 등)
  • : XOR 연산
  • ||: 연결 연산
  • opad: 외부 패딩 (0x5c 반복)
  • ipad: 내부 패딩 (0x36 반복)
입력 메시지K ipad연결내부 해시 H1K opad연결외부 해시 H2HMAC 결과

키가 해시 함수의 블록 크기보다 길면 먼저 해시해 줄이고, 짧으면 0으로 패딩한다. 그 뒤 패딩된 키와 ipad를 XOR한 값에 메시지를 연결해 내부 해시를 만든다. 이어서 같은 키에 opad를 XOR하고 내부 해시 결과를 연결해 외부 해시를 수행하면 최종 HMAC 값이 나온다.

보안 강도를 좌우하는 조건

HMAC의 강도는 해시 함수의 충돌 저항성과 비밀 키의 엔트로피에 의존한다. 키 길이가 해시 함수 출력 길이보다 길다고 해서 추가 보안 이점이 생기지는 않는다.

SHA-256 기반 HMAC은 브루트 포스 공격에 대해 128비트 보안 강도를 제공한다. 비밀 키가 없으면 유효한 HMAC을 만들 수 없고, 메시지가 달라지면 HMAC 값도 달라진다. 이 두 성질이 각각 인증과 무결성 검증의 기반이 된다.

해시값만 비교할 때와의 차이

HMAC메시지 기반 해시 처리HMAC값비밀일반 해시메시지해시 함수해시값

일반 해시는 메시지 무결성만 검증하는 반면, HMAC은 무결성과 인증을 동시에 제공한다. 일반 해시는 중간자 공격에 취약할 수 있지만 HMAC은 비밀 키를 요구한다.

또한 일반 해시는 길이 확장 공격(Length Extension Attack)에 취약할 수 있으나, HMAC은 이중 해싱으로 이를 방어한다. 해시 충돌 공격이 발생했을 때도 일반 해시는 심각한 위험이 될 수 있는 반면 HMAC은 비밀 키 때문에 상대적으로 안전하다.

프로토콜과 서비스에서 쓰이는 지점

TLS/SSL은 핸드셰이크 과정의 메시지 인증에 HMAC을 활용하며, IPsec은 패킷 인증을 위해 HMAC을 구현한다. SSH에서도 메시지 무결성 검증에 사용된다.

RESTful API에서는 요청 인증용 서명으로 HMAC을 적용할 수 있다. AWS, Azure 등의 클라우드 서비스 API 요청 검증도 이 활용 범위에 속한다. 요청 매개변수, 타임스탬프, HTTP 메서드를 결합해 HMAC을 생성하는 방식이 예시다.

Authorization: HMAC-SHA256 Credential=access-key/20230522,Signature=5d672d79c15b13162d9279b0855cfaf6

HMAC은 패스워드 기반 키 유도 함수(PBKDF2)의 핵심 구성 요소이기도 하다. 솔트와 함께 사용하면 레인보우 테이블 공격을 막는 데 활용할 수 있다.

파일 다운로드의 체크섬, 데이터베이스 레코드의 무결성 확인, 디지털 포렌식 증거의 무결성 보장에도 적용된다. JWT(JSON Web Token) 서명과 세션리스(Sessionless) 인증 시스템 역시 HMAC을 사용할 수 있는 대표적인 영역이다.

Python으로 생성하고 비교하기

다음 구현은 키와 메시지로 HMAC-SHA256 값을 만들고, 수신한 값과 상수 시간 비교를 수행한다.

import hmac
import hashlib

def create_hmac(key, message):
    """
    키와 메시지로 HMAC을 생성하는 함수
    """
    # key와 message를 바이트 형태로 변환
    if isinstance(key, str):
        key = key.encode('utf-8')
    if isinstance(message, str):
        message = message.encode('utf-8')

    # HMAC-SHA256 생성
    h = hmac.new(key, message, hashlib.sha256)

    # 16진수 문자열로 반환
    return h.hexdigest()

def verify_hmac(key, message, received_hmac):
    """
    수신된 HMAC 값이 올바른지 검증하는 함수
    """
    calculated_hmac = create_hmac(key, message)

    # 상수 시간 비교로 타이밍 공격 방지
    return hmac.compare_digest(calculated_hmac, received_hmac)

# 사용 예시
secret_key = "my_secret_key"
message = "Hello, HMAC!"

# HMAC 생성
signature = create_hmac(secret_key, message)
print(f"생성된 HMAC: {signature}")

# HMAC 검증
is_valid = verify_hmac(secret_key, message, signature)
print(f"검증 결과: {is_valid}")

# 메시지 변조 시뮬레이션
is_valid = verify_hmac(secret_key, message + " 변조됨", signature)
print(f"변조된 메시지 검증 결과: {is_valid}")

키와 구현 세부 사항에서 생기는 차이

키는 암호학적으로 안전한 난수 생성기(CSPRNG)로 생성하고, 길이는 최소 해시 함수 출력 길이와 같게 설정한다. HSM(Hardware Security Module) 또는 안전한 키 저장소에 보관하며, 정기적인 키 순환 정책도 필요하다.

해시 함수는 SHA-256 이상을 선택하고 MD5와 SHA-1은 피하는 것이 권장된다. 보안 요구사항에 맞는 출력 길이를 가진 해시 함수를 골라야 한다.

검증 단계에서는 상수 시간 비교 함수를 사용해 타이밍 공격을 방지한다. 민감한 연산은 사이드 채널 공격으로부터 보호하고, 오류 메시지가 과도한 정보를 노출하지 않도록 구성한다. 반복 계산이 많은 환경에서는 캐싱을 검토할 수 있으며, 가능한 경우 하드웨어 가속을 활용한다.

공유 키 모델이 맞지 않는 경우

HMAC은 대칭 키 기반이므로 송신자와 수신자가 같은 비밀 키를 공유해야 한다. 안전한 키 교환 메커니즘이 필요하며, 다수 당사자 간 인증에서는 N개 노드에 N(N-1)/2 키가 필요해 비효율적일 수 있다.

이 제약이 문제라면 디지털 서명을 고려할 수 있다. RSA, ECDSA, EdDSA 등의 비대칭 암호화 기반 방식은 공개 키 인프라(PKI)를 통해 확장성을 제공하고 부인 방지(non-repudiation) 기능도 제공하지만, HMAC보다 계산 비용이 높다.

GCM, ChaCha20-Poly1305 같은 인증된 암호화(AEAD)는 기밀성과 무결성을 함께 제공하며, 단일 패스로 암호화와 인증을 수행해 성능을 높인다. Poly1305-AES는 단일 사용 인증자(one-time authenticator)로 높은 성능과 보안성을 제공하지만, 매 메시지마다 새로운 논스(nonce)가 필요하다.

HMAC메시지 인증암호화API 보안무결성