~공식문서~
$ conda create -n auth python=3.8
$ conda activate auth
bcrypt는 암호를 해싱해주는 라이브러리로 파이썬 외에도 다양한 언어를 지원하고 있다.
$ pip install bcrypt
$ python
import bcrypt
password='1234'
비밀번호를 해시해주는 해시 패스워드 함수 실행하고, 레인보우 테이블 공격을 막기 위해 salt값을 추가해서 해시값을 강하게 만들어보자.
bcrypt.hashpw( password, bcrypt.gensalt() )
TypeError 발생.
"유니코드 객체는 해싱 전에 반드시 인코딩 되어야 한다"는 error가 발생했다. bcrypt는 str 데이터가 아닌 Bytes 데이터를 암호화하기 때문에, 암호화시에는 bytes화 먼저 해야한다.
인코딩한다는 것은 유니코드(파이썬 문자열)를 utf8 또는 EUC-KR이나 아스키 형식의 bytes코드로 변환하는걸 의미한다. 즉, 문자열 "1234" str타입을 인코딩하면 bytes의 1234로 인코딩이 된다. 반대로 bytes의 1234를 디코딩하면 문자열 "1234"가 된다.
그럼 인코딩과 디코딩이 뭔지 알았으니 직접 확인해 보자. 우선 인코딩을 하면,
#password 타입 확인
type(password)
>>> <class 'str'>
#인코딩
encoded_password = password.encode('utf-8')
encoded_password
>>> b'1234'
#인코딩된 password 타입 확인
type(encoded_password)
>>> <class 'bytes'>
인코딩 된 password를 디코딩 하면,
decoded_password = encoded_password.decode("utf-8")
decoded_password
#'1234'
그럼 다시 TypeError를 해결하기 위해서 인코딩된 password를 넣어서 패스워드를 해시해보자.
hashed_password = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt() )
hashed_password
# b'$2b$12$wChzrJyBNeyjJahRPalo/eBBqKrqUcyUJYW.MIMRtYBCdN5olRfJa'
해시가 잘 된것을 볼 수 있다. bcrypt.gensalt()
는 호출을 할 때마다 랜덤한 값이 출력된다.
salt값은 랜덤으로 생성되는데 이후에 해시된 패스워드를 어떻게 비교할 수 있을까? 해시된 패스워드에 salt값이 추가되어있기 때문에 가능하다. 한 번 확인해 보자.
salt에 저장된 값과 해시된 값을 확인해 보면 안에 salt 값이 추가된 것을 볼 수 있다. 여기서 주의할 점은 database 테이블에 저장할 때는 지금과 같이 문자열 1234를 바로 저장하는게 아닌 해시된 값을 저장해야 한다. 따라서 테이블에 저장할 때는 해시된 패스워드 값(bytes)을 디코드하면 문자열로 되고, 이 문자열을 전송하면된다.
bcrypt.checkpw()
함수는 로그인 앤드포인트를 구현할 때 패스워드를 비교할 때 사용된다. 첫 번째 인자로는 입력한 password가 들어가고 두 번째 인자로는 해시된 password가 들어가게 된다. 물론 두 인자 모두 데이터타입이 bytes여야 한다.
한 번 테스트 해보자.
지금은 password 변수에 문자열 '1234'를 넣었지만, 이후에 이 값은 프론트에서 입력받은 값으로 치환해주면 된다.
~공식문서~
bcrypt는 단방향 해시 알고리즘이라서 salting하고 stretching하면 복호화가 거의 불가능에 가깝다. JWT는 복호화하면 payload에 암호에서 담긴 정보를 확인할 수 있어서, 사용자가 로그인하면 백엔드 서버가 JWT 토큰을 프론트에 발행하게 되고, 프론트는 API를 요청 할 때 header에 토큰을 넣어서 보내게 된다. 백앤드 서버는 이 토큰에 담겨있는 header, payload, signature를 확인해서 인가방법을 제공하게 된다.
$ pip install pyjwt
$ python
import jwt
첫 번째 인자는 payload에 담을 정보를 써준다. 두 번째 인자는 세 번째 인자를 이용해서 payload에 담긴 인자를 복호화 할 수 있기 때문에 절대 노출되면 안된다(세 번째 인자도 마찬가지). 여기서는 우선 jwt 웹 토큰을 발행하고, 그 토큰을 jwt 디코딩을 했을 때 payload 정보가 복호화 된다는 정도만 알면 되겠다.
위는 5번 user를 jwt인코딩하는 과정이다. 여기서 중요한점은 payload에 절대 user의 password와 같은 개인정보를 넣으면 안된다.
type값은 원래 bytes가 나오는게 맞다는데 왜 이렇게 나온건지?
왜 algorithm은 안되고 algorithms로 하면 되는거지?
디코드 메서드로 복호화하면 user-id 5 가 복호화됨을 확인할 수 있다.