Django 프로젝트를 생성하면 settings.py에는SECRET_KEY
와 이와 관련된 보안 경고 주석이 있다
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '<your secret key>'
staging 환경을 새로 구성하면서 보안 조치를 위해서 기존 환경과 다른 SECRET_KEY
값을 넣어주었고, 그에 따라 기존 DB를 복사해서 넣었을 때 예전 패스워드를 사용하지 못할 것으로 예상했습니다.
하지만 그렇지 않았습니다. 기존 패스워드를 입력해도 로그인이 진행 되었다.
그렇다면 SECRET_KEY는 패스워드 생성과 관계가 없는것일까? 가 궁금해져서 포스팅을 하게 되었습니다.
공식문서 의 설명에 따르면 SECRET_KEY 는
에서 사용한다고 합니다.
역시.. Password를 생성하는데 사용한다고 적혀 있지 않습니다.
공식문서에 따르면 장고의 패스워드는 기본적으로 PBKDF2 알고리즘을 사용하고
(NIST의 권장사항)
<algorithm>$<iterations>$<salt>$<hash>
위의 형태와 같이 저장된다고 합니다.
User를 생성해서 DB의 User 테이블에 저장된 password
를 보면
pbkdf2_sha256$320000$mP81so9CdBlV6R52gXDgHS$RKEHamBwjVjmpqdkvQf1tGu/mntLnfMl3y9u4bYE3m4=
이렇게 생겼습니다.
pbkdf2_sha256 알고리즘을 사용했고 320000 번 iteration 해서 패스워드를 생성했습니다. 별 다른 설정 없이도 NIST의 권장사항을 잘 준수했습니다.
다시한번 공식 문서를 보면
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.ScryptPasswordHasher',
]
알고리즘을 생성하는 Hasher
는 이렇게 기본 설정이 되어 있고, 설정 값의 처음에 적혀 있는 것을 사용한다고 합니다. 여기서는 PBKDF2PasswordHasher
를 사용하게 됩니다.
그래서 PBKDF2PasswordHasher
의 코드를 보면
class PBKDF2PasswordHasher(BasePasswordHasher):
"""
Secure password hashing using the PBKDF2 algorithm (recommended)
Configured to use PBKDF2 + HMAC + SHA256.
The result is a 64 byte binary string. Iterations may be changed
safely but you must rename the algorithm if you change SHA256.
"""
algorithm = "pbkdf2_sha256"
iterations = 320000
digest = hashlib.sha256
def encode(self, password, salt, iterations=None):
self._check_encode_args(password, salt)
iterations = iterations or self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
hash = base64.b64encode(hash).decode("ascii").strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
iterations
의 기본값은 32000 번이고, algorithm
은 pbkdf2_sha256 입니다.
hash
는 password, salt, iterations, digest를 이용해서 생성합니다.
그리고 salt
는 PBKDF2PasswordHasher
에 정의되어 있지 않아서 부모 클래스인 BasePasswordHasher
를 보면
class BasePasswordHasher:
"""
Abstract base class for password hashers
When creating your own hasher, you need to override algorithm,
verify(), encode() and safe_summary().
PasswordHasher objects are immutable.
"""
...
def salt(self):
"""
Generate a cryptographically secure nonce salt in ASCII with an entropy
of at least `salt_entropy` bits.
"""
# Each character in the salt provides
# log_2(len(alphabet)) bits of entropy.
char_count = math.ceil(self.salt_entropy / math.log2(len(RANDOM_STRING_CHARS)))
return get_random_string(char_count, allowed_chars=RANDOM_STRING_CHARS)
랜덤 스트링 이라는것을 있습니다.
따라서 SECRET_KEY
와 관련있는 부분은 전혀 없음을 알 수 있습니다.
SECRET_KEY
의 값을 바꾸어도 기존의 패스워드를 그대로 사용할 수 있습니다.
PASSWORD_HASHERS
를 재정의하지 않는다면 Django의 버전을 바꾸어도, 프로젝트 명을 바꾸어도, repository를 바꾸어도 기존의 패스워드를 그대로 사용할 수 있습니다.