HMAC 생성기
1. API에 보안을 더하다.
쇼핑몰의 결제 및 상품 처리 로직을 담당하게 된 주니어 개발자라고 가정해 보겠습니다.
현재 시스템은 고객이 결제를 완료하면 외부 결제 대행사(PG)가 우리 서버로 웹훅(Webhook)을 날려 정상 결제 완료를 알리고, 우리 서버는 이를 받아 주문을 최종 처리하는 아키텍처로 구성되어 있습니다.
보안 위협과 해결책
이 구조에서 치명적인 보안 위협이 발생할 수 있습니다. 만약 해커가 우리 서버의 웹훅 수신 주소를 알아낸 뒤, 가짜 결제 완료 데이터를 진짜인 것처럼 꾸며서 서버로 보내면 어떻게 될까요? 서버가 이를 그대로 믿고 처리한다면, 쇼핑몰은 결제 대금도 받지 못한 상태로 상품 배송 처리를 하게됩니다.
이를 방지하기 위해서는 요청 데이터에 대한 '서명(Signature)' 이 필요합니다. 외부 결제 대행사와 우리 서버만이 사전에 공유한 비밀키를 이용해 요청마다 서명을 검증하여 제대로된 요청인지 확인할 수 있습니다.
만약 해커가 웹훅 주소를 알아내어 가짜 요청을 보내더라도, 공유된 비밀키를 모르기 때문에 올바른 서명을 만들어낼 수 없습니다. 따라서 우리 서버는 서명이 불일치하는 것을 확인하고 "이 요청은 결제 대행사가 보낸 것이 아니다"라고 판단하여 즉시 반려할 수 있습니다. 결과적으로 악의적인 공격으로부터 상품을 안전하게 지켜낼 수 있으며, 이 때 사용하는 핵심 기술이 바로 HMAC입니다.
2. HMAC은 어떻게 사용하는가?
말로만 서명을 생성하고 검증한다고 하면 직관적으로 와닿지 않을 수 있습니다. 실질적으로 HMAC을 사용하여 API 요청을 처리하는 과정을 파이썬(Python) 예제 코드와 함께 살펴보겠습니다.
예시 코드
클라이언트는 아래와 같이 signature 서명을 HTTP 헤더에 보내야합니다.
...
# 전송할 데이터
payload = { ... my data ... }
# HMAC-SHA256 서명
signature = "my_signature"
# HTTP 요청 전송 (서명을 헤더에 포함)
headers = {
"Content-Type": "application/json",
"X-Signature": signature #HMAC 서명을 헤더에 담아 전송
}
url = "https://our-shop.com/api/webhook/payment"
서버측에서는 클라이언트에게 받은 헤더를 검증하고, 서명이 유효성에 따라 요청을 처리합니다.
def resolve_hmac():
# 헤더에서 클라이언트가 보낸 서명(Signature) 추출
received_signature = request.headers.get("X-Signature")
# 서명이 아예 존재하지 않는 경우 (비정상 요청)
if not received_signature:
# 적절하게 처리
# HTTP Payload 추출
# 주의: 서명 검증을 위해서는 파싱된 JSON 객체가 아닌 전송된 그대로의 원본 바이트 데이터가 필요합니다.
raw_payload = request.get_data()
# 3. 서버 측에서 동일한 페이로드와 비밀키로 HMAC-SHA256 서명 계산
expected_signature =hmac.new(
key=SECRET_KEY,
msg=raw_payload,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_signature, received_signature):
# 적절하게 처리
서버 측에서는 이 요청을 받으면, 동일한 SECRET_KEY를 사용해 수신한 message의 HMAC 값을 직접 계산합니다. 그리고 클라이언트가 헤더(X-Signature)로 보낸 값과 자신이 계산한 값이 일치하는지 비교하여 데이터의 위조 여부를 판별합니다.
3. HMAC의 구성요소
HMAC을 좀 더 명확하게 사용하기 위해서는 다음 요소들을 알아야합니다.
- 해시 알고리즘
- 사전 공유 키
- 인코딩 포맷
각 요소들이 무엇이며 해당 요소들이 가지고 있는 구성요소들의 특징들을 공부하면, 자신의 상황에 적절히 사용하실 수 있습니다.
a. 해시 알고리즘
해시 알고리즘은 임의의 길이의 데이터를 입력받아 고정된 길이의 고유한 결과값(해시값 또는 다이제스트)을 반환하는 단방향 암호화 함수입니다. 데이터가 1바이트라도 변경되면 출력값이 완전히 달라져 출력값만으로는 원본 데이터를 절대 유추할 수 없다는 특징이 있습니다.
API 보안에 HMAC을 적용할 때는 뼈대가 되는 해시 알고리즘을 먼저 결정해야 합니다. 알고리즘마다 출력 길이와 연산 속도, 보안 강도에 차이가 있으므로 아래의 장단점 비교를 통해 현재 시스템에 가장 적합한 알고리즘을 선택하세요. 해당 생성기에서 지원하는 해시 알고리즘을 다음 표에 정리해 두었습니다.
| 알고리즘 | 장점 | 단점 | 권장되는 상황 |
|---|---|---|---|
| SHA1 | 폭넓은 시스템에서 지원되며 연산 속도가 빠름 | 해시 충돌(Collision) 취약점이 발견되어 현대 기준 보안 미달 | 구형 레거시 시스템과의 하위 호환성 유지가 필수적인 경우 |
| SHA224 | SHA256과 동일한 구조로 높은 보안성 제공 | SHA256과 연산량은 같은데 출력만 짧아 범용성이 낮음 | 224비트 출력이 엄격하게 요구되는 특정 프로토콜 연동 |
| SHA256 | 속도와 보안의 밸런스가 뛰어나며 업계 표준(De facto standard) | SHA512 대비 64비트 시스템에서의 연산 효율이 약간 낮음 | (기본값 강력 권장) 일반적인 API 인증 및 웹훅 서명 |
| SHA384 | SHA512 기반으로 강력한 보안 제공 | SHA256보다 리소스 소모가 크며, 범용성이 상대적으로 낮음 | 정부 규제 등 384비트 이상의 보안이 요구되나 대역폭 제한이 있는 경우 |
| SHA512 | 현존 최고 수준의 보안 강도, 64비트 아키텍처에서 고속 연산 가능 | 32비트 환경에서는 속도가 느리며, 다이제스트 길이가 길어 대역폭 차지 | (고보안 권장) 극도의 데이터 무결성이 필요한 엔터프라이즈 및 금융 시스템 |
| MD5 | 처리 속도가 매우 빠르고 구현이 간단함 | 보안 결함이 심각하여 위조(Spoofing) 공격에 매우 취약함 | 보안과 무관한 단순 파일 체크섬(무결성 확인) (API 서명용 절대 비권장) |
| RIPEMD160 | 유럽 표준으로 개발되어 SHA 계열의 훌륭한 대안으로 작용 | SHA-2 계열에 비해 범용 인지도가 낮음 | 비트코인 등 RIPEMD160을 표준으로 사용하는 블록체인/특수 프로토콜 |
| MD2 | (초창기 알고리즘) 메모리 사용량이 극히 적게 설계됨 | 연산 속도가 매우 느리고, 보안이 완전히 파훼됨 | 오래된 특정 X.509 인증서 검증 등 극단적인 레거시 환경 |
| DES (CBC-MAC) | 과거 하드웨어 가속기 및 구형 단말기에서 널리 지원됨 | 56비트의 짧은 키 길이로 인해 브루트포스 공격에 취약함 | 구형 스마트카드, 마그네틱 결제 단말기 등 레거시 금융망 연동 |
| DES/CFB8 | DES 알고리즘을 스트림 암호처럼 사용하여 실시간 처리 가능 | DES와 동일하게 심각한 보안 취약점 존재 | 스트림 방식의 구형 실시간 통신 프로토콜 유지보수 |
| DESEDE (Triple-DES) | DES를 3번 반복 적용하여 기존 DES의 보안 취약점을 완화함 | 연산 속도가 매우 느리며, 최신 AES-MAC 방식보다 효율이 크게 떨어짐 | 과거 3DES를 표준으로 채택했던 구형 금융 시스템 및 PG사 연동 |
| DESEDE/CFB8 | 3DES의 보안성과 CFB 모드의 실시간 처리 특성을 결합 | 성능 저하가 크고 최신 환경에서는 거의 사용되지 않음 | 해당 모드를 강제하는 특정 레거시 시스템과의 통신 |
| DESEDE64 | 64비트 블록 크기에 맞춘 3DES 변형 | 블록 크기 제한으로 인한 충돌(Sweet32 공격) 가능성 | 오래된 특정 64비트 아키텍처 기반의 통신망 유지보수 |
| DESEDE64 WITH... | ISO 7816-4 패딩을 지원하여 스마트카드 통신 규격 충족 | 복잡한 패딩 구조와 구형 암호화의 한계 | 구형 IC 카드 및 금융 IC 단말기(EMV 등) 연동 |
| RC2 | 가변 키 길이를 지원하여 구형 시스템 환경에 맞춰 사용 가능 | 90년대 개발된 규격으로 현대 보안 기준에 미달 | 오래된 이메일(S/MIME) 암호화 등 과거 시스템 복호화 및 검증 |
| RC2/CFB8 | RC2 알고리즘의 8비트 스트림 처리 모드 | 보안성 결여 및 현대 플랫폼에서의 호환성 문제 | RC2 스트림 방식을 사용하는 극히 제한적인 레거시 환경 |
| MD4 | MD5의 전신으로 설계가 단순하고 연산이 극히 빠름 | 충돌 저항성이 완전히 깨져 있어 보안 기능 상실 | 역사적 연구 목적 또는 보안이 전혀 필요 없는 폐쇄망 내 고속 체크 |
| TIGER | 64비트 프로세서에서 극단적으로 빠른 해싱을 위해 설계됨 | 최신 라이브러리(fb-tiger-hash)의 유지보수 미비, SHA-2에 밀려 사장됨 | TIGER 트리 해시를 사용하는 과거 P2P 네트워크(Direct Connect 등) 호환 |
b. 사전 공유키
데이터를 보내는 측(해당 예시에서는: PG사)과 받는 측(쇼핑몰 서버)이 사전에 약속하여 나눈 비밀 문자열입니다. 해커가 통신 구간에서 원본 데이터(Payload)를 탈취하고 어떤 해시 알고리즘을 사용하는지 알아내더라도, 이 '비밀키'를 모르면 결코 유효한 서명을 위조할 수 없습니다. 즉, 해당 요청이 신뢰할 수 있는 발신자로부터 왔음을 증명하는 유일한 수단입니다.
사전 공유 키가 유출되면 HMAC을 통한 방어 체계가 완전히 무너집니다. 따라서 실무 적용 시 아래 보안 수칙을 반드시 준수하세요.
- 소스코드 내 하드코딩(Hardcoding) 엄금: 비밀키를 소스코드 파일(
*.js,*.py,*.java등)에 직접 텍스트로 작성하면, Git 등 버전 관리 시스템에 영구적으로 기록되어 심각한 보안 사고로 이어집니다. - 안전한 키 저장소 활용: 비밀키는 반드시 서버의 환경 변수(Environment Variables,
.env파일)로 분리하거나, AWS Secrets Manager, HashiCorp Vault와 같은 전문적인 보안 키 관리 서비스를 통해 런타임에 주입받아야 합니다. - 충분한 길이와 무작위성 보장: 비밀키가 너무 짧거나 유추하기 쉬운 단어(예:
secret123)로 구성될 경우, 무차별 대입 공격(Brute-force)에 의해 키가 뚫릴 수 있습니다. 암호학적으로 안전한 난수 생성기를 사용하여 길고 복잡한 키를 사용하는 것이 권장됩니다.
연동하려는 외부 서비스의 문서에서 어떤 형식을 요구하는지 먼저 확인한 뒤 맞춰 선택하면 됩니다.
c. 출력 포멧
HMAC 연산의 결과물은 텍스트가 아닌 컴퓨터가 이해하기 쉬운 이진 데이터로 나옵니다. 하지만, 해당 연산 결과물을 HTTP 통신을 위해 해더나, Json에 넣어야합니다. 이때 텍스트 형식으로 넣어야하기 때문에 이진데이터를 그대로 넣으면 데이터가 깨지거나, 통신에러가 발생할 가능성이 높습니다.
이를 위해서 인코딩이 필요한 것입니다. 인코딩은 다음 두가지 형태를 주로 사용하며, 이 역시 상황에 맞게 사용하시는 것이 좋습니다.
주요 포맷 종류
일반적으로 API 서명에는 다음 두 가지 포맷 중 하나가 표준으로 사용됩니다.
- Hex (16진수, Hexadecimal): 데이터를
0~9와a~f로만 표현합니다. 문자열이 길어지는 단점이 있지만, 데이터의 손실이나 변형이 일어날 확률이 극히 낮아 가장 보편적으로 사용됩니다. (예:d2b5...f1a4) - Base64: 데이터를 64개의 출력 가능한 문자(알파벳 대소문자, 숫자,
+,/)로 변환합니다. Hex에 비해 문자열 길이가 짧아 네트워크 대역폭을 절약할 수 있습니다. (예:T6b...x8Q=)
주의 사항
데이터를 보내는 측과 서명을 검증하는 측은 당연히 인코딩 포멧이 같아야합니다. 같은 사전 공유키를 사용하여 통신을 주고 받았으나 제대로 데이터가 보이지 않는다면 인코딩 포멧이 같은지 확인하시는 것이 좋습니다.
4. 온라인 HMAC 생성기의 쓰임
온라인 HMAC 생성기를 사용하여, 디버깅, 테스터에 사용하세요. 온라인 HMAC 생성기는 다음 상황에서 사용할 수 있습니다.
a. 내 코드가 맞게 서명되고 있는지?
API 인증시 서명이 안맞아 401이 뜨는경우, 같은 입력값으로 해당 도구에 돌려서 내 코드 결과와 비교해보기
b. 웹훅 시크릿 키가 맞는지?
수신한 Webhook payload를 붙여넣어서 서명 값을 대조해볼 수 있습니다.
5. HMAC 트러블 슈팅
HMAC을 처음 도입하면, 거의 반드시 한 번은 "분명히 같은 시크릿과 같은 메시지인데 결과가 다르다"는 상황을 마주하게 됩니다. 어떻게 해야 하는지 알아보기 이전에 한발짝 물러서서 원인을 먼저 분석해 봅시다. 원인을 알면 길이 보이기 때문입니다.
a. 인코딩 불일치
사실 HMAC 검증이 실패하는 경우의 99.9%는 메시지 또는 키의 인코딩 차이라고 봐도 무방합니다. HMAC은 문자열이 아니라 바이트 배열을 입력받습니다. 같은 한글 메시지라도 UTF-8과 EUC-KR은 완전히 다른 바이트 시퀀스를 만들어내기 때문에, 결과 다이제스트도 완전히 달라집니다.
"안녕" (UTF-8) → EC 95 88 EB 85 95
"안녕" (EUC-KR) → BE C8 B3 E7
이 도구는 메시지와 키를 모두 UTF-8로 처리합니다. 외부 시스템과 비교할 때는 상대방의 인코딩 명세를 반드시 확인해 주시기 바랍니다.
b. 본문 변형(Whitespace, Newline, BOM)
웹 프레임워크가 친절하게도 요청 본문의 앞뒤 공백을 다듬거나, JSON을 자동 파싱하면서 키 순서를 정렬하는 경우가 있습니다. 다음과 같은 사소한 차이로 HMAC이 어긋납니다.
\r\nvs\n줄바꿈 차이 (Windows ↔ Unix)- 끝에 붙은 줄바꿈 한 글자 유무
- BOM(Byte Order Mark) 포함 여부
- JSON 키 순서 변경 (
{"a":1,"b":2}vs{"b":2,"a":1})
회사 정책에 따라 다르겠지만, 수신 측에서는 본문을 어떤 미들웨어도 거치지 않은 상태로 캡처해 두는 것이 가장 안전합니다. Express라면 bodyParser보다 먼저 raw body를 보존하는 미들웨어를 두는 식입니다.
c. 출력 형식 혼동
송신자는 Hex를 보냈는데 우리는 Base64로 비교하거나, 송신자가 헤더 앞에 sha256= 같은 프리픽스를 붙였는데 우리는 그것을 떼지 않고 비교하는 경우입니다. 이 도구로 양쪽 형식을 모두 출력해 두고 헤더 값과 한 글자 한 글자 비교해 보면, 어디서 불일치가 발생하는지 빠르게 파악할 수 있습니다.
d. 해결책: 흔적을 남기는 것
인코딩 문제든, 본문 변형 문제든, 출력 형식 문제든, HMAC 검증 실패를 다루는 해결책에는 공통점이 있습니다. 수신한 원시 바이트, 사용한 시크릿의 해시(키 자체가 아닌 식별자), 적용한 알고리즘과 출력 형식을 로그에 남겨 두는 것입니다. 키 자체를 로그에 남기면 안 되지만, 어떤 키를 썼는지 식별할 수 있는 메타 정보는 남겨야 한다는 점을 기억해 주시기 바랍니다.
6. 여전히 고려해야할 것들
HMAC은 단순하면서도 강력합니다. 어려운 것은 알고리즘이 아니라, 모든 외부 콜백에 검증 로직을 빠짐없이 적용하는 습관과 키 관리 체계를 망가지지 않게 유지하는 운영 역량입니다. 이 도구를 만든 이유도 결국 그 검증 과정에서 디버깅 마찰을 최소화하기 위해서였습니다.
다만 몇 가지 근본적인 질문은 남습니다.
송신자 시스템이 통째로 침해되면? HMAC은 시크릿을 모르는 외부인의 위조는 막아 주지만, 시크릿을 정상적으로 보유한 송신자가 침해당해 악의적인 페이로드를 정상 서명과 함께 보내는 경우는 막아 줄 수 없습니다. 이런 위협 모델에서는 **추가적인 비즈니스 검증(주문 ID 중복 체크, 이상 거래 탐지)**이 마지막 방어선이 됩니다.
Replay Attack은 HMAC만으로 막을 수 없습니다. 공격자가 정상적인 요청을 그대로 재전송하면 서명이 유효하기 때문에 통과합니다. 이를 막으려면 페이로드에 타임스탬프와 nonce를 포함시키고, 너무 오래된 요청이나 이미 처리한 nonce를 거부하는 추가 로직이 필요합니다. HMAC은 인증의 기초일 뿐, 완전한 솔루션이 아니라는 점을 잊지 말아야 합니다.
키 회전 중 일순간의 401 폭주는 어떻게 막을까요? 키를 교체하는 순간 송신자와 수신자의 시점이 어긋나면, 짧은 시간 동안 모든 Webhook이 검증 실패로 떨어집니다. 운영 시스템에서는 일정 기간 두 키를 모두 유효한 것으로 처리하는 이중 검증 윈도우를 두는 것이 표준적인 해결책입니다.
브라우저 탭 하나로 시크릿과 메시지를 넣고 즉시 HMAC을 확인할 수 있다면, Webhook 디버깅에 들어가는 시간이 절반으로 줄어듭니다. 그것만으로도 이 도구의 역할은 충분하다고 생각합니다.