Django | 클래스 101 클론 - bcrypt

김민철·2021년 2월 13일
0

팀프로젝트

목록 보기
7/8
post-thumbnail

이 글은 어떤식으로 암호를 만들어가는지에 초점을 두고 작성했습니다.


회원가입한 유저의 비밀번호를 저장해 보겠습니다. 만약 유저가 비밀번호를 "password" 로 지정했으면 이대로 DB에 저장시켜주면 될까요 ??

단방향 해시 함수

단방향 해시 함수는 수학적인 과정을 거쳐 암호를 생성합니다. 단방향이라는 말 처럼 원본 비밀번호를 알면 생성된 암호를 구하기 쉽지만, 암호만 가지고는 원본 비밀번호를 구할 수 없어야 합니다.

import hashlib

>>> hashlib.sha256(b"mincheol").hexdigest()
'62e1c059101f4ee2fc053b2664e52c111eb67e73a996c0da9091338d7c575a06'
 
>>> hashlib.sha256(b"kimmincheol").hexdigest()
'c1e848ae48530986bf7772636c7fca2de1d121cb84c095946ef82e31a1f4804c'

mincheol로 만든 해시 값과 kimmincheol 로 만든 값입니다. 완전히 달라졌습니다. 제가 보기엔 완벽해 보입니다. 하지만 상대는 해커입니다. 해시 함수는 원본에 대해서 매번 같은 값을 생성합니다. 그렇기에 해커가 컴퓨터를 이용해 무한번에 가까운 횟수를 입력한다면 비밀번호를 알아낼 수 있습니다 !

Salting

Salt ? 소금 ?

네 맞습니다. 이제 원본 비밀번호에 소금을 치겠습니다. 여기서 소금은 임의의 문자열을 뜻합니다. 원본 비밀번호에 특정 문자들을 추가해서 해시함수를 거칩니다. 유저마다 다른 문자열을 사용한다면 해커가 비밀번호를 알아내기 어렵게 됩니다. (모든 비밀번호는 완벽하지 않다고 합니다. 우리는 해킹당하는 시간을 늦추는 것에 초점을 맞춰야 합니다.)

bcrypt

bcrypt 는 단방향 해시함수와 salting 을 제공해주는 강력한 암호 라이브러리 입니다.

그러면 salting 을 치고 hashing 해보겠습니다.

bcrypt.hashpw()

>>> import bcrypt
>>> password = b"mincheol"

>>> salt = bcrypt.gensalt()
>>> salt
b'$2b$12$7kZE/yu.FKL1G/Hid1Nbke'
>>> hash_password = bcrypt.hashpw(password, salt)
>>> hash_password
b'$2b$12$7kZE/yu.FKL1G/Hid1NbkeV56sIKYtbHLTSVerXcrqu84olvCN056'

hashpw 메서드는 인자로 bytes 타입과 salt를 가지게 됩니다. 이렇게 생성된 hash_password 에는 salt 가 들어가 있다는 것을 알 수 있습니다.

>>> salt2 = bcrypt.gensalt()
>>> salt2
b'$2b$12$4Rpwc1geHBLhvKvREv0YKO'
>>> hash_password2=bcrypt.hashpw(password, salt2)
>>> hash_password2
b'$2b$12$4Rpwc1geHBLhvKvREv0YKOBcCVKmpCg.wiUP3wZnexiNFuOvV5ake'

같은 password 를 사용했지만 , salt가 달라졌기에, 생성된 암호는 전혀 다른 값입니다.

bcrypt.checkpw()

>>> bcrypt.checkpw(b"mincheol", hash_password)
True
>>> bcrypt.checkpw(b"kimmincheol", hash_password)
False

이제정확히 어떤 로직인지는 모르지만, checkpw 메서드는 인자들을 비교해 같은지 확인해줍니다. 참고로 인자들은 모두 bytes 타입을 가져야합니다.

작성한 코드

class SignUpView(View):
	def post(self, request):
            data = json.loads(request.body)
			
            ...
            hashed_pw = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

	    ...
	    User.objects.filter(number=phone).update(
                ...
                password = hashed_pw,
                ...
            )

여기선 해싱 후 디코딩을 해줬습니다. 그 이유는 DB의 필드를 CharField 로 설정했기 때문입니다.

class SignInView(View):
	def post(self, request):
    		...
            
        	if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):
		...

checkpw 는 인자로 bytes 타입을 받기에, 인코딩을 해줍니다.

정리

유저가 입력한 비밀번호를 bcrypt 라이브러리를 이용해 암호화해줍니다. 이때 생성되는 암호는 각각 다른 salt 값을 가지고 있습니다. 그리고 이 salt 값은 생성된 암호에 포함됩니다. 마지막으로 암호의 확인은 checkpw 메서드를 이용해 맞는지, 틀린지 확인할 수 있습니다.

0개의 댓글