Note for check_password in Django
Background
We are refactoring our project from Django 1.9.10 to a new project with Django 1.11.14 and asyncpg for database connections.
I tried to fork the logic of User.check_password and create an async one. But the new one does not work as my wrong exception. But I learn something from that at least.
Different behavior of User.check_password between objects from fixture and created manual
I found when I use the official check_password on a user object from fixtures, the value of the object changed. But when I execute it on which I created recently. It did! I was confused about the difference.
code in django/contrib/auth/base_user
1 | from django.contrib.auth.hashers import ( |
code in django/contrib/auth/hasher.py
1 | def check_password(password, encoded, setter=None, preferred='default'): |
We can find when executed check_password of User object. The setter will be called when the password is_correct and must_update. And I find the must_update is False when checking the one I creatd but True in fixtures.
Why must_update?
The variable preferred with attribute must_update is declared by get_hasher and we can find it is an instance of PBKDF2PasswordHasher as default. So the dependence of updating is the iterations in decoded password.
1 | class PBKDF2PasswordHasher(BasePasswordHasher): |
I printed the decoded password and found the iterations in fixtures is 24000 and was changed into 36000 after check_password. And here is the truth that the iterations of PBKDF2PasswordHasher in version 1.9.10 of Django is 24000 and is changed into 36000 in the version 1.11.14. But of couse it will not make any trouble when check_password in the new version, just changing the decoded password of user object.
snippet of `PBKDF2PasswordHasher` in Django 1.9.10
1
2
3
4
5
6
7
8
9
10
11
12
13class 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 = 24000
digest = hashlib.sha256
# ...
snippet of `PBKDF2PasswordHasher` in Django 1.11.14
1
2
3
4
5
6
7
8
9
10
11
12
13class 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 = 36000
digest = hashlib.sha256
# ...
After figuring out the cause of changing in password, I found the tip in the section Password upgrading in Django documentation.
Hashed passwords will be updated when increasing (or decreasing) the number of PBKDF2 iterations or bcrypt rounds.