Hi everyone - I’m working on a project to update a legacy site and migrate it to a new platform. It was using PHP, and I’m working on switching over to Django.
As part of the project, I want to update the password hashing. The legacy site had two password hashers - a bcrypt hasher built-in to PHP and an older custom-built hmac hasher.
The bcrypt one isn’t too hard, but I also need to be able to support the older hmac hashing. I just need to use the custom hasher to verify when a password is correct and then I’ll let Django update it to the PBKDF2.
My questions are about how to implement the custom password hasher. Let’s call the new password hasher LegacyPasswordHasher. It looks like I need to implement the following (leaving out the implementation details of the hashing algorithm since it’s not really relevant to the discussion):
class LegacyPasswordHasher(BasePasswordHasher): algorithm = 'legacy_hasher' def encode(self, password: str, salt: str) -> Any: # Implementation details go here def verify(self, password: str, encoded: str) -> bool: # Implementation details go here def safe_summary(self, encoded: str) -> Any: # Implementation details go here
And then I need to add this to settings.py:
# Password hashing 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.BCryptPasswordHasher', 'mysite.hashers.LegacyPasswordHasher' ]
And now, my questions:
How does Django select which password hasher to use? Does it just try all of the password hashers in the list and fail if
verify() returns false for all of them? Or does it have some criterion it uses for selecting a specific hasher from the list?
When verifying a login attempt, what is Django’s order of operations for a hasher? For example, does it just call
verify, or does it call
safe_summary and then
verify, or something else?
Do I need to implement all three functions in my custom hasher if I’m only planning on verifying? I don’t want to save passwords using the legacy hasher, I just want to be able to log users in and then hash their password using PBKDF2.
Do I need to add any data to the hash stored in the database to indicate that it’s hashed using the legacy algorithm? An example hash may look like this:
ce7a3ced60ceb06e746665fd5d22a2a0dfa187ec - should this be stored in the auth_user.password, or does it need some additional decorations to help Django identify which hasher to use? According to this documentation, a hash is normally stored in this format:
<algorithm>$<iterations>$<salt>$<hash> - but I don’t know if this is required or just a convention.
Thanks in advance, I can provide further details if needed.