HMAC 생성·서명 검증
메시지와 키에서 HMAC-SHA-1, SHA-256, SHA-384, SHA-512를 계산하고, 주어진 서명이 어느 것으로 만들어졌는지 판별합니다.
서명을 계산하려면 키를 입력하세요.
이 도구가 하는 일
HMAC은 메시지와 공유 시크릿을 짧은 서명으로 바꿉니다. 같은 시크릿을 가진 사람은 누구나 그것을 다시 계산해 메시지가 바뀌지 않고 도착했는지, 시크릿을 아는 누군가에게서 왔는지 볼 수 있습니다. 이 도구는 흔한 네 변형을 한꺼번에 계산하고 — 받은 서명을 주면 — 어느 것이 그것을 냈는지 알려 줍니다.
그 두 번째 방향이 대개 사람이 찾아오는 이유입니다. Webhook이 검증에 실패하는데, 물음은 사실 "이 서명이 유효한가"가 아니라 "그럴듯한 여러 가지 중 내가 보낸 이와 다르게 하는 것이 무엇인가"입니다.
어긋나는 곳은 키
HMAC은 바이트를 바이트로 서명합니다. 메시지는 대개 뻔합니다 — 맨 요청 본문 — 하지만 키는 거의 결코 그렇지 않습니다. 시크릿이 문자열로 도착하고, 문자열은 어떻게 읽을지 정하기 전에는 바이트가 아니기 때문입니다:
a3f2 텍스트로 4바이트 61 33 66 32 a3f2 16진수로 2바이트 a3 f2
두 읽기 모두 정당하고, 둘 다 흠 없이 잘 된 서명을 내며, 두 서명에는 공통점이 없습니다. 아무것도 경고하지 않습니다: 오류도, 길이 불평도 없고, 그저 보낸 이가 계산한 것과 맞지 않는 값이 있을 뿐입니다. 그래서 여기서 키 인코딩은 추측이 아니라 보이는 선택입니다 — 서명이 맞지 않을 때 먼저 바꿔 볼 것이 이것입니다.
대략의 기준: whsec_ 같은 접두사나 대소가 섞인 글자와 숫자의 연속을 지닌 시크릿은 보통 텍스트로 의도된 것입니다. 정확히 32자나 64자로 0-9와 a-f만 쓰는 문자열은 대개 16진수입니다. =로 끝나는 것은 거의 확실히 Base64입니다. 다만 모양은 증명이 아니므로, 모양이 아니라 보낸 이의 문서를 확인하세요.
메시지는 정확한 바이트여야 한다
어긋남의 나머지 절반은 메시지입니다. HMAC은 바이트 위에 정의되므로, 바이트를 바꾸는 것은 무엇이든 서명을 완전히 바꿉니다 — 부분 점수도, 아까운 차이도 없습니다.
- JSON 재직렬화. 본문을 파싱해 다시 문자열화하면 키가 재배열되거나, 간격이 바뀌거나, 숫자가 정규화될 수 있습니다. 받은 맨 본문에 서명하고 검증하세요, 결코 그것의 왕복한 복사본이 아니라.
- 끝의 줄바꿈. 본문을 파일에 저장할 때 더하는 도구가 있습니다. 그것은 바이트이고, 모든 것을 바꿉니다.
- 문자 인코딩. 비ASCII 텍스트를 담은 본문은 양쪽이 쓰는 같은 인코딩 — 실제로는 UTF-8 — 으로 읽혀야 합니다.
- 압축이나 미들웨어. 핸들러 앞의 무언가가 본문을 풀거나 다시 쓴다면, 그 후가 아니라 그 전에 검증하세요.
많은 제공자는 본문만 서명하지도 않습니다. Stripe는 타임스탬프와 본문을 점으로 이어 서명하고, AWS는 메서드, 경로, 헤더, 페이로드의 해시로 만든 정규 요청에 서명합니다. 맨 본문에 대해 검증이 실패하면 서명된 문자열은 아마 맨 본문이 아닙니다 — 그것은 보낸 이 쪽에 문서화되어 있고, 더 디버깅하기 전에 읽을 만합니다.
SHA-1이 해시 도구가 아니라 여기서 제공되는 이유
해시 도구는 SHA-1을 깨진 것으로 표시합니다. 이것은 HMAC-SHA-1을 경고 없이 싣고, 그것은 실수가 아니라 의도입니다.
SHA-1은 서명과 인증서에 쓸 수 없습니다. 충돌을 구성할 수 있기 때문입니다: 서로 다른 두 문서가 다이제스트를 공유하도록 만들 수 있습니다. HMAC은 그 성질에 기대지 않습니다. 그 안전성은 시크릿 키에 있고, 구성 — 메시지를 두 번, 두 번 다 키를 섞어 해시하는 것 — 은 바탕 해시가 충돌에 약해도 버팁니다. HMAC-SHA-1은 지금도 건전하고 OAuth 1.0a와 오래된 AWS 서명이 아직 쓰는 것이라, 그것을 계산하기를 거부하는 도구는 더 안전해지지 않으면서 그저 덜 쓸모 있어질 뿐입니다.
새로운 것에는 무엇이든 SHA-256이 무난한 기본값입니다. 긴 다이제스트가 여기서 유의미하게 더 강하지는 않으므로 — 안전성의 천장은 다이제스트 길이가 아니라 키입니다 — SHA-384와 SHA-512는 상호 운용해야 할 무언가가 그것을 요구할 때만 고를 만합니다.
서명을 안전하게 비교하기
이 페이지는 보통의 문자열 비교로 비교하고, 여기서는 그것으로 괜찮습니다: 당신이 직접 키를 가지고 있어 새어 나갈 것이 없습니다. 들어오는 요청을 검증하는 서버에서는 괜찮지 않습니다. 보통의 비교는 첫 다른 바이트에서 멈추므로, 걸리는 시간이 추측의 얼마가 맞았는지 드러내고, 많은 요청을 보낼 수 있는 공격자가 유효한 서명을 한 바이트씩 복원할 수 있습니다.
플랫폼이 제공하는 상수 시간 비교 — Node의 crypto.timingSafeEqual, Python의 hmac.compare_digest, PHP의 hash_equals — 를 16진수 문자열이 아니라 맨 바이트에 대해 쓰세요. 그와 함께, 요청에 이름 붙은 알고리즘을 믿는 대신 직접 계산한 서명에 대해 비교하고, 오래된 유효한 서명이 재생되지 않도록 타임스탬프가 지금에서 먼 메시지를 거부하세요.
HMAC은 해시도 서명도 아니다
여기서 세 가지가 혼동되고, 그 차이는 나중에 무엇을 주장할 수 있는지에 중요합니다:
- 해시는 메시지를 받아 다이제스트를 냅니다. 누구나 계산할 수 있으므로 메시지가 우연히 바뀌지 않았음을 증명합니다 — 특정한 누군가에게서 왔음이 아니라.
- HMAC은 메시지와 공유 시크릿을 받습니다. 양쪽 당사자가 같은 키를 가지므로 보낸 이가 시크릿을 알았음을 증명합니다. 어느 쪽이 보냈는지는 증명할 수 없습니다. 둘 다 낼 수 있었기 때문입니다.
- 서명은 보낸 이만 가진 개인 키와 누구나 검증할 수 있는 공개 키를 씁니다. 그것이 부인 방지를 줍니다: 보낸 이가 나중에 그것을 부정할 수 없습니다.
그래서 HMAC은 이미 시크릿을 공유하는 두 시스템 사이 — Webhook, 내부 API, 세션 토큰 — 에서 옳은 도구이고, 제삼자에게 누가 무엇을 보냈는지 증명해야 할 때 그른 것입니다. 또 인증은 하지만 감추지는 않음에 유의하세요: 메시지는 평문으로 오가고, HMAC은 기밀성에 대해 아무것도 말하지 않습니다.
자주 묻는 질문
- 서명이 맞지 않습니다. 먼저 무엇을 확인하나요?
- 키 인코딩, 다음으로 메시지 바이트입니다. 16진수처럼 보이는 시크릿은 흔히 텍스트가 아니라 16진수로 의도되고, 둘은 어느 쪽도 오류 없이 전혀 다른 서명을 냅니다. 그다음, 재직렬화한 복사본이 아니라 받은 그대로의 맨 본문에 서명하는지 확인하세요.
- 해시 도구가 깨졌다고 하는 SHA-1이 여기서 제공되는 이유는 무엇인가요?
- HMAC이 SHA-1이 잃은 성질인 충돌 저항에 기대지 않기 때문입니다. 그 안전성은 키에서 옵니다. HMAC-SHA-1은 지금도 건전하고 OAuth 1.0a와 오래된 AWS 서명에서 아직 쓰이지만, 새로운 것에는 SHA-256이 옳은 기본값입니다.
- 긴 다이제스트가 더 안전한가요?
- 유의미하게는 아닙니다. HMAC의 강도는 다이제스트 길이가 아니라 시크릿으로 한정되므로, SHA-512가 SHA-256보다 네 배 낫지는 않습니다. 상대가 기대하는 것을 고르세요.
- HMAC과 디지털 서명의 차이는 무엇인가요?
- HMAC은 양쪽이 아는 하나의 시크릿을 쓰므로 보낸 이가 시크릿을 알았음은 증명하지만 어느 쪽이 보냈는지는 아닙니다. 디지털 서명은 보낸 이만 가진 개인 키를 쓰므로 제삼자가 검증할 수 있고 보낸 이가 부정할 수 없습니다. 그 마지막 성질이 필요하면 HMAC은 그른 도구입니다.
- 서명을 ===로 비교해야 하나요?
- 서버에서는 안 됩니다. 보통의 비교는 바이트가 다르면 곧 돌아오고, 그 타이밍이 추측한 서명의 얼마가 맞았는지 새게 합니다. 플랫폼의 상수 시간 비교를 맨 바이트에 쓰세요. 이 페이지에서는 이미 키를 가지고 있어 문제되지 않습니다.
- 제 키가 어딘가로 전송되나요?
- 아니요. 모든 것이 Web Crypto API로 브라우저 안에서 계산됩니다. 메시지도 키도 기기 밖으로 나가지 않습니다.