2011-06-27 00:51:46 +08:00
|
|
|
import hashlib
|
|
|
|
from django.utils.encoding import smart_str
|
|
|
|
from django.utils.crypto import constant_time_compare
|
|
|
|
|
|
|
|
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
|
|
|
|
|
|
|
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 hashlib.md5(salt + raw_password).hexdigest()
|
|
|
|
elif algorithm == 'sha1':
|
|
|
|
return hashlib.sha1(salt + raw_password).hexdigest()
|
|
|
|
raise ValueError("Got unknown password algorithm type in password.")
|
|
|
|
|
|
|
|
def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
|
|
|
|
"""
|
|
|
|
Returns a random string of length characters from the set of a-z, A-Z, 0-9
|
|
|
|
for use as a salt.
|
|
|
|
|
|
|
|
The default length of 12 with the a-z, A-Z, 0-9 character set returns
|
|
|
|
a 71-bit salt. log_2((26+26+10)^12) =~ 71 bits
|
|
|
|
"""
|
|
|
|
import random
|
|
|
|
try:
|
|
|
|
random = random.SystemRandom()
|
|
|
|
except NotImplementedError:
|
|
|
|
pass
|
|
|
|
return ''.join([random.choice(allowed_chars) for i in range(length)])
|
|
|
|
|
|
|
|
def check_password(raw_password, enc_password):
|
|
|
|
"""
|
|
|
|
Returns a boolean of whether the raw_password was correct. Handles
|
2011-10-19 04:28:52 +08:00
|
|
|
hashing formats behind the scenes.
|
2011-06-27 00:51:46 +08:00
|
|
|
"""
|
|
|
|
parts = enc_password.split('$')
|
|
|
|
if len(parts) != 3:
|
|
|
|
return False
|
|
|
|
algo, salt, hsh = parts
|
|
|
|
return constant_time_compare(hsh, get_hexdigest(algo, salt, raw_password))
|
|
|
|
|
|
|
|
def is_password_usable(encoded_password):
|
|
|
|
return encoded_password is not None and encoded_password != UNUSABLE_PASSWORD
|
|
|
|
|
|
|
|
def make_password(algo, raw_password):
|
|
|
|
"""
|
|
|
|
Produce a new password string in this format: algorithm$salt$hash
|
|
|
|
"""
|
|
|
|
if raw_password is None:
|
|
|
|
return UNUSABLE_PASSWORD
|
|
|
|
salt = get_random_string()
|
|
|
|
hsh = get_hexdigest(algo, salt, raw_password)
|
|
|
|
return '%s$%s$%s' % (algo, salt, hsh)
|