login page
This commit is contained in:
642
Lib/site-packages/django/contrib/auth/hashers.py
Normal file
642
Lib/site-packages/django/contrib/auth/hashers.py
Normal file
@@ -0,0 +1,642 @@
|
||||
import base64
|
||||
import binascii
|
||||
import functools
|
||||
import hashlib
|
||||
import importlib
|
||||
import warnings
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.signals import setting_changed
|
||||
from django.dispatch import receiver
|
||||
from django.utils.crypto import (
|
||||
constant_time_compare, get_random_string, pbkdf2,
|
||||
)
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import gettext_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
|
||||
|
||||
|
||||
def is_password_usable(encoded):
|
||||
"""
|
||||
Return True if this password wasn't generated by
|
||||
User.set_unusable_password(), i.e. make_password(None).
|
||||
"""
|
||||
return encoded is None or not encoded.startswith(UNUSABLE_PASSWORD_PREFIX)
|
||||
|
||||
|
||||
def check_password(password, encoded, setter=None, preferred='default'):
|
||||
"""
|
||||
Return 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)
|
||||
try:
|
||||
hasher = identify_hasher(encoded)
|
||||
except ValueError:
|
||||
# encoded is gibberish or uses a hasher that's no longer installed.
|
||||
return False
|
||||
|
||||
hasher_changed = hasher.algorithm != preferred.algorithm
|
||||
must_update = hasher_changed or preferred.must_update(encoded)
|
||||
is_correct = hasher.verify(password, encoded)
|
||||
|
||||
# If the hasher didn't change (we don't protect against enumeration if it
|
||||
# does) and the password should get updated, try to close the timing gap
|
||||
# between the work factor of the current encoded password and the default
|
||||
# work factor.
|
||||
if not is_correct and not hasher_changed and must_update:
|
||||
hasher.harden_runtime(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 generate a new random salt. If password is None then
|
||||
return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
|
||||
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)
|
||||
if not isinstance(password, (bytes, str)):
|
||||
raise TypeError(
|
||||
'Password must be a string or bytes, got %s.'
|
||||
% type(password).__qualname__
|
||||
)
|
||||
hasher = get_hasher(hasher)
|
||||
salt = salt or hasher.salt()
|
||||
return hasher.encode(password, salt)
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def get_hashers():
|
||||
hashers = []
|
||||
for hasher_path in settings.PASSWORD_HASHERS:
|
||||
hasher_cls = import_string(hasher_path)
|
||||
hasher = hasher_cls()
|
||||
if not getattr(hasher, 'algorithm'):
|
||||
raise ImproperlyConfigured("hasher doesn't specify an "
|
||||
"algorithm name: %s" % hasher_path)
|
||||
hashers.append(hasher)
|
||||
return hashers
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def get_hashers_by_algorithm():
|
||||
return {hasher.algorithm: hasher for hasher in get_hashers()}
|
||||
|
||||
|
||||
@receiver(setting_changed)
|
||||
def reset_hashers(**kwargs):
|
||||
if kwargs['setting'] == 'PASSWORD_HASHERS':
|
||||
get_hashers.cache_clear()
|
||||
get_hashers_by_algorithm.cache_clear()
|
||||
|
||||
|
||||
def get_hasher(algorithm='default'):
|
||||
"""
|
||||
Return an instance of a loaded password hasher.
|
||||
|
||||
If algorithm is 'default', return the default hasher. Lazily import hashers
|
||||
specified in the project's settings file if needed.
|
||||
"""
|
||||
if hasattr(algorithm, 'algorithm'):
|
||||
return algorithm
|
||||
|
||||
elif algorithm == 'default':
|
||||
return get_hashers()[0]
|
||||
|
||||
else:
|
||||
hashers = get_hashers_by_algorithm()
|
||||
try:
|
||||
return hashers[algorithm]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown password hashing algorithm '%s'. "
|
||||
"Did you specify it in the PASSWORD_HASHERS "
|
||||
"setting?" % algorithm)
|
||||
|
||||
|
||||
def identify_hasher(encoded):
|
||||
"""
|
||||
Return an instance of a loaded password hasher.
|
||||
|
||||
Identify hasher algorithm by examining encoded hash, and call
|
||||
get_hasher() to return hasher. Raise 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="*"):
|
||||
"""
|
||||
Return 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:
|
||||
"""
|
||||
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:
|
||||
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):
|
||||
"""Generate a cryptographically secure nonce salt in ASCII."""
|
||||
# 12 returns a 71-bit value, log_2((26+26+10)^12) =~ 71 bits
|
||||
return get_random_string(12)
|
||||
|
||||
def verify(self, password, encoded):
|
||||
"""Check if the given password is correct."""
|
||||
raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
|
||||
|
||||
def encode(self, password, salt):
|
||||
"""
|
||||
Create an encoded database value.
|
||||
|
||||
The result is normally formatted as "algorithm$salt$hash" and
|
||||
must be fewer than 128 characters.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
|
||||
|
||||
def safe_summary(self, encoded):
|
||||
"""
|
||||
Return 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('subclasses of BasePasswordHasher must provide a safe_summary() method')
|
||||
|
||||
def must_update(self, encoded):
|
||||
return False
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
"""
|
||||
Bridge the runtime gap between the work factor supplied in `encoded`
|
||||
and the work factor suggested by this hasher.
|
||||
|
||||
Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
|
||||
`self.iterations` is 30000, this method should run password through
|
||||
another 10000 iterations of PBKDF2. Similar approaches should exist
|
||||
for any hasher that has a work factor. If not, this method should be
|
||||
defined as a no-op to silence the warning.
|
||||
"""
|
||||
warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
|
||||
|
||||
|
||||
class PBKDF2PasswordHasher(BasePasswordHasher):
|
||||
"""
|
||||
Secure password hashing using the PBKDF2 algorithm (recommended)
|
||||
|
||||
Configured to use PBKDF2 + HMAC + SHA256.
|
||||
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 = 216000
|
||||
digest = hashlib.sha256
|
||||
|
||||
def encode(self, password, salt, iterations=None):
|
||||
assert password is not None
|
||||
assert salt and '$' not in salt
|
||||
iterations = iterations or 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 {
|
||||
_('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
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
algorithm, iterations, salt, hash = encoded.split('$', 3)
|
||||
extra_iterations = self.iterations - int(iterations)
|
||||
if extra_iterations > 0:
|
||||
self.encode(password, salt, extra_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 Argon2PasswordHasher(BasePasswordHasher):
|
||||
"""
|
||||
Secure password hashing using the argon2 algorithm.
|
||||
|
||||
This is the winner of the Password Hashing Competition 2013-2015
|
||||
(https://password-hashing.net). It requires the argon2-cffi library which
|
||||
depends on native C code and might cause portability issues.
|
||||
"""
|
||||
algorithm = 'argon2'
|
||||
library = 'argon2'
|
||||
|
||||
time_cost = 2
|
||||
memory_cost = 512
|
||||
parallelism = 2
|
||||
|
||||
def encode(self, password, salt):
|
||||
argon2 = self._load_library()
|
||||
data = argon2.low_level.hash_secret(
|
||||
password.encode(),
|
||||
salt.encode(),
|
||||
time_cost=self.time_cost,
|
||||
memory_cost=self.memory_cost,
|
||||
parallelism=self.parallelism,
|
||||
hash_len=argon2.DEFAULT_HASH_LENGTH,
|
||||
type=argon2.low_level.Type.I,
|
||||
)
|
||||
return self.algorithm + data.decode('ascii')
|
||||
|
||||
def verify(self, password, encoded):
|
||||
argon2 = self._load_library()
|
||||
algorithm, rest = encoded.split('$', 1)
|
||||
assert algorithm == self.algorithm
|
||||
try:
|
||||
return argon2.low_level.verify_secret(
|
||||
('$' + rest).encode('ascii'),
|
||||
password.encode(),
|
||||
type=argon2.low_level.Type.I,
|
||||
)
|
||||
except argon2.exceptions.VerificationError:
|
||||
return False
|
||||
|
||||
def safe_summary(self, encoded):
|
||||
(algorithm, variety, version, time_cost, memory_cost, parallelism,
|
||||
salt, data) = self._decode(encoded)
|
||||
assert algorithm == self.algorithm
|
||||
return {
|
||||
_('algorithm'): algorithm,
|
||||
_('variety'): variety,
|
||||
_('version'): version,
|
||||
_('memory cost'): memory_cost,
|
||||
_('time cost'): time_cost,
|
||||
_('parallelism'): parallelism,
|
||||
_('salt'): mask_hash(salt),
|
||||
_('hash'): mask_hash(data),
|
||||
}
|
||||
|
||||
def must_update(self, encoded):
|
||||
(algorithm, variety, version, time_cost, memory_cost, parallelism,
|
||||
salt, data) = self._decode(encoded)
|
||||
assert algorithm == self.algorithm
|
||||
argon2 = self._load_library()
|
||||
return (
|
||||
argon2.low_level.ARGON2_VERSION != version or
|
||||
self.time_cost != time_cost or
|
||||
self.memory_cost != memory_cost or
|
||||
self.parallelism != parallelism
|
||||
)
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
# The runtime for Argon2 is too complicated to implement a sensible
|
||||
# hardening algorithm.
|
||||
pass
|
||||
|
||||
def _decode(self, encoded):
|
||||
"""
|
||||
Split an encoded hash and return: (
|
||||
algorithm, variety, version, time_cost, memory_cost,
|
||||
parallelism, salt, data,
|
||||
).
|
||||
"""
|
||||
bits = encoded.split('$')
|
||||
if len(bits) == 5:
|
||||
# Argon2 < 1.3
|
||||
algorithm, variety, raw_params, salt, data = bits
|
||||
version = 0x10
|
||||
else:
|
||||
assert len(bits) == 6
|
||||
algorithm, variety, raw_version, raw_params, salt, data = bits
|
||||
assert raw_version.startswith('v=')
|
||||
version = int(raw_version[len('v='):])
|
||||
params = dict(bit.split('=', 1) for bit in raw_params.split(','))
|
||||
assert len(params) == 3 and all(x in params for x in ('t', 'm', 'p'))
|
||||
time_cost = int(params['t'])
|
||||
memory_cost = int(params['m'])
|
||||
parallelism = int(params['p'])
|
||||
return (
|
||||
algorithm, variety, version, time_cost, memory_cost, parallelism,
|
||||
salt, data,
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
password = password.encode()
|
||||
# Hash the password prior to using bcrypt to prevent password
|
||||
# truncation as described in #20138.
|
||||
if self.digest is not None:
|
||||
# Use binascii.hexlify() because a hex encoded bytestring is str.
|
||||
password = binascii.hexlify(self.digest(password).digest())
|
||||
|
||||
data = bcrypt.hashpw(password, salt)
|
||||
return "%s$%s" % (self.algorithm, data.decode('ascii'))
|
||||
|
||||
def verify(self, password, encoded):
|
||||
algorithm, data = encoded.split('$', 1)
|
||||
assert algorithm == self.algorithm
|
||||
encoded_2 = self.encode(password, data.encode('ascii'))
|
||||
return constant_time_compare(encoded, encoded_2)
|
||||
|
||||
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 {
|
||||
_('algorithm'): algorithm,
|
||||
_('work factor'): work_factor,
|
||||
_('salt'): mask_hash(salt),
|
||||
_('checksum'): mask_hash(checksum),
|
||||
}
|
||||
|
||||
def must_update(self, encoded):
|
||||
algorithm, empty, algostr, rounds, data = encoded.split('$', 4)
|
||||
return int(rounds) != self.rounds
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
_, data = encoded.split('$', 1)
|
||||
salt = data[:29] # Length of the salt in bcrypt.
|
||||
rounds = data.split('$')[2]
|
||||
# work factor is logarithmic, adding one doubles the load.
|
||||
diff = 2**(self.rounds - int(rounds)) - 1
|
||||
while diff > 0:
|
||||
self.encode(password, salt.encode('ascii'))
|
||||
diff -= 1
|
||||
|
||||
|
||||
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
|
||||
bcrypt's 72 bytes password truncation. Most use cases should prefer the
|
||||
BCryptSHA256PasswordHasher.
|
||||
"""
|
||||
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((salt + password).encode()).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 {
|
||||
_('algorithm'): algorithm,
|
||||
_('salt'): mask_hash(salt, show=2),
|
||||
_('hash'): mask_hash(hash),
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
pass
|
||||
|
||||
|
||||
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((salt + password).encode()).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 {
|
||||
_('algorithm'): algorithm,
|
||||
_('salt'): mask_hash(salt, show=2),
|
||||
_('hash'): mask_hash(hash),
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
pass
|
||||
|
||||
|
||||
class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
|
||||
"""
|
||||
Very insecure algorithm that you should *never* use; store 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(password.encode()).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 {
|
||||
_('algorithm'): self.algorithm,
|
||||
_('hash'): mask_hash(hash),
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
pass
|
||||
|
||||
|
||||
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(password.encode()).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 {
|
||||
_('algorithm'): self.algorithm,
|
||||
_('hash'): mask_hash(encoded, show=3),
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
pass
|
||||
|
||||
|
||||
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(password, salt)
|
||||
assert data is not None # A platform like OpenBSD with a dummy crypt module.
|
||||
# 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(password, data))
|
||||
|
||||
def safe_summary(self, encoded):
|
||||
algorithm, salt, data = encoded.split('$', 2)
|
||||
assert algorithm == self.algorithm
|
||||
return {
|
||||
_('algorithm'): algorithm,
|
||||
_('salt'): salt,
|
||||
_('hash'): mask_hash(data, show=3),
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
pass
|
||||
Reference in New Issue
Block a user