- Author:
- pberndt
- Posted:
- February 22, 2014
- Language:
- Python
- Version:
- Not specified
- Score:
- 0 (after 0 ratings)
Use this class to authenticate against Drupal 7 password strings. When importing passwords from Drupal, the database values should be prefixed with "drupal$".
In contrast to the two present solutions, this class also works with passwords which were imported to Drupal 7 (e.g. from Drupal 6 or phpBB) and with passwords with variable iteration numbers. It is possible to use this class for encoding passwords, but due to questionable design decisions in Drupal (like truncating the non-standard base64 encoded hash at 43 characters) I'd recommend to do this this only if you really need to be able to migrate back to Drupal.
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import get_random_string
from collections import OrderedDict
from django.utils.translation import ugettext_noop as _
import hashlib
class DrupalPasswordHasherInvalidHashException(Exception):
pass
class DrupalPasswordHasher(BasePasswordHasher):
"""
Authenticate against Drupal 7 passwords.
The passwords should be prefixed with drupal$ upon importing, such that
Django recognizes them correctly. Drupal's method does some funny stuff
(like truncating the hashed password), so you might not want to use this
hasher for storing new passwords.
"""
algorithm = "drupal"
_DRUPAL_HASH_LENGTH = 55
_DRUPAL_HASH_COUNT = 15
_digests = {
'$S$': hashlib.sha512,
'$H$': hashlib.md5,
'$P$': hashlib.md5,
}
_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
def _get_settings(self, encoded):
settings_bin = encoded[:12]
count_log2 = self._itoa64.index(settings_bin[3])
count = 1 << count_log2
salt = settings_bin[4:12]
return {
'count': count,
'salt': salt
}
def _drupal_b64(self, input):
output = ""
count = len(input)
i = 0
while True:
value = ord(input[i])
i += 1
output += self._itoa64[value & 0x3f]
if i < count:
value |= ord(input[i]) << 8
output += self._itoa64[(value >> 6) & 0x3f]
i += 1
if i >= count:
break
if i < count:
value |= ord(input[i]) << 16
output += self._itoa64[(value >> 12) & 0x3f]
i += 1
if i >= count:
break
output += self._itoa64[(value >> 18) & 0x3f]
return output
def _apply_hash(self, password, digest, settings):
password_hash = digest(settings["salt"] + password).digest()
for i in range(settings["count"]):
password_hash = digest(password_hash + password).digest()
return self._drupal_b64(password_hash)[:self._DRUPAL_HASH_LENGTH - 12]
def salt(self):
return get_random_string(8)
def encode(self, password, salt):
assert len(salt) == 8
digest = '$S$'
settings = {
'count': 1 << self._DRUPAL_HASH_COUNT,
'salt': salt
}
encoded_hash = self._apply_hash(password, self._digests[digest], settings)
return self.algorithm + "$" + digest + self._itoa64[self._DRUPAL_HASH_COUNT] + salt + encoded_hash
def verify(self, password, encoded):
encoded = encoded.split("$", 1)[1]
if encoded[0] == 'U':
# Imported passwords from old Drupal versions, see user_update_7000()
encoded = encoded[1:]
password = hashlib.md5(password).hexdigest()
digest = encoded[:3]
if digest not in self._digests:
raise DrupalPasswordHasherInvalidHashException()
digest = self._digests[digest]
settings = self._get_settings(encoded)
encoded_hash = encoded[12:]
password_hash = self._apply_hash(password, digest, settings)
return password_hash == encoded_hash
def safe_summary(self, encoded):
encoded = encoded.split("$", 1)[1]
settings = self._get_settings(encoded)
return OrderedDict([
(_('algorithm'), self.algorithm),
(_('iterations'), settings["count"]),
(_('salt'), settings["salt"]),
(_('hash'), encoded[12:]),
])
def must_update(self, encoded):
encoded = encoded.split("$", 1)[1]
if encoded[0] == 'U':
return True
settings = self._get_settings(encoded)
return settings["count"] < (1 << self._DRUPAL_HASH_COUNT)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Thanks a lot for posting this - massive help to everyone migrating from Drupal :)
Just one issue, in for loop in _apply_hash function I had to add the following:
password = password.decode().encode('utf-8')
#
I really need this snippet but as a newbie to django have no clue how to implement it. I appreciate if you add a brief how-to. Thanks.
#
Please login first before commenting.