Algorithm - JWT 위변조를 알아내는 HMAC이란?

2019. 4. 21. 16:48알고리즘&자료구조

오늘 포스팅할 내용은 HMAC에 대한 설명이다. 우선 HMAC에 대해 설명하기 이전에 요즘 대부분이 사용하고 있는 토큰인증 방식에 이용되는 JWT(Json Web Token)이다. 그렇다면 JWT란 무엇일까?

 

JSON Web Token은 웹표준(RFC-7519)으로서 두 개체에서 JSON객체를 사용하여 가볍고 자가수용적인(self-contained)방식으로 인증정보를 안정성있게 주고 받기 위해 만들어진 토큰이다. 우선 JWT토큰은 수많은 프로그래밍 언어에서 공통적으로 사용할 수 있는 인증 토큰이다. 그리고 JWT는 자체적으로 필요한 모든 정보(in Claims)를 가지고 있다. JWT 시스템에서 발급된 토큰은, 토큰에 대한 기본정보,전달할 정보(ex. 유저정보,권한 등..) 그리고 토큰의 signature를 포함하고 있다. 그렇다면 왜 토큰 방식의 인증방법을 채택하는 것일까?

 

Stateless 서버

Stateless 서버를 설명하기 이전에 Stateful서버가 무엇인지 먼저 알아본다. Stateful 서버는 클라이언트에게서 요청을 받을 때마다, 클라이언트의 상태를 계속해서 유지하고, 이 정보를 이용하여 서비스제공을 한다. Stateful 서버의 예제로는 HttpSession을 서버에 유지하고 있는 WAS이다. 예를들어 사용자가 로그인하면 로그인한 사용자의 정보를 자체 메모리에 갖고 있는다. 그리고 매 사용자 요청에 자신의 메모리에 담긴 세션객체를 이용한다. 만약 사용자가 많아 진다면 메모리에 담긴 세션객체가 많아질 것이고, 그렇다면 서버의 램에 많은 부하가 갈것이다. 그렇기 때문에 Stateless서버로 아키텍쳐를 잡고 서비스를 운영하면 상태를 유지할 필요가 없기때문에 서버에 부담도 주는 것과 동시에 확장성 또한 매우 높아진다.

 

<서버기반 인증>

 

<토큰기반 인증>

 

그렇다면 토큰기반인증에 사용되는 JWT란 진짜 무엇인가?

 

JWT의 구성

JWT는 "."을 구분으로 3가지의 문자열로 구성되어 있다.

이렇게 3가지 부분으로 나뉘어있는 토큰을 하나하나 설명해본다.

 

헤더(Header)

헤더는 두가지의 정보를 가지고 있다.

  • typ : 토큰의 타입을 지정한다. 여기서는 JWT.
  • alg : 해싱 알고리즘을 지정한다. 해싱 알고리즘으로는 보통 HMAC SHA256 혹은 RSA가 사용된다. 오늘 설명할 부분이기도 하다.
1
{ "typ" : "JWT", "alg" : "HS256" }
cs

 

이 JSON 형태의 헤더정보를 base64로 인코딩하게 되면 토큰의 첫번째 헤더부분에 위치하게 된다.

 

정보(Payload)

Payload 부분에는 토큰에 담을 정보가 들어있다. Payload에 담기는 정보의 한 조각을 'Claim'이라고 부르고, 이는 키-값형태의 한쌍을 의미한다. 그렇다면 Payload는 'Claim'들의 모임인 'Claims'가 된다. 이런 클레임에는 크게 세분류로 나뉘어져있다.

 

  • 등록된(registered) 클레임
  • 공개(public)클레임
  • 비공개(private)클레임

등록된 클레임

  • iss : 토큰 발급자(issuer)
  • sub : 토큰 제목(subject)
  • aud : 토큰 대상자(audience)
  • exp : 토큰의 만료시간, 시간은 NumericDate 형식으로 되어있어야한다.(ex. 1241421414124141)
  • iat : 토큰이 발급된 시간(issued at), 이 값을 이용하여 토큰의 age를 판단할 수 있다.
  • jti : JWT의 고유 식별자로써, 주로 중복적인 처리를 방지하기 위하여 사용된다.

위에 설명된 것보다 등록된 클레임 종류는 더 있을 것이다.

 

공개 클레임

공개클레임들은 충돌이 방지된 이름을 가지고 있어야한다. 충돌을 방지하기 위해서는, 클레임 이름을 URI형식으로 짓는다.

 

1
2
3
{
    "https://jwt.com/jwt_claims/is_admin" : true
}
cs

 

