ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL.61 Bcrypt, Pyjwt (암호화)
    TIL 2020. 12. 8. 16:40
    728x90

    사용자의 비밀번호를 암호화하여 데이터베이스에 저장하고

    저장된 데이터베이스의 비밀번호화 사용자가 로그인할때 입력하는 비밀번호가 어떻게 같은지 검증하는법을 알아보자.

     

    비밀번호를 암호화 하기 위해 Bcrypt 와

    사용자 로그인시 함께 전달할 JWT Token을 만들기 위해 Pyjwt를 먼저 설치하자

    pip install bcrypt
    pip install pyjwt

    비밀번호 암호화

     

    'Bcrypt

    bcrypt는 단방향 해시 알고리즘으로 

    Salting Key Stretching 하면 복호화가 거의 불가능에 가깝다

     

    salting과 key stretching을 이용해 암호화를 하기 위해 Bcrypt에선

    hashpw 함수를 사용한다

    • hashpw( 기준 문자열(type : bytes) , bcrypt.gensalt() )

     

    먼저 아래와 같은 비밀번호를 암호화하는 과정을 알아보자

    >>>Import bcrypt
    >>>import jwt
    >>>
    >>>pw = '1234'

     

    그럼 '1234' 를 암호화 해보자

    >>> bcrypt.hashpw( pw, bcrypt.gensalt())

    기준 문자열 자리에 유니코드를 그대로 넣어주면 에러가 발생한다.

    (유니코드(Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준)

     

    따라서 encode를하여 type : str --> bytes로 변환한 후 값을 넣어줘야 한다.

    >>> pw.encode('utf-8')
    b'1234'
    >>> bcrypt.hashpw( pw.encode('utf-8'), bcrypt.gensalt())
    b'$2b$12$yRtBICmDHS/ASZrh10cO5.a.Wxy9jGIOtdgC44KQxmJfQepNMfuQm'
    
    #  salt ~ .... ~~ + password 형태이다

    * encoding 과 decoding 개념을 알아야 한다

    https://velog.io/@meekukin/%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%95%94%ED%98%B8%ED%99%94%ED%95%98%EA%B8%B0

     

    bcrypt 에서

    encoding 역할은 문자열을 -> bytes 으로 변환시켜 주는 역할을 한다.

    decoding 역할은 bytes -> 문자열 로 변환시켜 주는 역할을 한다.


    따라서 encode를하여 type : str --> bytes로 변환한 후 값을 넣어줘야 한다.

    >>> pw.encode('utf-8')
    b'1234'
    >>> bcrypt.hashpw( pw.encode('utf-8'), bcrypt.gensalt())
    b'$2b$12$yRtBICmDHS/ASZrh10cO5.a.Wxy9jGIOtdgC44KQxmJfQepNMfuQm'
    
    #  salt ~ .... ~~ + password 형태이다

     

    bcrypt.gensalt()로 불규칙한 salt를 만들 수 있는데 이를 

    예를 들어, salt 라는 변수로 이를 할당한 후 해쉬된 비밀번호를 만드는 방법으로 Key Stretching을 한다 생각하면 된다

     

    위와 같은 방법으로 hashen_pw 를 만든다

    >>> hashed_pw = bcrypt.hashpw (pw.encode('utf-8'), bcrypt.gensalt())
    >>> hashed_pw
    b'$2b$12$9471p.XRlBD81NW0ZcMSbuAi0FdpuFbaMyraTpy8vcyMgtkthULh6'

     


    여기서 주의!

    이렇게 만든 hashed_pw로 저장하면 안된다. 왜냐 DB -> b' $2b~~ 처럼 앞의 " b "가 함께 들어가게 된다.

    아래의 예제는 그대로 hashed_pw를 DB에 저장했을때이다.

     

    이렇게 저장해버리면 password를 데이터베이스에서 불러오면 데이터 타입이 Str으로 되어있는데

    이때 b 가 같이 나오게 된다.

    값을 입력하기 전에는 bytes -> 문자열 로 변환하는 decode가 선행되어야만 한다 (반드시)


    DB 저장전 반드시 decode 선행되어야만 한다.

    아래와 같은 상태로 Create 시 사용되어야 한다

    >>> hashed_pw.decode('utf-8')
    '$2b$12$9471p.XRlBD81NW0ZcMSbuAi0FdpuFbaMyraTpy8vcyMgtkthULh6'

    그렇다면 우리가 비밀번호를 암호화하기 위해서는 아무도 모르게 하기 위한 작업이다.

    그런데 위와 같이 salt값이 모두 다 보이는 이유는 무엇일까?

    이는 사용자가 로그인을 입력할때 DB에 저장되어있는 암호화된 비밀번호와 맞는지 검증하는데 필요하기 때문이다


    이제 로그인할때 어떻게 암호화된 비밀번호와 검증하는지 알아보자

    비밀번호를 암호화 하기 위해 hashpw 함수를 사용했다면,

    입력된 비밀번호를 비교하기 위해서는 checkpw 함수를 사용하면 된다.

     

    사용자가 비밀번호 1234 로 알맞게 입력 하였을때

    >>>bcrypt.checkpw('1234', hashed_pw)
    #
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/munseunghui/miniconda3/envs/auth/lib/python3.8/site-packages/bcrypt/__init__.py", line 120, in checkpw
        raise TypeError("Unicode-objects must be encoded before checking")
    TypeError: Unicode-objects must be encoded before checking

    checkpw 함수도 hashpw 함수와 마찬가지로 기존 문자열은 bytes 타입으로 들어가야한다!

    >>>bcrypt.checkpw( '1234'.encode('utf-8'), hashed_pw)
    #
    True

     

    잘못된 비밀번호 입력시

    >>>bcrypt.checkpw( '123'.encode('utf-8'), hashed_pw)
    #
    False

    JWT 생성

    토큰에 담겨있는 헤더, 페이로드, 시그니쳐를 확인하여 인가 방법을 제공하게 된다.

    pyjwt 라이브러리 사용

     

    토큰 생성시 형식은 아래와 같다

    •  jwt.encode(payload, key, algorithm)
    >>> encoded_jwt = jwt.encode({'user-id' : 5}, 'secret', algorithm='HS256')
    >>> encoded_jwt
    b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLWlkIjo1fQ.tBQu0HfnOYK7lL3tH5ImgsI-y4Jz1RKscJWbV3U2QMI'
    

    payload

    payload는 개인을 식별하는 정보 / payload는 암호화 하는 것이 아니라 encode하는 것이므로 개인 정보가 들어가면 안됨

    보통 data의 id를 payload로 사용

     

    key

    시크릿 키 / 랜덤한 조합의 키 (유출하면 안됨)
    예시 ) django settings.py의 SECRET_KEY

     

    algorithm

    보통 algorithm = 'HS256'을 많이 이용함 (알고리즘 또한 유출되면 절대 안됨)

     

    시크릿키와 암호화알고리즘도 my_settings.py에 넣어 settings.py가 깃허브에 올라갈때

    어떤 알고리즘을 쓰는지 알수없도록 설정해주자

     


    토큰 사용하기

    request.header의 'Authorization'에서 받은 토큰을 decode해서 payload를 풀어내서 이용자 정보를 사용/확인한다.

    token=jwt.encode(payload, 'secret', algorithm='HS256') payload=jwt.decode(token, 'secret', algorithm='HS256')

     

    위에서 만든 토큰을 이용해 통신을 주고 받는다

    그럼 아래와 같은 토큰이 요청으로 들어왔을때 백엔드서버에서는 어떤 일을 해줘야 할까?

    1. 우리 서비스 사용자가 맞는지

    2. 맞다면, 누구인지? 를 알아내야 한다.

    >>> encoded_jwt = jwt.encode({'user-id' : 5}, 'secret', algorithm='HS256')
    >>> encoded_jwt
    b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLWlkIjo1fQ.tBQu0HfnOYK7lL3tH5ImgsI-y4Jz1RKscJWbV3U2QMI'
    

     

    우리 서비스의 사용자가 아니라면 -> 발행한 토큰과 값이 다르다면

    아래와 같이 jwt가 exceptions error를 반환 -> 즉 우리 사용자가 아니다.

    >>>jwt.decode(encoded_jwt, 'KKKKK', algorithm='HS256')

     

    알맞는 토큰 값이 들어온다면

    해당하는 id값으로 어떤 사용자인지 알아내고 그에 맞는 권한을 부여하면 되겠다

     

     

    728x90

    'TIL' 카테고리의 다른 글

    TIL.66 스크럼(Scrum)  (0) 2020.12.13
    TIL. 62 회원가입, 로그인 _ HTTP 통신  (0) 2020.12.09
    TIL.60 인증 & 인가 (Hash/Hashing)  (0) 2020.12.07
    TIL.56 SPA_UX 및 개발자  (0) 2020.12.04
    TIL.49 How the web works?  (0) 2020.11.27
Designed by Tistory.