회원가입 시 비밀번호는 데이터베이스에 저장하기 전 암호화가 필요하다.
bcrypt 는 비밀번호 암호화에 사용되는 알고리즘을 제공하는 라이브러리이다.
가상환경 내에 설치하도록 한다. PyJWT 또한 동시에 설치 하였다.
$ pip install bcrypt PyJWT
# 가상환경 bcrypt_test를 활성화 & 프로젝트디렉토리로 이동
bcrypt_test $ pip install bcrypt # 비크립트 설치
Collecting bcrypt
Downloading bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl (53 kB)
|████████████████████████████████| 53 kB 203 kB/s
...중략
Installing collected packages: six, pycparser, cffi, bcrypt
Successfully installed bcrypt-3.1.7 cffi-1.14.0 pycparser-2.20 six-1.14.0
pip freeze 로 설치된 내역들을 확인할 수 있다.
bcrypt의 hashpw() 메소드로 비밀번호 암호화를 진행하는 테스트를 위해 터미널에서 python 을 입력하여 파이썬 인터프리터를 실행해 본다.
실습 사진을 첨부해 본다.
>>> import bcrypt
>>> bcrypt.hashpw('5602',bcrypt.gensalt())
>>> bcrypt.hashpw('5602',bcrypt.gensalt())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/teddyjung/miniconda3/envs/westagram/lib/python3.7/site-packages/bcrypt/__init__.py", line 61, in hashpw
raise TypeError("Unicode-objects must be encoded before hashing")
TypeError: Unicode-objects must be encoded before hashing
오 해쉬 전에 유니코드 오브젝트는 인코딩 되어야 한다 라는데 그 말인 즉슨 내가 비밀번호라고 지정한 '5602' 가 사실 문자열이기 때문에 문자열 비밀번호를 bytes 로 변환한 뒤 암호화를 진행해야 된다는 타입 에러이다.
왜 유니코드는 안되?라고 생각할수 있는데 이유는 그저 bcrypt 인자는 bytes로 입력을 받기때문에~ 비밀번호를 byte형으로 바꿔주자.
>>> bytes('5602','utf-8')
b'5602'
>>> a = bytes('5602','utf-8')
>>> type(a)
<class 'bytes'>
bytes 로 형변환을 해주고, 변수 a에 대입한 다음 변수 a의 타입을 확인해 보았다. 역시나 바이트로 바뀌어 있었다. (피자 한 바이트 하고 싶..읍..)
>>> b = '5602'
>>> type(b)
<class 'str'>
>>> b.encode('utf-8')
b'5602'
>>> c = b.encode('utf-8')
>>> c
b'5602'
>>> type(c)
<class 'bytes'>
임의의 변수에 문자열을 할당해 주고 b.encode('utf-8') 로 확인하니 바로 위 형변환 방법1번과 동일한 결과를 확인할 수 있었다.
아니 비밀번호는 문자열이야? 숫자 아닌감? 난 숫자로 넣을꺼 따옴표 뺄꺼! 빼액!
>>> bytes(5602, 'utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: encoding without a string argument
조용히 문자열이라는 점을 깨닫도록 하자.
자 그럼 원래 하려던 거 마저 해야지.
>>> hashed_password = bcrypt.hashpw('5602'.encode('utf-8'),bcrypt.gensalt())
>>> hashed_password
b'$2b$12$LGEQ6ljIJgMiT7QTPyWdw.9TF4PRq.5r9SBCLE/bnZB26K4nWxCju'
참고로 bcrypt.gensalt() 에서의 gensalt() 는 소금 생성기로써 비밀번호를 외계어로 바꿔치기 하는 것에 더해 소금을 뿌려서 비밀번호 + 더미 를 생성해 암호화를 강력하게 만들어 준다는 느낌으로 이해하면 되겠다.
암호화 된 비밀번호를 출력해보니 b'~~~' 와같이 byte형 타입 인 것을 확인 할 수 있다.
이제 이 암호화 된 비밀번호를 DB에 저장해보자!
데이터베이스 의 유저 정보를 담는 테이블 그러니까 account 앱 내의 models.py 파일을 확인해보자.
아마 이럴껄? 아님 말고..
password = model.CharField(max_length=200)
해쉬 비밀번호는 bytes형 이기 때문에 DB에 저장 할 수 없다. 저장할 수 있도록 문자열로 변환하자.
>>> decode_hashedpw = hashed_password.decode('utf-8')
>>> decode_hashedpw
'$2b$12$LGEQ6ljIJgMiT7QTPyWdw.9TF4PRq.5r9SBCLE/bnZB26K4nWxCju'
>>> type(decode_hashedpw)
<class 'str'>
로그인이 구현되는 과정을 알아보자.
bcrypt에서는 비밀번호 정보를 비교할수 있는 checkpw()메서드를 제공한다. 위 과정을 더 단순하게 구현 할 수 있다. 먼저 bcrypt.checkpw()는 어떤 인자를 받는 지 확인하자.
파이썬 쉘이든 뭐든 쉘을 잘 써 버릇해야 되는구나 싶다.
>>> bcrypt.checkpw()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: checkpw() missing 2 required positional arguments: 'password' and 'hashed_password'
그 두가지 포지셔널 인자는 바로
1. password : 로그인 시 입력받은 비밀번호
2. hashed_password : DB에 저장된 해시 암호화 된 값
>>> bcrypt.checkpw('5602'.encode('utf-8'), hashed_password)
True
bcrypt.checkpw()는 Bool 값을 리턴한다. 비밀번호 확인인 True 가 출력 되었을때 토큰을 발행하도록 진행하면 마무리 된다.