django-hashers-passlib aims to make password hashing schemes provided by
passlib usable in
Django. Unlike passlibs
passlib.ext.django,
it does not replace Djangos password management
system but
provides standard hashers that can be added to the PASSWORD_HASHERS setting
for hash schemes provided by passlib.
There are two primary usecases for this module:
- You want to import password hashes from an existing application into your Django database.
- You want to export password hashes to a different application in the future.
This module is available via pip, install it with
pip install django-hashers-passlib
It requires Django >= 1.5 (1.4 should work) and passlib >= 1.6.2. It supports Python versions 2.7 and 3.2 or later.
This module supports almost every hash supported by passlib (some must be
converted at first - see below), but hashes must be slightly modified in order
to fit into Djangos hash encoding scheme (see "How it works interally" below
for details). Every hasher class is named like the module provided by passlib
and every hash has a from_orig() and to_orig() method, which allows to
import/export hashes. So importing a user from a different system is simply a
matter of calling from_orig() of the right hasher and save that to the
password field of Djangos User model. Here is a simple example:
# Lets import a phpass (WordPress, phpBB3, ...) hash. This assumes that you
# have 'hashers_passlib.phpass' in your PASSWORD_HASHERS setting.
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import get_hasher
User = get_user_model() # get any custom user model
hasher = get_hasher('phpass')
# you got this from i.e. a WordPress database:
raw_hashes = {
'joe': '$P$EnOjUf5ie1AeWMHpw1dqHUQYHAIBe41',
'jane': '$P$E6UROQJscRzZ3ve2hoIFZ1OcjBA1W10',
}
for username, hash in raw_hashes.items():
user = User.objects.create(username=username)
user.password = hasher.from_orig(hash)
user.save()The users "joe" and "jane" can now login with their old usernames and passwords. If you want to export users with a phpass hash to a WordPress database again, you can simple get the original hashes back (for simplicity, we just print everything to stdout here):
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import get_hasher
User = get_user_model() # get any custom user model
hasher = get_hasher('phpass')
for user in User.objects.filter(password__startswith='phpass$'):
orig_hash = hasher.to_orig(user.password)
print('%s has hash "%s"' % (user.username, orig_hash))This module provides hashers for most hash schemes provided by passlib - but
remember you have to import them using the hashers from_orig() method first
to be useable. Some have to be be converted first (see below), and only a few
minor old hashes are not supported. All password hashers have the same class
name as the passlib hasher they wrap and are located in the hashers_passlib
module. So to enable support for e.g. sha1_crypt hashes, add
hashers_passlib.sha1_crypt to your PASSWORD_HASHERS Django setting.
WARNING: Some hashes are longer then the 128 characters provided by the
standard User model provided by Django. You have to specify a custom user
model
with at least 256 characters for hex_sha512, pbkdf2_sha512, scram and
sha512_crypt or at least 384 characters for grub_pbkdf2_sha512.
The following algorithms are supported: des_crypt, bsdi_crypt, bigcrypt, crypt16, md5_crypt, sha1_crypt, sun_md5_crypt, sha256_crypt, sha512_crypt, apr_md5_crypt, bcrypt_sha256, phpass, pbkdf2_<digest>, dlitz_pbkdf2_sha1, cta_pbkdf2_sha1, scram, ldap_salted_md5, ldap_salted_sha1, atlassian_pbkdf2_sha1, fshp, mssql2000, mssql2005, mysql323, mysql41, oracle11, lmhash, nthash, cisco_pix, cisco_type7, grub_pbkdf2_sha512 and hex_{md4,sha256,sha512}.
Most hashes will be saved with a simple prefix <algorithm>$, where
"<algorithm>" is the name of the hasher. The only exception are a few
hashes (apr_md5_crypt, bcrypt_sha256, pbkdf2_<digest>, scram) that
already almost fit into Djangos hash scheme, where only the leading $ is
stripped.
NOTE: Some hashes (bcrypt_sha256, pbkdf2_<digest>, ...) look very
similar to what Django provides but are actually distinct algorithms.
Some hash schemes really are just a minor transformation of a different hash
scheme. For example, the
bsd_nthash
is just a regular
nthash
with $3$$ prepended and the
ldap_md5
has is just a plain MD5 hash with {MD5} prepended that is already supported
by Django.
In order to avoid code duplication, this module does not provide password
hashers for these schemes, but converters under hashers_passlib.converters.
Converted hashes are either readable by a different hasher or by a hasher
provided by Django.
If you want to import bsd_nthash hashes, you can either manually strip the
identifier or use the converter:
# Lets import bsd_nthash hashes as plain nthash hashes. This assumes you have
# have 'hashers_passlib.nthash' in your PASSWORD_HASHERS setting.
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import get_hasher
from hashers_passlib.converters import bsd_nthash
conv = bsd_nthash()
raw_hashes = {
'joe': '$3$$baac3929fabc9e6dcd32421ba94a84d4',
}
for username, hash in raw_hashes.items():
user = User.objects.create(username=username)
# convert bsd_nthash to plain nthash:
user.password = converter.from_orig(hash)
user.save()The following converters are available under hashers_passlib.converters, they
can be used to convert from and to the original scheme:
| From | To | Notes |
|---|---|---|
| bcrypt | BCryptPasswordHasher |
Converted to bcrypt hash supported by the stock Django hasher. |
| bsd_nthash | nthash |
Convert from bsd_nthash to nthash and vice versa. |
| ldap_md5 | UnsaltedMD5PasswordHasher |
Converted to plain MD5 hash supported by Django. |
| ldap_sha1 | UnsaltedSHA1PasswordHasher |
Converted to plain SHA1 hash supported by Django. |
| ldap_hex_md5 | UnsaltedMD5PasswordHasher |
Converted to plain MD5 hash supported by Django. |
| ldap_hex_sha1 | UnsaltedSHA1PasswordHasher |
Converted to plain SHA1 hash supported by Django. |
| ldap_{crypt} | various | Converted to their non-LDAP pendants (i.e. ldap_des_crypt is converted to a plain des_crypt hash). |
| ldap_bcrypt | BCryptPasswordHasher |
Unlike other ldap_{crypt} schemes, ldap_bcrypt hashes are converted to what Djangos stock BCrypt hashser understands. |
| ldap_pbkdf2_{digest} | pbkdf2_{digest} |
Converted to their non-LDAP pendants. |
Some hashes are unsupported because they require the username to generate the salt: postgres_md5, oracle10, msdcc, msdcc2
Djangos password management system stores passwords in a format that is very similar but still distinct from what passlib calls Modular Crypt Format:
<algorithm>$<content>
... where "<algorithm>" is the identifier used to select what hasher
class should handle the hash. The only difference to the Modular Crypt Format
is that it misses the leading $ sign. Note that the $ in the middle is a
mandatory delimiter.
This module modifies the hash schemes so they fit into this scheme before storing them in the database. The modifications are absolutely reversible - in fact this module depends on it being reversible, our hashers won't work any other way. Depending on the original hash scheme, the hashes are modified in one of several ways:
- Some old and insecure hashes require the username to encode the hash. Djangos hashers don't receive the username, so they are not compatible and not supported by this module.
- Some of passlibs hashes are already supported by Django and the functionality is not duplicated here.
- Some hash schemes are really just minor modifications of different schemes, we provide converters in this case.
- A few hashes already almost fit in Djangos scheme and have a reasonably
unique identifier, they just have the leading
$stripped. - All other hashes (which is the vast majority!) just have `$ prepended. This is the same approach as what Django does with e.g. bcrypt hashes.