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.