비공개 클레임

등록된 클레임도 아니고, 공개된 클레임도 아니다. 양 측간에 협의하에 사용되는 클레임 이름들이다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있다.

1
{ "username" : "yeoseonggae" }
cs

이러한 Payload가 base64로 인코딩되면 토큰의 두번째 자리에 위치하게 된다.

 

서명

사실 오늘 포스팅한 주제에 해당 되는 부분이다. 이부분은 헤더의 인코딩 값과, Payload의 인코딩 값을 합친 후 주어진 비밀키로 해쉬 값을 생성한다. 이렇게 만든 해쉬를 다시 base64로 인코딩하면 세번째 자리에 위치하게 되는 값이 된다. 그렇다면 서명은 왜 필요한 것일까?

 

JWT토큰 인증 방법의 큰 장점 중 하나는 토큰의 유효성을 검사하기 위하여 Resource Server(보호된 API를 가지고 있는 서버)가 인증서버(Authorization Server)로 토큰을 전송하지 않는 다는 점이다. JWT 토큰을 사용하지 않는 토큰 인증 방법은 Resource Server가 토큰값을 받으면 이 토큰값 검사를 위하여 인증 서버 혹은 토큰이 저장된 저장소에서 토큰을 꺼내와 토큰의 유효성을 검사하곤 했다. 사용자가 적으면 문제 없지만 사용자가 대고객이라면? 인증서버 혹은 토큰의 저장소(Redis,RDBMS)에 큰 무리가 갈것이다. 하지만 JWT 토큰은 자체적으로 토큰 유효성 검사가 가능하다 ! 그 이유는 오늘 설명할 HMAC이다.(물론 HMAC이 아니라 RSA도 있다.) Resource Server는 토큰 유효성을 검사하기 위해 인증서버 혹은 토큰이 저장된 저장소에서 토큰을 가져올 필요가 없다. 단순히 JWT토큰을 받아서 해당 토큰이 위변조되었는지만 확인하여 위변조가 되지 않으면 JWT토큰 내부에서 사용자 정보를 꺼내 사용할 수 있기 때문이다. 바로 이렇게 토큰의 위변조가 있었는지 혹은 위변조를 방지하는 기법 중 하나를 HMAC(Hash-based Message Authentication)이라고 한다.

 

해싱은 원문(Plain Text)을 일정 길이의 바이트로 변환하는데 그 결과가 유일하여 긴 문장의 빠른 검색을 위한 키 값으로 많이 쓰인다. 그리고 해시된 결과를 사용해서 거꾸로 원문을 복구할 수 없다는 것이 해시를 사용하는 고유한 가치라고 할 수 있다. 이러한 해시의 특성을 사용하여 데이터의 위변조 여부를 알아낼 수 있다.

 

출처 : http://blog.jakeymvc.com/sso-hmac/

 

  1. 사전에 Sender와 Receiver는 별도 채널로 해시에 사용할 키(Share key)를 공유한다. 그리고, 양쪽에서 사용할 해시 알고리즘을 정한다.
  2. Sender는 공유키를 사용해서 UserId를 해시한다.
  3. Sender는 원본 UserId와 그 해시결과(HMAC)을 쿼리스트링 값으로 Receiver에게 전달한다.
  4. Receiver는 받은 UserId를 공유키를 사용하여 같은 알고리즘으로 해시한 결과(Receiver's HMAC)를 만든다.
  5. Receiver가 만든 HMAC과 쿼리스트링으로 받은 HMAC이 같다면 UserId는 변경되지 않았다고 신뢰할 수 있다.

UserId + Sharekey = HMAC 이라는 공식에서 UserId를 변경한다면 그 결과인 HMAC도 변경된다. 따라서, 위조한 UserId가 Receiver에서 인정 받으려면 똑같은 해싱 과정을 거쳐 HMAC을 제공해야하는데, 공유키와 해시 알고리즘을 알지 못하면 어려운 일인 것이다.

 

즉, JWT는 이러한 위변조 방지 방법을 이용하여 인증서버 없이도 Resource Server에서 안전한 토큰 유효성 검사가 가능한 것이다. 물론 우리가 인증에 사용하는 JWT는 위에서 설명한 HMAC 플로우랑은 조금 다를 수 있다. 하지만 이번 포스팅의 목적은 이러한 HMAC을 이용하여 토큰의 유효성을 검사한다라는 것을 알기 위한 포스팅이기에 직접 프로젝트에 JWT인증 방법을 도입하려면 적당한 플로우를 적용시켜야 할 것이다.