You: "I imported the legacy hashes with an algorithm prefix and wrote a custom password hasher.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | # ====================================================================== # Q: CTO: "How did you migrate users with their old passwords?" # # You: "I imported the legacy hashes with an algorithm prefix and wrote a custom # password hasher. Because the modern hasher is first in PASSWORD_HASHERS, # Django automatically rehashes on the first successful login. So no mass # password reset was needed, and over time the old hashes disappeared. Those who # never logged in received a reset email after six months." # ====================================================================== class LegacyASPHasher(BasePasswordHasher): algorithm = 'legacy_md5' # stored as legacy_md5$salt$hash def verify(self, password, encoded): _, salt, hash_ = encoded.split('$', 2) candidate = hashlib.md5((salt + password).encode()).hexdigest() return constant_time_compare(candidate, hash_) def safe_summary(self, encoded): return {'algorithm': self.algorithm} PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', # preferred -> rehash 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'accounts.hashers.LegacyASPHasher', ] |