summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/contrib/auth/hashers.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/auth/hashers.py')
-rw-r--r--lib/python2.7/site-packages/django/contrib/auth/hashers.py515
1 files changed, 0 insertions, 515 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/auth/hashers.py b/lib/python2.7/site-packages/django/contrib/auth/hashers.py
deleted file mode 100644
index d285126..0000000
--- a/lib/python2.7/site-packages/django/contrib/auth/hashers.py
+++ /dev/null
@@ -1,515 +0,0 @@
-from __future__ import unicode_literals
-
-import base64
-import binascii
-import hashlib
-
-from django.dispatch import receiver
-from django.conf import settings
-from django.test.signals import setting_changed
-from django.utils import importlib
-from django.utils.datastructures import SortedDict
-from django.utils.encoding import force_bytes, force_str, force_text
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.crypto import (
- pbkdf2, constant_time_compare, get_random_string)
-from django.utils.module_loading import import_by_path
-from django.utils.translation import ugettext_noop as _
-
-
-UNUSABLE_PASSWORD_PREFIX = '!' # This will never be a valid encoded hash
-UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40 # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
-HASHERS = None # lazily loaded from PASSWORD_HASHERS
-PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
-
-
-@receiver(setting_changed)
-def reset_hashers(**kwargs):
- if kwargs['setting'] == 'PASSWORD_HASHERS':
- global HASHERS, PREFERRED_HASHER
- HASHERS = None
- PREFERRED_HASHER = None
-
-
-def is_password_usable(encoded):
- if encoded is None or encoded.startswith(UNUSABLE_PASSWORD_PREFIX):
- return False
- try:
- identify_hasher(encoded)
- except ValueError:
- return False
- return True
-
-
-def check_password(password, encoded, setter=None, preferred='default'):
- """
- Returns a boolean of whether the raw password matches the three
- part encoded digest.
-
- If setter is specified, it'll be called when you need to
- regenerate the password.
- """
- if password is None or not is_password_usable(encoded):
- return False
-
- preferred = get_hasher(preferred)
- hasher = identify_hasher(encoded)
-
- must_update = hasher.algorithm != preferred.algorithm
- if not must_update:
- must_update = preferred.must_update(encoded)
- is_correct = hasher.verify(password, encoded)
- if setter and is_correct and must_update:
- setter(password)
- return is_correct
-
-
-def make_password(password, salt=None, hasher='default'):
- """
- Turn a plain-text password into a hash for database storage
-
- Same as encode() but generates a new random salt.
- If password is None then a concatenation of
- UNUSABLE_PASSWORD_PREFIX and a random string will be returned
- which disallows logins. Additional random string reduces chances
- of gaining access to staff or superuser accounts.
- See ticket #20079 for more info.
- """
- if password is None:
- return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
- hasher = get_hasher(hasher)
-
- if not salt:
- salt = hasher.salt()
-
- return hasher.encode(password, salt)
-
-
-def load_hashers(password_hashers=None):
- global HASHERS
- global PREFERRED_HASHER
- hashers = []
- if not password_hashers:
- password_hashers = settings.PASSWORD_HASHERS
- for backend in password_hashers:
- hasher = import_by_path(backend)()
- if not getattr(hasher, 'algorithm'):
- raise ImproperlyConfigured("hasher doesn't specify an "
- "algorithm name: %s" % backend)
- hashers.append(hasher)
- HASHERS = dict([(hasher.algorithm, hasher) for hasher in hashers])
- PREFERRED_HASHER = hashers[0]
-
-
-def get_hasher(algorithm='default'):
- """
- Returns an instance of a loaded password hasher.
-
- If algorithm is 'default', the default hasher will be returned.
- This function will also lazy import hashers specified in your
- settings file if needed.
- """
- if hasattr(algorithm, 'algorithm'):
- return algorithm
-
- elif algorithm == 'default':
- if PREFERRED_HASHER is None:
- load_hashers()
- return PREFERRED_HASHER
- else:
- if HASHERS is None:
- load_hashers()
- if algorithm not in HASHERS:
- raise ValueError("Unknown password hashing algorithm '%s'. "
- "Did you specify it in the PASSWORD_HASHERS "
- "setting?" % algorithm)
- return HASHERS[algorithm]
-
-
-def identify_hasher(encoded):
- """
- Returns an instance of a loaded password hasher.
-
- Identifies hasher algorithm by examining encoded hash, and calls
- get_hasher() to return hasher. Raises ValueError if
- algorithm cannot be identified, or if hasher is not loaded.
- """
- # Ancient versions of Django created plain MD5 passwords and accepted
- # MD5 passwords with an empty salt.
- if ((len(encoded) == 32 and '$' not in encoded) or
- (len(encoded) == 37 and encoded.startswith('md5$$'))):
- algorithm = 'unsalted_md5'
- # Ancient versions of Django accepted SHA1 passwords with an empty salt.
- elif len(encoded) == 46 and encoded.startswith('sha1$$'):
- algorithm = 'unsalted_sha1'
- else:
- algorithm = encoded.split('$', 1)[0]
- return get_hasher(algorithm)
-
-
-def mask_hash(hash, show=6, char="*"):
- """
- Returns the given hash, with only the first ``show`` number shown. The
- rest are masked with ``char`` for security reasons.
- """
- masked = hash[:show]
- masked += char * len(hash[show:])
- return masked
-
-
-class BasePasswordHasher(object):
- """
- Abstract base class for password hashers
-
- When creating your own hasher, you need to override algorithm,
- verify(), encode() and safe_summary().
-
- PasswordHasher objects are immutable.
- """
- algorithm = None
- library = None
-
- def _load_library(self):
- if self.library is not None:
- if isinstance(self.library, (tuple, list)):
- name, mod_path = self.library
- else:
- name = mod_path = self.library
- try:
- module = importlib.import_module(mod_path)
- except ImportError as e:
- raise ValueError("Couldn't load %r algorithm library: %s" %
- (self.__class__.__name__, e))
- return module
- raise ValueError("Hasher %r doesn't specify a library attribute" %
- self.__class__.__name__)
-
- def salt(self):
- """
- Generates a cryptographically secure nonce salt in ascii
- """
- return get_random_string()
-
- def verify(self, password, encoded):
- """
- Checks if the given password is correct
- """
- raise NotImplementedError()
-
- def encode(self, password, salt):
- """
- Creates an encoded database value
-
- The result is normally formatted as "algorithm$salt$hash" and
- must be fewer than 128 characters.
- """
- raise NotImplementedError()
-
- def safe_summary(self, encoded):
- """
- Returns a summary of safe values
-
- The result is a dictionary and will be used where the password field
- must be displayed to construct a safe representation of the password.
- """
- raise NotImplementedError()
-
- def must_update(self, encoded):
- return False
-
-
-class PBKDF2PasswordHasher(BasePasswordHasher):
- """
- Secure password hashing using the PBKDF2 algorithm (recommended)
-
- Configured to use PBKDF2 + HMAC + SHA256 with 12000 iterations.
- The result is a 64 byte binary string. Iterations may be changed
- safely but you must rename the algorithm if you change SHA256.
- """
- algorithm = "pbkdf2_sha256"
- iterations = 12000
- digest = hashlib.sha256
-
- def encode(self, password, salt, iterations=None):
- assert password is not None
- assert salt and '$' not in salt
- if not iterations:
- iterations = self.iterations
- hash = pbkdf2(password, salt, iterations, digest=self.digest)
- hash = base64.b64encode(hash).decode('ascii').strip()
- return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
-
- 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'), mask_hash(salt)),
- (_('hash'), mask_hash(hash)),
- ])
-
- def must_update(self, encoded):
- algorithm, iterations, salt, hash = encoded.split('$', 3)
- return int(iterations) != self.iterations
-
-
-class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
- """
- Alternate PBKDF2 hasher which uses SHA1, the default PRF
- recommended by PKCS #5. This is compatible with other
- implementations of PBKDF2, such as openssl's
- PKCS5_PBKDF2_HMAC_SHA1().
- """
- algorithm = "pbkdf2_sha1"
- digest = hashlib.sha1
-
-
-class BCryptSHA256PasswordHasher(BasePasswordHasher):
- """
- Secure password hashing using the bcrypt algorithm (recommended)
-
- This is considered by many to be the most secure algorithm but you
- must first install the bcrypt library. Please be warned that
- this library depends on native C code and might cause portability
- issues.
- """
- algorithm = "bcrypt_sha256"
- digest = hashlib.sha256
- library = ("bcrypt", "bcrypt")
- rounds = 12
-
- def salt(self):
- bcrypt = self._load_library()
- return bcrypt.gensalt(self.rounds)
-
- def encode(self, password, salt):
- bcrypt = self._load_library()
- # Need to reevaluate the force_bytes call once bcrypt is supported on
- # Python 3
-
- # Hash the password prior to using bcrypt to prevent password truncation
- # See: https://code.djangoproject.com/ticket/20138
- if self.digest is not None:
- # We use binascii.hexlify here because Python3 decided that a hex encoded
- # bytestring is somehow a unicode.
- password = binascii.hexlify(self.digest(force_bytes(password)).digest())
- else:
- password = force_bytes(password)
-
- data = bcrypt.hashpw(password, salt)
- return "%s$%s" % (self.algorithm, force_text(data))
-
- def verify(self, password, encoded):
- algorithm, data = encoded.split('$', 1)
- assert algorithm == self.algorithm
- bcrypt = self._load_library()
-
- # Hash the password prior to using bcrypt to prevent password truncation
- # See: https://code.djangoproject.com/ticket/20138
- if self.digest is not None:
- # We use binascii.hexlify here because Python3 decided that a hex encoded
- # bytestring is somehow a unicode.
- password = binascii.hexlify(self.digest(force_bytes(password)).digest())
- else:
- password = force_bytes(password)
-
- # Ensure that our data is a bytestring
- data = force_bytes(data)
- # force_bytes() necessary for py-bcrypt compatibility
- hashpw = force_bytes(bcrypt.hashpw(password, data))
-
- return constant_time_compare(data, hashpw)
-
- def safe_summary(self, encoded):
- algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
- assert algorithm == self.algorithm
- salt, checksum = data[:22], data[22:]
- return SortedDict([
- (_('algorithm'), algorithm),
- (_('work factor'), work_factor),
- (_('salt'), mask_hash(salt)),
- (_('checksum'), mask_hash(checksum)),
- ])
-
-
-class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
- """
- Secure password hashing using the bcrypt algorithm
-
- This is considered by many to be the most secure algorithm but you
- must first install the bcrypt library. Please be warned that
- this library depends on native C code and might cause portability
- issues.
-
- This hasher does not first hash the password which means it is subject to
- the 72 character bcrypt password truncation, most use cases should prefer
- the BCryptSha512PasswordHasher.
-
- See: https://code.djangoproject.com/ticket/20138
- """
- algorithm = "bcrypt"
- digest = None
-
-
-class SHA1PasswordHasher(BasePasswordHasher):
- """
- The SHA1 password hashing algorithm (not recommended)
- """
- algorithm = "sha1"
-
- def encode(self, password, salt):
- assert password is not None
- assert salt and '$' not in salt
- hash = hashlib.sha1(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'), mask_hash(salt, show=2)),
- (_('hash'), mask_hash(hash)),
- ])
-
-
-class MD5PasswordHasher(BasePasswordHasher):
- """
- The Salted MD5 password hashing algorithm (not recommended)
- """
- algorithm = "md5"
-
- def encode(self, password, salt):
- assert password is not None
- assert salt and '$' not in salt
- hash = hashlib.md5(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'), mask_hash(salt, show=2)),
- (_('hash'), mask_hash(hash)),
- ])
-
-
-class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
- """
- Very insecure algorithm that you should *never* use; stores SHA1 hashes
- with an empty salt.
-
- This class is implemented because Django used to accept such password
- hashes. Some older Django installs still have these values lingering
- around so we need to handle and upgrade them properly.
- """
- algorithm = "unsalted_sha1"
-
- def salt(self):
- return ''
-
- def encode(self, password, salt):
- assert salt == ''
- hash = hashlib.sha1(force_bytes(password)).hexdigest()
- return 'sha1$$%s' % hash
-
- def verify(self, password, encoded):
- encoded_2 = self.encode(password, '')
- return constant_time_compare(encoded, encoded_2)
-
- def safe_summary(self, encoded):
- assert encoded.startswith('sha1$$')
- hash = encoded[6:]
- return SortedDict([
- (_('algorithm'), self.algorithm),
- (_('hash'), mask_hash(hash)),
- ])
-
-
-class UnsaltedMD5PasswordHasher(BasePasswordHasher):
- """
- Incredibly insecure algorithm that you should *never* use; stores unsalted
- MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an
- empty salt.
-
- This class is implemented because Django used to store passwords this way
- and to accept such password hashes. Some older Django installs still have
- these values lingering around so we need to handle and upgrade them
- properly.
- """
- algorithm = "unsalted_md5"
-
- def salt(self):
- return ''
-
- def encode(self, password, salt):
- assert salt == ''
- return hashlib.md5(force_bytes(password)).hexdigest()
-
- def verify(self, password, encoded):
- if len(encoded) == 37 and encoded.startswith('md5$$'):
- encoded = encoded[5:]
- encoded_2 = self.encode(password, '')
- return constant_time_compare(encoded, encoded_2)
-
- def safe_summary(self, encoded):
- return SortedDict([
- (_('algorithm'), self.algorithm),
- (_('hash'), mask_hash(encoded, show=3)),
- ])
-
-
-class CryptPasswordHasher(BasePasswordHasher):
- """
- Password hashing using UNIX crypt (not recommended)
-
- The crypt module is not supported on all platforms.
- """
- algorithm = "crypt"
- library = "crypt"
-
- def salt(self):
- return get_random_string(2)
-
- def encode(self, password, salt):
- crypt = self._load_library()
- assert len(salt) == 2
- data = crypt.crypt(force_str(password), salt)
- # we don't need to store the salt, but Django used to do this
- return "%s$%s$%s" % (self.algorithm, '', data)
-
- def verify(self, password, encoded):
- crypt = self._load_library()
- algorithm, salt, data = encoded.split('$', 2)
- assert algorithm == self.algorithm
- return constant_time_compare(data, crypt.crypt(force_str(password), data))
-
- def safe_summary(self, encoded):
- algorithm, salt, data = encoded.split('$', 2)
- assert algorithm == self.algorithm
- return SortedDict([
- (_('algorithm'), algorithm),
- (_('salt'), salt),
- (_('hash'), mask_hash(data, show=3)),
- ])
-