SoFunction
Updated on 2025-03-01

Django password storage strategy analysis

1. Source code analysis

Version 1.4 released by Django contains some important security improvements. One of them is to use the PBKDF2 password encryption algorithm instead of SHA1. Another feature is that you can add your own password encryption method.

Django will use the first password encryption method you provide (at least one method in your file)

PASSWORD_HASHERS = [
  '.PBKDF2PasswordHasher',
  '.PBKDF2SHA1PasswordHasher',
  '.Argon2PasswordHasher',
  '.BCryptSHA256PasswordHasher',
  '',
]

Let’s take a look at the PBKDF2PasswordHasher encryption method.

class BasePasswordHasher(object):
  """
  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.
  """
  algorithm = None
  library = None
 
  def _load_library(self):
    if  is not None:
      if isinstance(, (tuple, list)):
        name, mod_path = 
      else:
        name = mod_path = 
      try:
        module = importlib.import_module(mod_path)
      except ImportError:
        raise ValueError("Couldn't load %s password algorithm "
                 "library" % name)
      return module
    raise ValueError("Hasher '%s' doesn't specify a library attribute" %
             self.__class__)
 
  def salt(self):
    """
    Generates a cryptographically secure nonce salt in ascii
    """
    return get_random_string()
 
  def verify(self, password, encoded):
    """
    Checks if the given password is correct
    """
    raise NotImplementedError()
 
  def encode(self, password, salt):
    """
    Creates an encoded database value
    The result is normally formatted as "algorithm$salt$hash" and
    must be fewer than 128 characters.
    """
    raise NotImplementedError()
 
  def safe_summary(self, encoded):
    """
    Returns a summary of safe values
    The result is a dictionary and will be used where the password field
    must be displayed to construct a safe representation of the password.
    """
    raise NotImplementedError()
 
 
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 = 36000
  digest = hashlib.sha256
 
  def encode(self, password, salt, iterations=None):
    assert password is not None
    assert salt and '$' not in salt
    if not iterations:
      iterations = 
    hash = pbkdf2(password, salt, iterations, digest=)
    hash = base64.b64encode(hash).decode('ascii').strip()
    return "%s$%d$%s$%s" % (, iterations, salt, hash)
 
  def verify(self, password, encoded):
    algorithm, iterations, salt, hash = ('$', 3)
    assert algorithm == 
    encoded_2 = (password, salt, int(iterations))
    return constant_time_compare(encoded, encoded_2)
 
  def safe_summary(self, encoded):
    algorithm, iterations, salt, hash = ('$', 3)
    assert algorithm == 
    return OrderedDict([
      (_('algorithm'), algorithm),
      (_('iterations'), iterations),
      (_('salt'), mask_hash(salt)),
      (_('hash'), mask_hash(hash)),
    ])
 
  def must_update(self, encoded):
    algorithm, iterations, salt, hash = ('$', 3)
    return int(iterations) != 
 
  def harden_runtime(self, password, encoded):
    algorithm, iterations, salt, hash = ('$', 3)
    extra_iterations =  - int(iterations)
    if extra_iterations > 0:
      (password, salt, extra_iterations)

As you can see, you must inherit from BasePasswordHasher and override the verification(), encode() and safe_summary() methods.

Django uses PBKDF 2 algorithm with 36,000 iterations to make it less easy to be easily compromised by brute force cracking. Passwords are stored in the following format:

algorithm$number of iterations$salt$password hash”

Example: pbkdf2_sha256$36000$Lx7auRCc8FUI$eG9lX66cKFTos9sEcihhiSCjI6uqbr9ZrO+Iq3H9xDU=

2. Custom password encryption method

1. Add a custom encryption algorithm to it:

PASSWORD_HASHERS = [
  '.MyMD5PasswordHasher', 
  '.PBKDF2PasswordHasher',
  '.PBKDF2SHA1PasswordHasher',
  '.Argon2PasswordHasher',
  '.BCryptSHA256PasswordHasher',
  '',
]

2. Let’s look at MyMD5PasswordHasher. This is my custom encryption method, which is the basic md5. Django’s MD5PasswordHasher is salted:

 from  import BasePasswordHasher,MD5PasswordHasher
 from  import mask_hash
 import hashlib
 
 class MyMD5PasswordHasher(MD5PasswordHasher):
   algorithm = "mymd5"
 
   def encode(self, password, salt):
     assert password is not None
     hash = hashlib.md5(password).hexdigest().upper()
     return hash
 
   def verify(self, password, encoded):
     encoded_2 = (password, '')
     return () == encoded_2.upper()
 
   def safe_summary(self, encoded):
     return OrderedDict([
         (_('algorithm'), algorithm),
         (_('salt'), ''),
         (_('hash'), mask_hash(hash)),
         ])

You can later see in the database that the password does use a custom encryption method.

3. Modify the authentication method

AUTHENTICATION_BACKENDS = (
  '', #New  '',
  '',
)

4. Let’s look at the customized authentication method

:

 import hashlib
 from pro import models
 from  import ModelBackend
 
 class MyBackend(ModelBackend):
   def authenticate(self, username=None, password=None):
     try:
       user = models.M_User.(username=username)
       print user
     except Exception:
       print 'no user'
       return None
     if hashlib.md5(password).hexdigest().upper() == :
       return user
     return None
 
   def get_user(self, user_id):
     try:
       return models.M_User.(id=user_id)
     except Exception:
       return None

Of course, after these modifications, the final security is much lower than that provided by django, but this is the requirement and must be met.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.