What is the format in which Django passwords are stored in the database?

Question:

You know how django passwords are stored like this:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

and that is the “hashtype $salt $hash”. My question is, how do they get the $hash? Is it the password and salt combined and then hashed or is something else entirely?

Asked By: Joe

||

Answers:

According to the docs:

Hashtype is either sha1 (default), md5 or crypt — the algorithm used to perform a one-way hash of the password. Salt is a random string used to salt the raw password to create the hash.

According to the code of set_password:

def set_password(self, raw_password):
    import random
    algo = 'sha1'
    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    hsh = get_hexdigest(algo, salt, raw_password)
    self.password = '%s$%s$%s' % (algo, salt, hsh)

As the documentation describes, the hash is the salt, algorithm, and password, hashed.

Answered By: Paolo Bergantino

As always, use the source:

# root/django/trunk/django/contrib/auth/models.py
# snip
def get_hexdigest(algorithm, salt, raw_password):
    """
    Returns a string of the hexdigest of the given plaintext password and salt
    using the given algorithm ('md5', 'sha1' or 'crypt').
    """
    raw_password, salt = smart_str(raw_password), smart_str(salt)
    if algorithm == 'crypt':
        try:
            import crypt
        except ImportError:
            raise ValueError('"crypt" password algorithm not supported in this environment')
        return crypt.crypt(raw_password, salt)

    if algorithm == 'md5':
        return md5_constructor(salt + raw_password).hexdigest()
    elif algorithm == 'sha1':
        return sha_constructor(salt + raw_password).hexdigest()
    raise ValueError("Got unknown password algorithm type in password.")

As we can see, the password digests are made by concatenating the salt with the password using the selected hashing algorithm. then the algorithm name, the original salt, and password hash are concatenated, separated by “$”s to form the digest.

# Also from root/django/trunk/django/contrib/auth/models.py
def check_password(raw_password, enc_password):
    """
    Returns a boolean of whether the raw_password was correct. Handles
    encryption formats behind the scenes.
    """
    algo, salt, hsh = enc_password.split('$')
    return hsh == get_hexdigest(algo, salt, raw_password)

To validate passwords django just verifies that the same salt and same password result in the same digest.

For a long time, until version 1.3, Django indeed followed the irresponsible practice of using a plain single iteration of SHA1, with a salt that was too short, to store password information. That approach has been out of date since 1979. Any passwords still stored that way are highly vulnerable to brute force attack. For reasons why, see Security Stackexchange on password hashing

Since version 1.4 in 2012, Django has a default hashing algorithm based on a good, standard key derivation function, PBKDF2, with a configurable number of iterations, whose default increases with each release (20000 in version 1.7). It also provides bcrypt support, and is backwards compatible with earlier releases, automatically upgrading password hashes when users log in. See more at Password management in Django | Django documentation

Answered By: nealmcb
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.