Nodejs/암호화

암호화#2 - JWT

Sila 2022. 3. 2. 21:14

1. Intro

지난 글에 이어서 JWT (JSON Web Token)에 대해 알아보자.

 

지금까지 배운 로그인 방식에는 세션과 쿠키가 있다.

 

서버는 부담을 줄이기 위해 세션 정보를 쿠키에 담아 브라우저에 전달하고,

 

브라우저는 이 쿠키를 요청마다 제시함으로써 허용된 사용자라는 것을 증명한다.

 

그런데 문제는 여기서 쿠키가 조작이 가능하다는 것이다.

 

이걸 아무나 위조해서 들고다니는 것을 막기 위해 전 글에서 배운 해시가 사용된다.

 

구체적으로 어떤 식으로 그 일이 일어나는지 직접 코드를 작성하며 알아보자.

 

2. JWT (JSON Web Token)

2.1 header, payload encoding

이름에서도 대강 짐작할 수 있는 것 처럼 웹에서 사용하는 토큰인데, JSON 형태이다.

 

이를 메소드를 통해 문자열로 바꿧다가 돌렸다가 하면서 해시를 만들어준다.

 

/*  JWT_practice.js  */

const crypto = require('crypto')

const header = {
   alg:'sha256',
   tpy:'jwt'
}

const payload = {
   userid: 'lsj1994',
   name:'lsj'
}

 

JWT는 header와 payload (클라이언트의 서버로의 요청에서 body 역할을 하는) 두 가지 부분으로 나뉜다.

 

다음과 같이 선언을 해 준 후, 우선 객체 상태인 header를 string으로 바꿔보자.

 

/*  jwt_practice.js  */

const stringHeader = JSON.stringify(header)

 

이렇게 header를 stringify 메소드를 이용해 문자열로 바꿔준다. 

 

console.log로 확인해보면 header에 비해 stringHeader는 중괄호 안의 key, value 모두가

 

문자열로 인식되는 것을 확인할 수 있다.

 

이제 이 stringHeader를 Buffer객체와 메소드를 이용해 buffer로 바꿔보자.

 

/* jwt_practice.js  */

const encodingHeader = Buffer.from(stringHeader).toString('base64')

 

여기서는 base64 인코딩 방식을 사용했다.

 

base64 인코딩 방식은 64진법을 기초로 하는데, 이 영향으로 문자열을 인코딩하는 과정에서 자릿수가 바뀔 수 있다.

 

그 때 생기는 빈 공간을 '='으로 표시한다. 

 

계속 진행하기 전에, 필요없는 '='을 제거해준다.

 

/* jwt_practice.js  */

const encodingHeader = Buffer.from(stringHeader).toString('base64').replace(/[=]/g,'')
// 뒤에 replace 메소드를 붙여준다.

// eyJhbGciOiJzaGEyNTYiLCJ0cHkiOiJqd3QifQ

 

cf) toString 메소드를 사용해 다시 원래 문자열로 돌려줄 수도 있다.

 

/* jwt_practice.js  */

const encodingHeader = Buffer.from(stringHeader).toString('base64').replace(/[=]/g,'')
const decodingHeader = Buffer.from(encodingHeader, 'base64').toString()

console.log(decodingHeader)
// {"alg":"sha256","tpy":"jwt"} (string 형태)

 

payload도 동일한 방식으로 인코딩해준다.

 

/* jwt_practice.js  */

const encodingPayload = Buffer.from(JSON.stringify(payload)).toString('base64').replace(/[=]/g,'')

 

2.2 header, payload hash 생성

이렇게 인코딩해 모은 encodingHeader, encodingPayload로 해시를 생성해 줄 것이다.

 

/*  jwt_practice.js  */

const signature = crypto.createHmac('sha256', Buffer.from('web7722')).update(`${encodingHeader},
${encodingPayload}`).digest('base64').replace(/[=]/g,'')

console.log(signature)
//  cxL3TatVCHcK07gyW9L3GuxgfbYYA3/uXaCjOIZXtdQ

 

이제 jwt를 생성할 준비가 끝났다.

 

2.3 JWT 생성

변수 jwt를 다음과 같이 생성한다.

 

/*  jwt_practice.js  */

const jwt = `${encodingHeader}.${encodingPayload}.${signature}`

 

즉, jwt는 암호화된 헤더, 페이로드, 그리고 헤더와 페이로드를 합쳐서 해시값으로 바꾼 signature

 

3개의 변수를 (.)을 찍고 이어서 만들어준 변수인 것이다.

 

이 변수를 cookie라는 객체에 담아줄 것이다.

 

/*  jwt_practice.js  */

const cookie = {
   token: jwt
}

 

여기까지 하면 서버에서 쿠키를 주고 받는 것처럼 구색이 갖추어졌다.

 

이제 이걸 다시 쪼개서 해석하는 과정 (원래 객체를 찾는 과정)을 진행해보자.

 

2.4 Decoding

jwt라는 변수 자체가 encodingHeader, encodingPayload, signature 3개 변수를 (.)를 사이에 두고 이어 붙인 변수이므로

 

이를 다시 .을 기준으로 쪼개줄 수 있다.

 

/*  jwt_practice.js  */

const [ head, pay, sign ] = cookie.token.split('.')

 

이 후, pay를 맞는 알고리즘에 따라 해독하면 원래 문자열을 찾을 수 있다.

 

/*  jwt_practice.js  */

const decodingPayload = JSON.parse(Buffer.from(pay, 'base64').toString('utf-8'))

 

아까 payload 객체를 암호화 한 코드를 다시 살펴보자.

 

/*  jwt_practice.js  */

const encodingHeader = Buffer.from(stringHeader).toString('base64').replace(/[=]/g,'')

 

문자열 stringHeader를 buffer로 바꾼 후, 다시 base64 인코딩 알고리즘으로 문자열을 만들었다.

 

이번에 해독할때는 이와 정확히 반대로,

 

문자열인 pay를 base64 알고리즘으로 버퍼로 바꾼 후, utf-8 문자셋을 이용해 되돌렸다.

 

이후 되돌린 문자열을 객체로 바꾸는 것으로 마무리 해독 과정이 마무리 된다.

 

암호화와 해독 과정을 각 단계별로 console.log로 확인해보면 다음과 같다.

 

 

 

그림으로 표현하자면 다음과 같다고 볼 수 있다.

 

'Nodejs > 암호화' 카테고리의 다른 글

암호화 #1 - Buffer, hash  (0) 2022.03.02