117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
import hashlib
|
|
import base64
|
|
import math
|
|
|
|
from django.contrib.auth import hashers
|
|
from django.utils.crypto import constant_time_compare
|
|
from django.utils.datastructures import SortedDict
|
|
from django.utils.translation import ugettext_noop as _
|
|
from django.utils.encoding import force_bytes
|
|
|
|
class Drupal7PasswordHasher(hashers.BasePasswordHasher):
|
|
"""
|
|
Secure password hashing using the algorithm used by Drupal 7 (recommended)
|
|
"""
|
|
algorithm = "drupal7_sha512"
|
|
iterations = 10000
|
|
digest = hashlib.sha512
|
|
alphabet = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
|
|
def atoi64(self, v):
|
|
return self.alphabet.find(v)
|
|
|
|
def i64toa(self, v):
|
|
return self.alphabet[v]
|
|
|
|
def b64encode(self, v):
|
|
out = ''
|
|
count = len(v)
|
|
i = 0
|
|
while i < count:
|
|
value = ord(v[i])
|
|
i += 1
|
|
out += self.i64toa(value & 0x3f)
|
|
if i < count:
|
|
value |= ord(v[i]) << 8
|
|
out += self.i64toa((value >> 6) & 0x3f)
|
|
if i == count:
|
|
break
|
|
i += 1
|
|
if i < count:
|
|
value |= ord(v[i]) << 16
|
|
out += self.i64toa((value >> 12) & 0x3f)
|
|
if i == count:
|
|
break
|
|
i += 1
|
|
out += self.i64toa((value >> 18) & 0x3f)
|
|
return out
|
|
|
|
def from_drupal(self, encoded):
|
|
ident, log_count, salt, h = encoded[:3], encoded[3], encoded[4:12], encoded[12:]
|
|
if ident != '$S$':
|
|
raise ValueError('Not a Drupal7 SHA-512 hashed password')
|
|
count = 1 << self.atoi64(log_count)
|
|
return '%s$%s$%s$%s' % (self.algorithm, count, salt, h)
|
|
|
|
def to_drupal(self, encoded):
|
|
algo, count, salt, h = encoded.split('$', 3)
|
|
count = self.atoi64(math.ceil(math.log(count, 2)))
|
|
return '$S$%s%s%s' % (count, salt, h)
|
|
|
|
def encode(self, password, salt, iterations):
|
|
assert password
|
|
assert salt and '$' not in salt
|
|
h = salt
|
|
password = force_bytes(password)
|
|
for i in xrange(iterations+1):
|
|
h = self.digest(h + password).digest()
|
|
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, self.b64encode(h)[:43])
|
|
|
|
def verify(self, password, encoded):
|
|
algorithm, iterations, salt, hash = encoded.split('$', 3)
|
|
assert algorithm == self.algorithm
|
|
encoded_2 = self.encode(password, salt, int(iterations))
|
|
return constant_time_compare(encoded, encoded_2)
|
|
|
|
def safe_summary(self, encoded):
|
|
algorithm, iterations, salt, hash = encoded.split('$', 3)
|
|
assert algorithm == self.algorithm
|
|
return SortedDict([
|
|
(_('algorithm'), algorithm),
|
|
(_('iterations'), iterations),
|
|
(_('salt'), hashers.mask_hash(salt)),
|
|
(_('hash'), hashers.mask_hash(hash)),
|
|
])
|
|
|
|
class CommonPasswordHasher(hashers.BasePasswordHasher):
|
|
"""
|
|
The Salted MD5 password hashing algorithm (not recommended)
|
|
"""
|
|
algorithm = None
|
|
digest = None
|
|
|
|
def encode(self, password, salt):
|
|
assert password
|
|
assert '$' not in salt
|
|
hash = self.digest(force_bytes(salt + password)).hexdigest()
|
|
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
|
|
|
def verify(self, password, encoded):
|
|
algorithm, salt, hash = encoded.split('$', 2)
|
|
assert algorithm == self.algorithm
|
|
encoded_2 = self.encode(password, salt)
|
|
return constant_time_compare(encoded, encoded_2)
|
|
|
|
def safe_summary(self, encoded):
|
|
algorithm, salt, hash = encoded.split('$', 2)
|
|
assert algorithm == self.algorithm
|
|
return SortedDict([
|
|
(_('algorithm'), algorithm),
|
|
(_('salt'), hashers.mask_hash(salt, show=2)),
|
|
(_('hash'), hashers.mask_hash(hash)),
|
|
])
|
|
|
|
class SHA256PasswordHasher(CommonPasswordHasher):
|
|
algorithm = 'sha256'
|
|
digest = hashlib.sha256
|