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

윤현묵·2021년 8월 26일
0

Django

목록 보기
7/17
post-thumbnail
  • 인증(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
진정성 있는 개발자를 꿈꾼다

0개의 댓글