FastAPI 비밀번호 해싱하기(feat.argon2)

프로필 사진mingke

FastAPI Password Hasing with argon2

목차

FastAPI 비밀번호 해싱

너무나 당연하게도 유저의 비밀번호를 저장할 때 평문으로 저장하면 안됩니다. FastAPI 공식문서를 보면 비밀번호를 해싱할 때 passlib[bcrypt] 를 사용합니다. 저도 당연히 그걸 사용해서 코드를 작성했습니다. 최근에 새로운 프로젝트를 하던 중 아래와 유저가 회원가입을 할 때 아래와 같은 에러가 발생했습니다.

💡
AttributeError: module 'bcrypt' has no attribute '__about__'

AttributeError: module 'bcrypt' has no attribute '__about__' 을 그대로 검색해보니 최신 버젼에서 발생하는 에러이고 버젼을 낮추면 된다고 합니다. passlib 1.7.4 를 사용했었습니다. bcrpyt 를 의존하는데 4.1 버젼 이후부터 발생하는 것 같습니다.

버젼을 낮추는 대신 다른 해싱 방법을 적용하였습니다. 바로 argon2 를 사용하는 것으로 해싱 방법을 바꿔 봤습니다.

Argon2

Argon2는 2015년에 Password Hashing Competition에서 우승한 현대적인 해싱 알고리즘입니다. 2021년에는 IETF가 안전한 해싱 알고리즘 표준으로 지정하기도 했습니다. 현대적인 하드웨어인 GPU나 ASIC를 이용한 공격에도 잘 저항하도록 설계되어 안전하다고 합니다.

저는 암호학에 대해 아무것도 모르는데, 아무튼 우리가 흔하게 사용하는 bcrypt 및 PBKDF2 같은 기존 해싱 알고리즘 보다 보안 수준이 높고 안전하다고 합니다.

Loading...

Python Argon2 라이브러리 사용하기

위에 첨부한 링크에서도 보면 Argon2 설명을 읽을 필요도 없고 이해할 필요도 없다고 말합니다. 그냥 argon2.PasswordHasher 를 사용하면 된다고 합니다.

사용법은 아주 쉽습니다.

  • 설치
pip install argon2-cffi

설치 후에 기존에 사용하던 코드를 변경해주었습니다.

# 기존 코드
from passlib.context import CryptContext
 
class PasswordService:
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 
    @classmethod
    def verify_password(cls, plain_password, hashed_password):
        return cls.pwd_context.verify(plain_password, hashed_password)
 
    @classmethod
    def get_password_hash(cls, password):
        return cls.pwd_context.hash(password)
 
# 변경된 코드
from argon2 import PasswordHasher
 
class PasswordService:
    ph = PasswordHasher()
 
    @classmethod
    def verify_password(cls, plain_password, hashed_password):
        return cls.ph.verify(hashed_password, plain_password)
 
    @classmethod
    def get_password_hash(cls, password):
        return cls.ph.hash(password)

단순하게 PasswordHasher 객체로 바꿔준 것 밖에 없습니다.

위 코드를 사용 후 비밀번호가 저장된 결과를 비교해 보니 다음과 같습니다.

bcryt
$2b$12$virQMLThNkwhQ4Zzp.0zSeYcqWI8iqVaZu8DRZh0JIr6D1vUMDoba

argon2
$argon2id$v=19$m=65536,t=3,p=4$3ObSfJsIJbfNY9ksrgGpvA$rYe0w2sj0TKQzeOu5fmN/RPLmHxPxFpF3v0irHibcII

보다시피 사용하기 아주 쉽습니다. 하지만 verify 부분에서 bcrypt와 다른 점이 있습니다.

bcrypt는 검증에 실패하면 False 를 반환하지만 argon2는 VerifyMismatchError 에러를 발생시키기 때문에 예외처리를 해주어야 합니다. 안 그러면 FastAPI에서 500에러를 발생시킵니다.

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError # 추가
 
class PasswordService:
    ph = PasswordHasher()
 
    @classmethod
    def verify_password(cls, plain_password, hashed_password):
        # try except에 VerifyMismatchError 추가
        try:
            return cls.ph.verify(hashed_password, plain_password)
        except VerifyMismatchError:
            return False
 
    @classmethod
    def get_password_hash(cls, password):
        return cls.ph.hash(password)

이렇게 변경해주면 500를 발생시키지 않을 수 있습니다.

마무리

이번 포스팅에서 bcrypt말고 agron2 해싱 방법을 알아봤습니다. 사용 방법은 기존 방법들과 거의 다르지 않습니다. agron2를 이용해서 좀 더 높은 수준의 보안을 가진 비밀번호 해싱을 사용해 보는 것도 좋겠습니다.

Loading...