HTTP는 기본적으로 무상태(stateless) 프로토콜이다. 즉, 요청 하나만으로는 이 요청자가 누구인지 알 수 없다. 그래서 인증(Authentication) 메커니즘이 필요하다.
초창기에는 Basic 인증이 사용되었는데, 이는 단순히 username:password를 Base64로 인코딩해 Authorization 헤더에 넣는 방식이었다. 하지만 Base64는 단순 인코딩일 뿐 암호화가 아니다. 네트워크 스니핑만 해도 계정 정보가 그대로 노출된다. 이를 개선하려고 나온 방식이 Digest Authentication(다이제스트 인증)이다. 아이디어는 간단하다.
즉, 서버가 클라이언트에게 “비밀번호 직접 보여줘” 대신 “이 값들로 계산한 해시를 보내봐. 네가 진짜 비밀번호를 아는지 내가 확인할게”라는 식으로 인증을 수행한다.
Digest 인증은 challenge-response 패턴을 따른다. 핵심은 클라이언트가 서버에 비밀번호 원문을 보내지 않고 해시값을 보내 서버가 이를 검증한다는 점이다.
GET /private/data HTTP/1.1
Host: www.example.com
클라이언트는 인증 정보 없이 보호된 자원에 접근을 시도한다.
자원이 보호되어 있다면 서버는 401 Unauthorized를 반환하며 WWW-Authenticate 헤더에 Digest 인증 요구를 담는다.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
realm="Protected",
qop="auth",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
realm: 보호 영역 이름. 사용자에게 “어떤 영역에 접근하려고 하는지”를 알려준다. realm이 다르면 자격 증명이 달라질 수 있다.nonce: 서버가 발급하는 임의 문자열. 한 번 쓰고 버려지거나(1회용), 일정 시간만 유효하다. 재전송 공격을 막는 핵심 요소다.qop (Quality of Protection): 보안 수준.auth: 인증만 수행auth-int: 요청 본문까지 해시해 무결성 검증 수행 (성능·구현 복잡성 증가)opaque: 서버가 발급한 불투명 데이터. 클라이언트는 그대로 돌려주기만 하며, 서버가 세션 일관성을 유지하는 데 활용한다.algorithm: 해시 알고리즘. 보통 MD5 또는 MD5-sess (세션마다 HA1을 새로 계산).클라이언트는 비밀번호 원문을 보내지 않고 nonce, realm, URI, Method 등을 조합해 해시를 계산한다.
GET /private/data HTTP/1.1
Host: www.example.com
Authorization: Digest
username="hyeonjin",
realm="Protected",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
uri="/private/data",
response="6629fae49393a05397450978507c4ef1",
qop=auth,
nc=00000001,
cnonce="0a4f113b",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
여기서 핵심 파라미터는
username: 사용자 계정uri: 요청 URIresponse: 서버 검증을 위한 핵심 해시 값nc (nonce count): 특해당 nonce로 몇 번째 요청인지. 8자리 16진수(00000001)로 표현하며 서버가 증가 여부를 확인한다.cnonce: 클라이언트가 생성한 임의 문자열. nonce와 합쳐 공격 난이도를 높인다.서버는 클라이언트와 동일한 방식으로 해시를 계산해 response 값을 비교한다.
HA1 = MD5(username:realm:password)만 저장해도 충분하다.HA1과 요청 정보로 계산한 해시가 일치하면 인증 성공이다.Digest의 핵심은 비밀번호 원문 없이 검증 가능하게 만드는 해시 공식이다.
response = MD5(HA1 : nonce : nc : cnonce : qop : HA2)
HA1 = MD5(username : realm : password)
HA2 = MD5(method : uri) ; qop=auth
HA2 = MD5(method : uri : body); qop=auth-int
HA1: 비밀번호 기반 값. 서버는 이것만 저장해도 된다.HA2: 요청 기반 값. 요청마다 달라지므로 같은 비밀번호라도 매번 다른 해시가 생성된다.nonce, nc, cnonce, qop: 재전송 공격 방지에 기여한다.따라서 해커가 response만 가로채도 같은 요청 외에는 재사용할 수 없다.
비밀번호 원문 노출 방지
네트워크에 username:password가 그대로 흐르지 않고 해시값만 전송된다.
재전송 공격 방지
nonce, cnonce, nc를 조합해 단순 복붙 공격을 막는다.
서버는 nonce 유효기간을 짧게 잡아 replay를 방지할 수 있다.
서버 저장 방식 개선
서버가 원문 비밀번호를 저장하지 않아도 된다.
HA1만 저장해도 응답 검증이 가능하므로 관리가 더 안전하다.
여전히 취약
MD5는 충돌 취약점이 있고 오늘날 기준으로 안전하지 않다.
HA1이 유출되면 무차별 대입 공격(dictionary attack)으로 원문 비밀번호를 추측할 수 있다.
중간자 공격(Man-in-the-middle)
HTTPS가 아닌 평문 HTTP라면 중간자가 nonce를 가로채거나 변조할 수 있다.
Digest만으로는 안전하지 않으며 TLS(HTTPS)와 반드시 함께 사용해야 한다.
복잡한 구현과 낮은 채택률
구현이 복잡하고 클라이언트/서버가 모두 지원해야 한다.
실제로는 HTTPS + Basic, Bearer Token, OAuth2, JWT 같은 방식이 훨씬 널리 쓰인다.
nonce 설계
단순 난수 대신 timestamp + HMAC(secret, timestamp + client-ip) 형태로 발급하면 위조·재사용 방지를 강화할 수 있다.
HA1 보호
서버가 HA1을 저장한다고 해도 유출되면 오프라인 공격에 취약하므로 추가 보호가 필요하다.
qop 선택
auth-int를 쓰면 요청 본문 무결성까지 보장 가능하지만 성능과 구현 부담이 크다.
| 항목 | Basic Authentication | Digest Authentication |
|---|---|---|
| 비밀번호 전송 방식 | username:password를 Base64 인코딩하여 그대로 전송 | username, realm, nonce, URI 등과 비밀번호를 조합해 **MD5 해시(digest)**로 전송 |
| 암호화 여부 | 없음 (단순 인코딩) → 평문 노출 | 해시 기반으로 원문 비밀번호는 전송하지 않음 |
| 재전송 공격 방지 | 불가능 (한 번 캡처하면 재사용 가능) | nonce, cnonce, nc를 조합해 replay 공격 방지 |
| 서버 측 저장 방식 | 원문 비밀번호 필요 (또는 가역 암호화 저장) | HA1 = MD5(username:realm:password)만 저장 가능 |
| 추가 필드 | 거의 없음 | realm, nonce, qop, opaque, algorithm, nc, cnonce 등 다양한 보안 파라미터 사용 |
| 보안 수준 | 매우 낮음. HTTPS 없이는 사용 불가 | Basic보다는 개선되었지만, MD5 취약점·MITM 위험 여전 |
| 복잡도 | 단순, 구현 쉬움 | 상대적으로 복잡, 클라이언트/서버 모두 지원 필요 |
| 실제 채택 | HTTPS와 조합해 여전히 일부 사용됨 | 이론적 가치↑, 하지만 실제 사용은 거의 드묾 |
| 대체 기술 | HTTPS + Basic 조합, Bearer Token | JWT, OAuth2, SCRAM 등 현대 인증 방식 |
Digest 인증은 Basic 인증의 치명적인 약점을 개선하려는 과도기적 기술이었다.
하지만 Digest만으로는 안전하지 않다. 전송 계층 보안(HTTPS) 없이는 여전히 중간자 공격, 오프라인 공격 등에 취약하다. 따라서 Digest 인증은 오늘날 실무에서 거의 사용되지 않고 “HTTP 보안 발전 역사”의 한 단계를 보여주는 사례로 의미가 크다.