[TIL] 비밀번호 암호화, 인증, 인가 (bcrypt, pyjwt)

Jene Hojin Choi·2021년 2월 2일
0

Django

목록 보기
4/12
post-thumbnail

1. 비밀번호 암호화

만약 유저의 비밀번호를 데이터베이스에 그대로 저장하게 된다면 정보 유출이 너무 쉬워질 것이다. 그렇기 때문에 개발자로서 우리는 보안을 항상 염두해두고 있어야한다.

개발자들이 데이터를 저장할때는, 특히 비밀번호 같이 개인정보와 연관되어있는 정보라면, 그것을 암호화하여 저장하는 것이 반드시 필요하다.

1-1. bcrypt

암호화할때는 무엇을 써야할까? 다양한 방법이 있지만 이 글에서는 단방향 해시함수 bcrypt에 대해서 다룰 예정이다.

단방향이기 때문에 복원이 힘들지만, 완전히 안전한 것은 아니다. 그렇기 때문에 이를 보완하기 위해 bcrypt는 두개의 보완점을 사용한다.

1-2. Salting, Key Stretching

salting은 추가적인 데이터를 함께 hash 하는 방법이다. 이러한 방식을 사용하면 복호화하더라도 salt와 함께 추가적인 데이터가 복호화되기 때문에 정확한 원본을 알아내기 힘들다.

key stretching는 단방향 해쉬값을 계산한 후, 그 해쉬값을 또 해쉬하고, 그 작업을 계속 반복하는 것을 말한다. 이는 해커가 해쉬값을 계산하는 데에 필요한 시간을 늘리기 위해 사용된다.

2. bcrypt 실습

2-1. Setup

conda activate my_env
pip install bcrypt
pip install pyjwt

이후 python shell에 들어가서 실습을 할 것이기 때문에 import 해준다.

import bcrypt
import jwt     ##### pyjwt 아니고 jwt 입니다 

2-2. bcrypt.hashpw()

이 함수는 암호나 다른 어떤 string도 hash해준다.
bcrypt.hashpw(string, salt)

string (byptes)
Salt : bcrypt.gensalt()를 통해 자동으로 랜덤값을 받을 수 있다

>>> import bcrypt
>>> pw1 = '1111'
>>> hashed_pw1 = bcrypt.hashpw(pw1, bcrypt.gensalt())

근데 이러면 에러가 뜬다!

raise TypeError("Unicode-objects must be encoded before hashing")
TypeError: Unicode-objects must be encoded before hashing

2-3. .encode, .decode

unicode-object가 hash 전에 encoded 되어야한다는 에러이다.
unicode-object는 string을 16 혹은 32 바이트 데이터로 간주한다.

>>> hashed_pw1 = bcrypt.hashpw(pw1.encode('utf-8'), bcrypt.gensalt())
>>> hashed_pw1
b'$2b$12$RGgQJxEBM0ciEiKcVIp.eOrMD5zIYkO452V.8DAHubGVa9.X72vKq'

그래서 pw1 뒤에 encode를 하는 함수, .encode('utf-8') 를 붙여주어야한다!

>>> type(hashed_pw1)
<class 'bytes'>

하지만 이렇게 받은 hash가 된 비밀번호는 byte이다. 데이터베이스에 저장할때는 이를 decode 해주어 string으로 저장해야한다.

>>> hashed_decoded_pw1 = hashed_pw1.decode('utf-8')
>>> type(hashed_decoded_pw1)
<class 'str'>

3. pyjwt

로그인을 위해서 필요한 것은 인가(Authorization)이다. 인가는 유저가 요청하는 request를 실행할 수 있는 권한이 있는지 확인하는 절차이다.

서버는 이를 위해서 headers에 access token을 만들어서 보낸다. 이 정보를 jwt라고 한다.

이때 라이브러리는 pyjwt이다. 이는 jwt(json web token)을 암호화, 복호화하는 역할을 지닌다.

  1. header: 헤더는 토큰의 타입과 알고리즘 정보가 들어간다.
  2. payload: 내용에는 만료시간과 비공개 유저 정보가 담겨있다. claim set 이라고도 부른다. {name : value}의 한 쌍으로 구성되어 있고 여러 개의 클레임을 추가할 수 있다.
  3. signature: header, payload를 인코딩한 값을 합쳐 secret_key로 해쉬한다.

4. pyjwt 실습

4-1. jwt.encode

jwt.encode(토큰 대상, 시크릿 키, 해싱 알고리즘) 이런 식으로 사용하면 된다!


>>> import jwt
>>> SECRET = 'secret'

>>> access_token = jwt.encode({'user' : 1}, SECRET, algorithms = 'HS256')
>>> access_token
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxfQ.8myAOAXMOYyT7tWlXqqZEZhsd7KLczgfPIsnyZL2GEc'

>>> decoded_token = jwt.decode(access_token, SECRET, algorithms = 'HS256')
>>> decoded_token
{'user': '1'}

버전 전에는 algorithm이었다고 한다...
algorithms이다!

4-2. views.py 일부

아래와 같이 success 메세지와 함께 토큰을 발행해줄 수 있다.

# if 유저가 존재하고 그 유저의 비밀번호가 맞다면 
	token = jwt.encode({'email' : user.email}, my_settings.SECRET_KEY, algorithms=my_settings.ALGORITHM)                     
	return JsonResponse({'message': 'SUCCESS','token' : token}, status=200)  

0개의 댓글