TIL 31. 비밀번호 암호화 및 로그인 시 검증

윤현묵·2021년 8월 26일
0

Django

목록 보기
7/17
post-thumbnail
post-custom-banner
  • 인증(Authentication)

    -. 인증이란 유저의 Identification을 확인하는 절차로, 유저의 아이디와 비밀번호를 확인합니다.
    -. 인증을 하기 위해선 유저의 아이디와 비밀번호를 생성할 수 있는 기능이 필요합니다.

  • 먼저 로그인 절차는 다음과 같습니다.

  1. 유저 아이디와 비번 생성
  2. 유저 비번 암호화 해서 DB에 저장
  3. 유저 로그인 → 아이디와 비밀번호 입력
  4. 유저가 입력한 비밀번호 암호화 한후 암호화되서 DB에 저정된 유저 비밀번호와 비교
  5. 일치하면 로그인 성공
  6. 로그인 성공하면 access token을 클라이언트에게 전송
  7. 유저는 로그인 성공 후 다음부터는 access token을 첨부해서 request를 서버에 전송함으로서 매번 로그인 해도 되지 않도록 한다
  • 비밀번호 암호화

    -. 유저의 비밀번호는 보안을 위해 절대 그대로 DB에 저장되지 않고, 암호화하여 저장됩니다.
    -. 비밀번호 암호화에는 단방향 해쉬 함수가 일반적으로 쓰이는데, 해시 함수는 단방향이므로 암호화는 가능하지만 복호화는 불가능하여 원본을 알 수 없기 때문에 이와 같은 이유로 패스워드를 바로 데이터베이스에 저장하지 않고 단방향 암호화된 다이제스트를 저장하는 것이 보편화되었습니다. 하지만 몇몇 단점을 보완하기 위해 솔팅과 키 스트레칭 방법을 추가적으로 적용하고 있습니다.

단방향 해시 함수 문제점

※ 레인보우 공격(rainbow attack) - 인식 가능성
동일한 메시지가 언제나 동일한 다이제스트를 갖는다면 해커가 전처리(pre-computing) 된 다이제스트를 다량 확보한 다음 탈취한 다이제스트와 비교해 원본 메시지를 찾아내거나 동일한 효과의 메시지를 찾을 수 있습니다. 이와 같은 다이제스트 목록을 레인보우 테이블(rainbow table)이라 한다. 게다가 다른 사용자의 패스워드가 같으면 다이제스트도 같으므로 한꺼번에 모두 정보가 탈취될 수 있습니다.

※ 무차별 대입 공격(brute force attack) - 속도
해시 함수는 원래 짧은 시간에 데이터를 검색하기 위해 설계된 것으로 해시함수의 빠른 처리 속도로 인해 해커는 매우 빠른 속도로 임의의 문자열의 다이제스트와 해킹할 대상의 다이제스트를 비교할 수 있습니다. (MD5를 사용한 경우 일반적인 장비를 이용하여 1초당 56억 개의 다이제스트를 대입 가능)

단방향 해시 함수 보완

※ 솔팅(Salting)
패스워드에 임의의 문자열인 salt를 추가하여 다이제스트를 생성하는 것으로, 같은 패스워드라도 각기 다른 salt가 들어가 다이제스트가 다르게 생성되어 rainbow table을 무의미하게 만들어 줍니다. (salt는 최소 128bit 정도는 되어야 안전합니다)

※ 키 스트레칭(key stretching)
해시를 여러 번 반복하여 시간을 늘림으로써 무차별 대입 공격(brute force attack)에 대비하는 것입니다. 쉽게 말하자면, 패스워드의 다이제스트를 생성하고, 생성된 다이제스트를 입력값으로 하여 또 다이제스트를 생성하고... 이를 반복하여 다이제스트를 생성하는 식입니다. 만약 원래대로라면 1초에 56억 개를 대입하여 비교할 수 있는데 키스트레칭을 적용하여 1초에 5번 정도만 비교할 수 있게 설정한다면 하나의 다이제스트를 해킹하는 데 오랜 시간이 걸릴 것입니다.

Bcrypt 암호화

salting과 key stretching을 모두 구현한 해쉬 함수 중 가장 널리 사용되는 것이 bcrypt입니다. 암호화를 위해서 hashpw 함수를 사용하는데 사용법은 아래와 같습니다. (pip install bcrypt 설치)

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

그런데 기준 문자열 자리에 유니코드를 그대로 넣어주면 에러가 발생합니다.
(유니코드(Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준)
따라서 encode를 하여 type : str → bytes로 변환한 후 값을 넣어줘야 합니다.
(bcrypt 에서 encoding 역할은 문자열을 → bytes로 변환시켜 주는 역할을 하고,
decoding 역할은 bytes → 문자열로 변환시켜 주는 역할을 합니다.)
bcrypt.gensalt()로 불규칙한 salt를 만들어 해쉬된 비밀번호를 만드는 방식인데, 아래와 같이 비밀번호가 생성되는 것을 볼 수 있습니다. 여기서 앞의 b는 bytes 형식이라는 것을 의미하는데 이 상태에서는 DB에 값을 저장할 수 없어 다시 str 타입으로 변환하기 위해 hashed_pw.decode('utf-8')과 같이 decode를 해주어야 합니다.

hashed_pw = bcrypt.hashpw (pw.encode('utf-8'), bcrypt.gensalt())
hashed_pw
b'$2b$12$9471p.XRlBD81NW0ZcMSbuAi0FdpuFbaMyraTpy8vcyMgtkthULh6'
hashed_pw.decode('utf-8') #DB 저장을 위한 decode(str 타입으로 변경)

Bcrypt 비밀번호 검증

로그인 할 때 비밀번호 검증을 위해 입력된 비밀번호와 DB에 저장된 암호화된 비밀번호를 비교하기 위해서는 checkpw 함수를 사용하면 됩니다. 아래와 같이 사용하며 이 함수도 마찬가지로 bytes 타입으로 값을 입력해야 하므로 encode를 하여 값을 넣어주어야 합니다.

bcrypt.checkpw( 'password'.encode('utf-8'), hashed_pw)

이렇게 비밀번호를 검증하여 일치하지 않으면 에러 메시지를 리턴하고, 일치하게 되면 성공 메시지와 함께 토큰을 발급하게 됩니다.

JWT 토큰에 대해서는 다음 블로그 내용으로 알아보도록 하겠습니다!!

profile
진정성 있는 개발자를 꿈꾼다
post-custom-banner

0개의 댓글