회원가입시 프론트에서 전달받은 유저정보 중 비밀번호는 DB에 저장하기 전 암호화를 해주어야 한다. 어떤 과정을 거처 암호화가 되는지 알아보자.
bcrypt는 비밀번호 암호화에 사용되는 알고리즘을 제공하는 라이브러리다. 알고리즘은 직접 구현해도 되지만, 예민한 주제이니 만큼 검증된 라이브러리 사용을 추천한다.
$ pip install bcrypt
# 가상환경 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
Successfully~
하면 완료프론트에서 비밀번호 값을 받았다면. bcrypt의 hashpw()
메서드로 비밀번호 암호화를 진행한다. 실습을 위해 파이썬 인터프리터를 실행해보자.
python
을 콘솔에 입력>>> import bcrypt
>>> bcrypt.hashpw()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: hashpw() missing 2 required positional arguments: 'password' and 'salt'
bcrypt.hashpw()
를 인터프리터에 입력하게 되면 TypeError
가 발생한다. 내용을 보면 hashpw()의 인자로 password와 salt를 입력하라 한다.
>>> new_salt = bcrypt.gensalt() # gensalt()로 소금생성
>>> new_salt
>>> b'$2b$12$f1yD8xlLWG7R1pFWHOcizu' # new_salt의 값
>>> type(new_salt) # new_salt의 자료형을 본다
<class 'bytes'> # bytes형 인 것을 알 수 있다.
gensalt() 메서드로 소금을 생성했다. b'바이트바이트바이트'
가 리턴되었다. 리턴된 값은 bytes
형으로 출력되었다. 소금을 생성했다면 다시 hashpw()
를 사용해보자.
>>> bcrypt.hashpw('비밀번호',new_salt) # 입력받은 비밀번호와, 소금을 인자로 넘겼다.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/hwang/miniconda3/envs/bcrypt_test/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
TypeError: Unicode-objects must be encoded before hashing
또 에러가 출력되었다.
이번에는 입력받은 비밀번호가 utf-8 유니코드로 되어 있기 때문이다. 왜 유니코드는 안되?라고 생각할수 있는데 이유는 그저 bcrypt 인자는 bytes로 입력을 받기때문에~ 비밀번호를 byte형으로 바꿔주자.
# 인코딩을 하여 '비밀번호'를 bytes 변환
>>> new_password = '비밀번호'.encode('utf-8')
>>> new_password # 값을 출력해보면 아래와 같이 b'~~~' 바이트형이 된것을 확인할 수 있다.
>>> b'\xeb\xb9\x84\xeb\xb0\x80\xeb\xb2\x88\xed\x98\xb8'
준비는 되었으니 다시 비밀번호 암호를 시도해보자
# byte형 입력받은 비밀번호, byte형 소금을 인자로 전달
>>> hashed_password = bcrypt.hashpw(new_password, new_salt)
>>> hashed_password
>>> b'$2b$12$K.K0RarEc5A56OBULqrc0e9InU8ai5n1z4mQjdjNoB1g95ihc9xIu'
암호화 된 비밀번호를 출력해보니 b'~~~'
와같이 byte형 타입 인 것을 확인 할 수 있다.
이제 이 암호화 된 비밀번호를 DB에 저장해보자!
데이터베이스(database, DB)의 유저정보를 담는 테이블을 확인해보자. 비밀번호 fieldtype은
VARCHAR(200)
과 같이 문자열을 받도록 되어 있다. 해쉬 비밀번호는 bytes형 이기 때문에 DB에 저장 할 수 없다. 저장할 수 있도록 문자열로 변환하자.
>>> decode_hash_pw = hashed_password.decode('utf-8')
>>> decode_hash_pw
>>> '$2b$12$K.K0RarEc5A56OBULqrc0e9InU8ai5n1z4mQjdjNoB1g95ihc9xIu'
>>> type(decode_hash_pw) # 디코드한 해쉬비밀번호의 타입을 확인하면
>>> <class 'str'> # str이 출력되었다! 문자열로 변환
decode('utf-8')
을 사용한다.로그인이 구현되는 과정을 알아보자.
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'
# 문자열 비밀번호
>>> input_password = '비밀번호'
# DB에 저장된 비밀번호
>>> decode_hash_pw = '$2b$12$K.K0RarEc5A56OBULqrc0e9InU8ai5n1z4mQjdjNoB1g95ihc9xIu'
>>> bcrypt.checkpw(input_password,decode_hash_pw)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/hwang/miniconda3/envs/bcrypt_test/lib/python3.7/site-packages/bcrypt/__init__.py", line 100, in checkpw
raise TypeError("Unicode-objects must be encoded before checking")
또 타입에러가 발생!
역시나 이유는 단순하다. 유니코드(utf-8)가 아닌 bytes형 값을 입력해야 하기 때문이다. 두 인자의 데이터형을 bytes로 변환하여 다시 실행해보자.
>>> bytes_input_pw = input_password.encode('utf-8')
>>> bytes_db_pw = decode_hash_pw.encode('utf-8')
>>> bcrypt.checkpw(bytes_input_ytes_db_pw)
True
bcrypt.checkpw()
는 Bool
값을 리턴한다. 비밀번호 확인인 True 가 출력 되었을때 토큰을 발행하도록 진행하면 마무리 된다.
- 소금은 소금생성기로 랜덤한 값을 받자
bcrypt.gensalt()
- 비밀번호를 암호화 할땐
bcrypt.hashpw(바이트형 비밀번호, 소금생성기의 랜덤값)
- 비밀번호 확인 할땐
bcrypt.checkpw(바이트형 입력받은 비밀번호, 바이트형 DB에 저장된 해쉬 값)
- encode : 바이트 값으로 변환 해쉬용 값을 바꿀때
hashed_password.encode('utf-8')
- decode : 문자열로 변환 > DB에 저장할 때 !
hashed_password.decode('utf-8')
다음 포스팅은 jwt(json web token)
를 이용한 엑세스 토큰 발행과 어떻게 사용하는지 알아보도록 하겠돵!
선배님 글 보고 조금 정리 하구가요~ ㅎㅎ