TIL DAY 18 || Django Authentication using Bcrypt
Authentication
유저의 identification 을 확인하는 절차를 인증(Authentication) 이라고 한다.
로그인 절차
- 유저가 아이디와 패스워드 생성
- 서버는 유저 패스워드를 암호화 해서 DB에 저장
- 유저가 아이디와 패스워드를 입력하여 로그인 시도
- 유저가 입력한 패스워드를 암호화 한 것과 암호화되서 DB에 저장된 유저의 비밀번호와 비교.
- 일치하면 로그인 성공
- 로그인 성공하면
access token
을 클라이언트에게 전송. - 유저는 로그인 성공으로 부터 받아온
access token
을 request header 에 넣어 서버에 전송함으로서 매번 로그인 해도 되지 않도록 한다. (stateless 한 HTTP 의 특징)
패스워드 암호화(Hashing)
패스워드를 암호화 하는 방식에는 여러가지가 있지만 우리가 흔히 쓰는 방식은 단방향 암호화 이다.
양방향 암호화는 해쉬된 값을 복호화 할 수 있지만 단방향 암호화는 복호화가 불가능하다.
우리가 사이트 비밀번호를 까먹었을 때 비밀번호 찾기를 누르면 원래 비밀번호를 알려주는게 아니라 새로 다시만들라고 하는 것도, 복호화가 불가능하기 때문에 아예 새로 만들라고 하는 것이다.
avalance 한 성격 또한 단방향 암호화의 특징이다.
'test' 를 해쉬했을 때와, 'test1' 을 해쉬했을 때의 값은 전혀 비슷하지 않고 완전히 다른데 이를 avalance 한 효과 라고 한다.
Bcrypt
물론 단방향 암호화에도 단점이 존재하긴 한다.
-
Rainbow table attack 이라고 해서 미리 해쉬값들을 계산해 놓은 테이블을 Rainbow table 이라고 한다.
-
해시 함수는 원래 패스워드를 저장하기 위해서 설계된 것이 아니라 짧은 시간에 데이터를 검색하기 위해 설계된 것
-
처리 속도가 최대한 빠르도록 설계됨
-
덕분에 공격자는 매우 빠른 속도로 Rainbow table 의 해쉬된 값들과 해킹할 대상의 암호화 된 다이제스트를 비교 가능
-
MD5를 사용한 경우 일반적인 장비를 이용하여 1초당 56억 개의 다이제스트를 대입 가능
-
따라서 salting 과 key stretching 기법을 이용하여 이런 취약점을 보완할 수 있다.
Salting
실제 패스워드 이외에 추가적으로 랜덤 데이터를 넣어서 함께 해시하는 방법이다.
Key Stretching
이미 한번 계산 된 단방향 해쉬 값을 해쉬하고 또 해쉬하고 반복하는 방법이다.
해쉬 반복이 추가될 때 마다 다이제스트 값을 비교하는 속도가 엄청나게 느려진다.
Salting + Key Stretching
Salting 과 Key Stretching 로직을 동시에 수행하도록 구현한 해쉬 함수중 가장 널리 사용되는 것이 bcrypt 이다.
Sign-up - password storing
- bcrypt.gensalt() 로 랜덤으로 생성한 Salt 값과
- bytes 로 인코딩 된 password 와 함께 해시한다.
- 해시된 값을 다시 utf-8 로 encoding 하여 str 로 db에 저장한다.
- 패스워드 저장 공간을 넉넉하게 charfield(max_length=100) 으로 설정해주었다.
Login - password comparing
- bcrypt.checkpw() 를 통해 입력받은 utf-8 string 값을 bytes 로 encoding 해서 첫 번째 인자로 넣고, db 에 있는 hash 된 string 값을 다시 bytes 로 encoding 하여 두번 째 인자로 넣어 비교한다.
- 이 때 bcrypt 는 해쉬된 값에서 salt 값을 따로 가져 온 뒤 첫번 째 인자에 salt 를 넣고 같은 알고리즘으로 해시해서 결과값을 비교한다.
마주한 문제들
db 내부에 패스워드를 db 내에서 비교하지 않고,
db 에서 패스워드를 꺼낸 다음에 값을 비교해야 되는 상황이기 때문에
다음과 같이 User instance 를 가져온다음,
해당하는 password 를 bcrypt.checkpw() 메소드로 비교할 계획이였다.
User.objects.filter(Q(email=email) | Q(username=username) | Q(phone_number=phone_number))
이 과정을 수행하던 중 문제가 발생했다.
원래는 username, email, phone_number 셋 중 하나만 입력해도 로그인에 성공할 수 있게 하려고 했었다.
그래서 테스트를 해보던 중
username=<유저이름>
phone_number=''
을 넘겨보았을 때 문제가 발생했다.
한 명의 유저 객체만 가져와야 하는데 여러 객체가 반환이 되었다.
phone_number 컬럼의 옵션이 unique=True
이지만 동시에 null=True
인 경우
phone_number 를 등록하지 않은 유저의 phone_number 값은 db 상에서 null 이다.
따라서 만약 phone_number 를 입력을 받지 않았는데
Q(phone_number=None)
또는
Q(phone_number='')
을 수행하면 phone_number 를 등록하지 않은 유저가 몽땅 filter 에 걸리게 된다.
그래서 다음과 같이 email, username, phone_number 별로 해서 User 객체를 가져오면 그 객체를 사용하는 것으로 로직을 변경했다.
Author And Source
이 문제에 관하여(TIL DAY 18 || Django Authentication using Bcrypt), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tk_kim/TIL-DAY-18-Django-Authentication-using-Bcrypt저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)