도입
JWT를 처음 접할 때 HS256은 단순히 “기본 알고리즘” 정도로 보이기 쉽습니다. 하지만 실제로는 서명 방식, 키 배포 방식, 검증 주체의 신뢰 경계, 운영 중 키 관리 방식까지 함께 결정하는 중요한 선택입니다.
특히 HS256은 RSA나 ECDSA처럼 공개키/개인키를 분리하는 구조가 아니라, 같은 비밀키를 알고 있는 주체가 서명도 만들고 검증도 할 수 있다는 점에서 성격이 분명합니다.
그래서 HS256을 이해한다는 것은 단순히 JWT 헤더의 "alg": "HS256" 를 읽는 일이 아니라, JWT의 신뢰 모델을 어떻게 설계할지 이해하는 일에 가깝습니다.
필요성
JWT는 클레임을 전송하는 형식일 뿐이고, 실제 신뢰는 서명 또는 MAC이 제공합니다. HS256은 JWS 계열에서 그 MAC 역할을 하는 가장 대표적인 알고리즘입니다.
장점은 구조가 비교적 단순하다는 점입니다. 공개키 배포 없이도 공유 비밀만 있으면 생성과 검증이 가능합니다. 대신 이 단순함은 곧 키 관리 부담으로 돌아옵니다. 검증할 수 있는 주체는 같은 키로 유효한 MAC도 만들 수 있기 때문입니다.
즉 HS256의 가치는 “쉽다”에만 있지 않고, 신뢰 경계 안에서 빠르고 단순하게 MAC 기반 토큰 검증을 운영할 수 있다는 데 있습니다.
- 발급자와 검증자가 같은 운영 경계 안에서 하나의 공유 비밀을 안전하게 관리할 수 있을 때
- JWT에 암호화가 아니라 무결성과 발급자 검증이 필요한 경우
- 공개키 배포보다 대칭키 운영이 더 단순한 환경일 때
- 서명보다 keyed MAC 기반 검증이 더 잘 맞는 내부 토큰 모델을 쓸 때
정의
JOSE/JWA 기준에서 HS256 은 “HMAC using SHA-256”을 뜻합니다. 즉 비대칭 서명이 아니라 HMAC 계열의 MAC 알고리즘입니다.
JWT가 JWS Compact Serialization으로 표현될 때는 보통 세 부분, 즉 헤더·페이로드·서명으로 구성되며, 헤더에는 사용한 알고리즘을 나타내는 "alg" 값이 들어갑니다.
예를 들어 JWT/JWS 헤더에서 {"typ":"JWT","alg":"HS256"} 는 이 토큰의 서명 또는 MAC이 HS256으로 계산되었음을 뜻합니다.
"HS256의 본질은 JWT를 암호화하는 데 있지 않고
공유 비밀키 기반 MAC으로 토큰의 무결성과 발급자 측 비밀 보유 여부를 검증하는 데 있습니다."
핵심 원리
.)으로 이어 만든 JWS Signing Input에 대해 HMAC-SHA-256을 계산하고, 그 결과를 다시 base64url 인코딩해 서명 부분으로 붙인다JWS Compact Serialization은 크게 세 부분입니다. BASE64URL(UTF8(Protected Header)), BASE64URL(Payload), BASE64URL(Signature) 가 점으로 연결됩니다.
HS256에서 실제로 HMAC을 계산하는 입력은 앞의 두 부분을 점으로 이어 붙인 문자열입니다. 즉 헤더와 페이로드의 인코딩 결과가 그대로 HMAC의 입력이 됩니다.
그리고 계산된 HMAC 값을 base64url 인코딩한 것이 세 번째 부분, 즉 서명 영역이 됩니다. 검증할 때는 같은 입력과 같은 공유 비밀로 다시 HMAC을 계산해 비교합니다.
JWS Compact Serialization
BASE64URL(UTF8(JWS Protected Header))
.
BASE64URL(JWS Payload)
.
BASE64URL(JWS Signature)
HS256 Signature
BASE64URL(
HMAC_SHA256(
shared_secret,
BASE64URL(UTF8(header)) + "." + BASE64URL(payload)
)
)
구성 요소
| 요소 | 역할 | 실무 해석 |
|---|---|---|
| JOSE Header | 알고리즘과 부가 메타데이터 명시 | alg 는 반드시 있어야 하고 실제 사용 알고리즘과 일치해야 함 |
| Payload | 클레임 또는 임의의 내용 | JWT에서는 보통 claims set JSON |
| JWS Signing Input | 서명/MAC 계산 대상 | base64url(header) + "." + base64url(payload) |
| Shared Secret | HMAC 계산에 쓰는 대칭키 | 검증자도 같은 키를 알아야 하므로 키 분배와 저장이 핵심 |
| Signature | HMAC 결과의 base64url 표현 | 검증 시 constant-time 비교가 필요 |
기본 구현
{
"typ": "JWT",
"alg": "HS256"
}
signingInput =
BASE64URL(UTF8(header)) + "." + BASE64URL(payload)
signature =
BASE64URL(HMAC_SHA256(shared_secret, signingInput))
jwt =
BASE64URL(UTF8(header)) + "." +
BASE64URL(payload) + "." +
signature
패턴 1. 대칭키 MAC 모델
HS256은 RFC 7518이 말하듯 “shared key”를 사용합니다. 즉 발급자와 검증자가 같은 비밀을 알고 있어야 합니다.
이 말은 곧, 그 비밀을 가진 주체는 검증뿐 아니라 같은 방식의 유효한 MAC도 만들 수 있다는 뜻입니다. 따라서 공개키 기반 서명처럼 “검증은 많이, 서명은 소수만”이라는 분리를 그대로 기대하면 안 됩니다.
즉 HS256은 단순하고 빠르지만, 검증 주체를 넓게 퍼뜨릴수록 키 관리와 신뢰 경계 설계가 더 중요해집니다.
패턴 2. 키 길이와 키 엔트로피가 핵심이다
JWA는 HS256에 대해 해시 출력 크기와 같은 크기 이상, 즉 최소 256비트 이상의 키를 요구합니다. 따라서 사람이 적당히 정한 짧은 문자열은 설계부터 잘못된 경우가 많습니다.
JWT BCP는 더 강하게 말합니다. 사람이 기억하는 비밀번호 같은 낮은 엔트로피 값은 HS256 키로 직접 사용하면 안 되며, 이런 키는 토큰을 입수한 공격자에게 오프라인 brute-force 또는 dictionary attack 표적이 될 수 있습니다.
즉 HS256에서 “비밀”은 이름만 비밀이어서는 안 되고, 충분히 긴 무작위 바이트열이어야 합니다.
Good Secret
- 256 bits or more
- cryptographically random
- stored securely
- rotated deliberately
Bad Secret
- short config string
- reused password
- human-memorable phrase
- copied across too many services and environments
패턴 3. 알고리즘 검증을 토큰 헤더에 맡기면 안 된다
alg 값을 그대로 믿고 검증 로직을 바꾸는 구현이다RFC 8725는 라이브러리가 호출자에게 허용 알고리즘 집합을 지정하게 해야 하고, 그 외 알고리즘은 쓰지 말아야 한다고 요구합니다. 즉 토큰 헤더에 적힌 alg 를 그대로 신뢰해 검증 방식을 바꾸면 안 됩니다.
같은 문서는 실제 위협 사례로 none 으로 바꾸는 공격과, RS256 을 HS256 으로 바꾸어 공개키를 HMAC shared secret처럼 오용하게 만드는 공격을 직접 언급합니다.
또 각 키는 정확히 하나의 알고리즘과만 연결되어야 하며, 이 점도 검증 시점에 강제되어야 합니다. 즉 HS256 검증 키와 RS256 검증 키를 같은 흐름에서 느슨하게 섞어 쓰면 안 됩니다.
한계와 주의점
첫째, HS256은 MAC이지 암호화가 아닙니다. 따라서 JWT가 HS256으로 보호되어 있어도 payload 내용은 base64url로 디코딩해 볼 수 있으며, 기밀성이 필요하면 JWE 같은 별도 암호화 계층을 고려해야 합니다.
둘째, 공유 비밀 하나로 생성과 검증이 모두 가능하므로, 검증 가능한 주체를 넓게 퍼뜨릴수록 비밀 유출과 오남용 리스크도 함께 커집니다.
셋째, HS256 서명이 유효하다고 해서 토큰을 그대로 신뢰해도 되는 것은 아닙니다. JWT BCP는 iss, aud 같은 문맥별 검증과, 서로 다른 종류의 JWT에 대해 상호 배타적인 validation rule을 둘 것을 요구합니다.
- HS256은 무결성과 인증용 MAC 이지, payload 암호화가 아님
- 공유 비밀을 아는 주체는 검증뿐 아니라 유효한 MAC 생성도 가능함
- 짧거나 예측 가능한 비밀은 오프라인 brute-force 공격에 취약함
- 서명이 유효해도 iss/aud/type 같은 문맥 검증이 빠지면 토큰 오용이 가능함
자주 하는 실수
- 사람이 기억할 수 있는 비밀번호나 짧은 문자열을 비밀키로 사용함
- 토큰의
alg값을 그대로 믿고 검증 알고리즘을 동적으로 바꿈 - 같은 키를 여러 알고리즘이나 여러 종류의 JWT에 섞어 사용함
- HS256 서명이 맞으면 claims 검증도 끝난 것으로 착각함
- payload 가 서명되었으니 비밀 정보도 안전하다고 오해함
- 직접 구현하면서 HMAC 비교를 일반 문자열 비교로 처리함
실무 루틴
- 비밀키는 최소 256비트 이상 의 무작위 바이트열로 생성한다.
- 사람이 기억하는 비밀번호를 HS256 키로 직접 쓰지 않는다.
- 토큰 종류별로 허용 알고리즘 집합 을 미리 고정하고,
alg가 기대값과 일치하는지 본다. - 키 하나는 가능하면 정확히 하나의 알고리즘 에만 연결한다.
- 서명 검증 후에도 iss, aud, typ, 토큰 프로필별 필수 클레임을 따로 검증한다.
- 검증 주체를 넓게 퍼뜨릴수록 키 노출 면적이 커진다는 점을 항상 의식한다.
디버깅
alg 값이 실제 기대하는 알고리즘과 일치하는지 본다..) 연결 규칙이 정확한지 본다.점검 체크리스트
- 허용 알고리즘이 HS256으로 고정되어 있는가
- alg 헤더와 실제 검증 알고리즘이 일치하는가
- shared secret 이 환경별로 섞이지 않았는가
- header.payload 계산과 base64url 규칙이 정확한가
- 키 길이와 엔트로피가 충분한가
- 서명 외 claims 검증까지 하고 있는가
요약
- ✅ HS256은 JWA에서 정의한 HMAC using SHA-256 이다.
- ✅ JWT/JWS에서는 header.payload 에 대해 HMAC-SHA-256을 계산한 뒤 base64url 인코딩한 값이 서명 부분이 된다.
- ✅ HS256은 서명이 아니라 대칭키 기반 MAC 이다.
- ✅ 공유 비밀키는 최소 256비트 이상 이어야 하고 충분한 엔트로피를 가져야 한다.
- ✅ 사람이 기억하는 비밀번호를 HS256 키로 직접 쓰면 안 된다.
- ✅ 서명 비교는 constant-time 으로 해야 한다.
- ✅ 토큰의
alg헤더를 맹신하지 말고 허용 알고리즘을 미리 고정해야 한다. - ✅ HS256이 유효하더라도 iss, aud, typ 같은 애플리케이션 문맥 검증은 별도로 필요하다.