You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					513 lines
				
				20 KiB
			
		
		
			
		
	
	
					513 lines
				
				20 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.handlers.django- Django password hash support"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								from base64 import b64encode
							 | 
						||
| 
								 | 
							
								from binascii import hexlify
							 | 
						||
| 
								 | 
							
								from hashlib import md5, sha1, sha256
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.handlers.bcrypt import _wrapped_bcrypt
							 | 
						||
| 
								 | 
							
								from passlib.hash import argon2, bcrypt, pbkdf2_sha1, pbkdf2_sha256
							 | 
						||
| 
								 | 
							
								from passlib.utils import to_unicode, rng, getrandstr
							 | 
						||
| 
								 | 
							
								from passlib.utils.binary import BASE64_CHARS
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import str_to_uascii, uascii_to_str, unicode, u
							 | 
						||
| 
								 | 
							
								from passlib.crypto.digest import pbkdf2_hmac
							 | 
						||
| 
								 | 
							
								import passlib.utils.handlers as uh
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "django_salted_sha1",
							 | 
						||
| 
								 | 
							
								    "django_salted_md5",
							 | 
						||
| 
								 | 
							
								    "django_bcrypt",
							 | 
						||
| 
								 | 
							
								    "django_pbkdf2_sha1",
							 | 
						||
| 
								 | 
							
								    "django_pbkdf2_sha256",
							 | 
						||
| 
								 | 
							
								    "django_argon2",
							 | 
						||
| 
								 | 
							
								    "django_des_crypt",
							 | 
						||
| 
								 | 
							
								    "django_disabled",
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# lazy imports & constants
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# imported by django_des_crypt._calc_checksum()
							 | 
						||
| 
								 | 
							
								des_crypt = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _import_des_crypt():
							 | 
						||
| 
								 | 
							
								    global des_crypt
							 | 
						||
| 
								 | 
							
								    if des_crypt is None:
							 | 
						||
| 
								 | 
							
								        from passlib.hash import des_crypt
							 | 
						||
| 
								 | 
							
								    return des_crypt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# django 1.4's salt charset
							 | 
						||
| 
								 | 
							
								SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# salted hashes
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """base class providing common code for django hashes"""
							 | 
						||
| 
								 | 
							
								    # name, ident, checksum_size must be set by subclass.
							 | 
						||
| 
								 | 
							
								    # ident must include "$" suffix.
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt", "salt_size")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: django 1.0-1.3 would accept empty salt strings.
							 | 
						||
| 
								 | 
							
								    #       django 1.4 won't, but this appears to be regression
							 | 
						||
| 
								 | 
							
								    #       (https://code.djangoproject.com/ticket/18144)
							 | 
						||
| 
								 | 
							
								    #       so presumably it will be fixed in a later release.
							 | 
						||
| 
								 | 
							
								    default_salt_size = 12
							 | 
						||
| 
								 | 
							
								    max_salt_size = None
							 | 
						||
| 
								 | 
							
								    salt_chars = SALT_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.LOWER_HEX_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
							 | 
						||
| 
								 | 
							
								        return cls(salt=salt, checksum=chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        return uh.render_mc2(self.ident, self.salt, self.checksum)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# NOTE: only used by PBKDF2
							 | 
						||
| 
								 | 
							
								class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
							 | 
						||
| 
								 | 
							
								    """base class providing common code for django hashes w/ variable rounds"""
							 | 
						||
| 
								 | 
							
								    setting_kwds = DjangoSaltedHash.setting_kwds + ("rounds",)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    min_rounds = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
							 | 
						||
| 
								 | 
							
								        return cls(rounds=rounds, salt=salt, checksum=chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_salted_sha1(DjangoSaltedHash):
							 | 
						||
| 
								 | 
							
								    """This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and uses a single round of SHA1.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, a 12 character one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt_size: int
							 | 
						||
| 
								 | 
							
								    :param salt_size:
							 | 
						||
| 
								 | 
							
								        Optional number of characters to use when autogenerating new salts.
							 | 
						||
| 
								 | 
							
								        Defaults to 12, but can be any positive value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with Django 1.4's :class:`!SHA1PasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged: 1.6
							 | 
						||
| 
								 | 
							
								        This class now generates 12-character salts instead of 5,
							 | 
						||
| 
								 | 
							
								        and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
							 | 
						||
| 
								 | 
							
								        the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
							 | 
						||
| 
								 | 
							
								        generates these hashes; but hashes generated in this manner will still be
							 | 
						||
| 
								 | 
							
								        correctly interpreted by earlier versions of Django.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_salted_sha1"
							 | 
						||
| 
								 | 
							
								    django_name = "sha1"
							 | 
						||
| 
								 | 
							
								    ident = u("sha1$")
							 | 
						||
| 
								 | 
							
								    checksum_size = 40
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_salted_md5(DjangoSaltedHash):
							 | 
						||
| 
								 | 
							
								    """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and uses a single round of MD5.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, a 12 character one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt_size: int
							 | 
						||
| 
								 | 
							
								    :param salt_size:
							 | 
						||
| 
								 | 
							
								        Optional number of characters to use when autogenerating new salts.
							 | 
						||
| 
								 | 
							
								        Defaults to 12, but can be any positive value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.4's :class:`!MD5PasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged: 1.6
							 | 
						||
| 
								 | 
							
								        This class now generates 12-character salts instead of 5,
							 | 
						||
| 
								 | 
							
								        and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
							 | 
						||
| 
								 | 
							
								        the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
							 | 
						||
| 
								 | 
							
								        generates these hashes; but hashes generated in this manner will still be
							 | 
						||
| 
								 | 
							
								        correctly interpreted by earlier versions of Django.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_salted_md5"
							 | 
						||
| 
								 | 
							
								    django_name = "md5"
							 | 
						||
| 
								 | 
							
								    ident = u("md5$")
							 | 
						||
| 
								 | 
							
								    checksum_size = 32
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# BCrypt
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt,
							 | 
						||
| 
								 | 
							
								    prefix=u('bcrypt$'), ident=u("bcrypt$"),
							 | 
						||
| 
								 | 
							
								    # NOTE: this docstring is duplicated in the docs, since sphinx
							 | 
						||
| 
								 | 
							
								    # seems to be having trouble reading it via autodata::
							 | 
						||
| 
								 | 
							
								    doc="""This class implements Django 1.4's BCrypt wrapper, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This is identical to :class:`!bcrypt` itself, but with
							 | 
						||
| 
								 | 
							
								    the Django-specific prefix ``"bcrypt$"`` prepended.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    See :doc:`/lib/passlib.hash.bcrypt` for more details,
							 | 
						||
| 
								 | 
							
								    the usage and behavior is identical.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.4's :class:`!BCryptPasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								    """)
							 | 
						||
| 
								 | 
							
								django_bcrypt.django_name = "bcrypt"
							 | 
						||
| 
								 | 
							
								django_bcrypt._using_clone_attrs += ("django_name",)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# BCRYPT + SHA256
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_bcrypt_sha256(_wrapped_bcrypt):
							 | 
						||
| 
								 | 
							
								    """This class implements Django 1.6's Bcrypt+SHA256 hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and a variable number of rounds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    While the algorithm and format is somewhat different,
							 | 
						||
| 
								 | 
							
								    the api and options for this hash are identical to :class:`!bcrypt` itself,
							 | 
						||
| 
								 | 
							
								    see :doc:`bcrypt </lib/passlib.hash.bcrypt>` for more details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6.2
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_bcrypt_sha256"
							 | 
						||
| 
								 | 
							
								    django_name = "bcrypt_sha256"
							 | 
						||
| 
								 | 
							
								    _digest = sha256
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # sample hash:
							 | 
						||
| 
								 | 
							
								    # bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # XXX: we can't use .ident attr due to bcrypt code using it.
							 | 
						||
| 
								 | 
							
								    #      working around that via django_prefix
							 | 
						||
| 
								 | 
							
								    django_prefix = u('bcrypt_sha256$')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def identify(cls, hash):
							 | 
						||
| 
								 | 
							
								        hash = uh.to_unicode_for_identify(hash)
							 | 
						||
| 
								 | 
							
								        if not hash:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        return hash.startswith(cls.django_prefix)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        hash = to_unicode(hash, "ascii", "hash")
							 | 
						||
| 
								 | 
							
								        if not hash.startswith(cls.django_prefix):
							 | 
						||
| 
								 | 
							
								            raise uh.exc.InvalidHashError(cls)
							 | 
						||
| 
								 | 
							
								        bhash = hash[len(cls.django_prefix):]
							 | 
						||
| 
								 | 
							
								        if not bhash.startswith("$2"):
							 | 
						||
| 
								 | 
							
								            raise uh.exc.MalformedHashError(cls)
							 | 
						||
| 
								 | 
							
								        return super(django_bcrypt_sha256, cls).from_string(bhash)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        bhash = super(django_bcrypt_sha256, self).to_string()
							 | 
						||
| 
								 | 
							
								        return uascii_to_str(self.django_prefix) + bhash
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        secret = hexlify(self._digest(secret).digest())
							 | 
						||
| 
								 | 
							
								        return super(django_bcrypt_sha256, self)._calc_checksum(secret)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# PBKDF2 variants
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_pbkdf2_sha256(DjangoVariableHash):
							 | 
						||
| 
								 | 
							
								    """This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and a variable number of rounds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, a 12 character one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt_size: int
							 | 
						||
| 
								 | 
							
								    :param salt_size:
							 | 
						||
| 
								 | 
							
								        Optional number of characters to use when autogenerating new salts.
							 | 
						||
| 
								 | 
							
								        Defaults to 12, but can be any positive value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type rounds: int
							 | 
						||
| 
								 | 
							
								    :param rounds:
							 | 
						||
| 
								 | 
							
								        Optional number of rounds to use.
							 | 
						||
| 
								 | 
							
								        Defaults to 29000, but must be within ``range(1,1<<32)``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type relaxed: bool
							 | 
						||
| 
								 | 
							
								    :param relaxed:
							 | 
						||
| 
								 | 
							
								        By default, providing an invalid value for one of the other
							 | 
						||
| 
								 | 
							
								        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
							 | 
						||
| 
								 | 
							
								        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
							 | 
						||
| 
								 | 
							
								        will be issued instead. Correctable errors include ``rounds``
							 | 
						||
| 
								 | 
							
								        that are too small or too large, and ``salt`` strings that are too long.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.4's :class:`!PBKDF2PasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_pbkdf2_sha256"
							 | 
						||
| 
								 | 
							
								    django_name = "pbkdf2_sha256"
							 | 
						||
| 
								 | 
							
								    ident = u('pbkdf2_sha256$')
							 | 
						||
| 
								 | 
							
								    min_salt_size = 1
							 | 
						||
| 
								 | 
							
								    max_rounds = 0xffffffff # setting at 32-bit limit for now
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.PADDED_BASE64_CHARS
							 | 
						||
| 
								 | 
							
								    checksum_size = 44 # 32 bytes -> base64
							 | 
						||
| 
								 | 
							
								    default_rounds = pbkdf2_sha256.default_rounds # NOTE: django 1.6 uses 12000
							 | 
						||
| 
								 | 
							
								    _digest = "sha256"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        # NOTE: secret & salt will be encoded using UTF-8 by pbkdf2_hmac()
							 | 
						||
| 
								 | 
							
								        hash = pbkdf2_hmac(self._digest, secret, self.salt, self.rounds)
							 | 
						||
| 
								 | 
							
								        return b64encode(hash).rstrip().decode("ascii")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_pbkdf2_sha1(django_pbkdf2_sha256):
							 | 
						||
| 
								 | 
							
								    """This class implements Django's PBKDF2-HMAC-SHA1 hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and a variable number of rounds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, a 12 character one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt_size: int
							 | 
						||
| 
								 | 
							
								    :param salt_size:
							 | 
						||
| 
								 | 
							
								        Optional number of characters to use when autogenerating new salts.
							 | 
						||
| 
								 | 
							
								        Defaults to 12, but can be any positive value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type rounds: int
							 | 
						||
| 
								 | 
							
								    :param rounds:
							 | 
						||
| 
								 | 
							
								        Optional number of rounds to use.
							 | 
						||
| 
								 | 
							
								        Defaults to 131000, but must be within ``range(1,1<<32)``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type relaxed: bool
							 | 
						||
| 
								 | 
							
								    :param relaxed:
							 | 
						||
| 
								 | 
							
								        By default, providing an invalid value for one of the other
							 | 
						||
| 
								 | 
							
								        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
							 | 
						||
| 
								 | 
							
								        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
							 | 
						||
| 
								 | 
							
								        will be issued instead. Correctable errors include ``rounds``
							 | 
						||
| 
								 | 
							
								        that are too small or too large, and ``salt`` strings that are too long.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.4's :class:`!PBKDF2SHA1PasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_pbkdf2_sha1"
							 | 
						||
| 
								 | 
							
								    django_name = "pbkdf2_sha1"
							 | 
						||
| 
								 | 
							
								    ident = u('pbkdf2_sha1$')
							 | 
						||
| 
								 | 
							
								    checksum_size = 28 # 20 bytes -> base64
							 | 
						||
| 
								 | 
							
								    default_rounds = pbkdf2_sha1.default_rounds # NOTE: django 1.6 uses 12000
							 | 
						||
| 
								 | 
							
								    _digest = "sha1"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# Argon2
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# NOTE: as of 2019-11-11, Django's Argon2PasswordHasher only supports Type I;
							 | 
						||
| 
								 | 
							
								#       so limiting this to ensure that as well.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								django_argon2 = uh.PrefixWrapper(
							 | 
						||
| 
								 | 
							
								    name="django_argon2",
							 | 
						||
| 
								 | 
							
								    wrapped=argon2.using(type="I"),
							 | 
						||
| 
								 | 
							
								    prefix=u('argon2'),
							 | 
						||
| 
								 | 
							
								    ident=u('argon2$argon2i$'),
							 | 
						||
| 
								 | 
							
								    # NOTE: this docstring is duplicated in the docs, since sphinx
							 | 
						||
| 
								 | 
							
								    # seems to be having trouble reading it via autodata::
							 | 
						||
| 
								 | 
							
								    doc="""This class implements Django 1.10's Argon2 wrapper, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This is identical to :class:`!argon2` itself, but with
							 | 
						||
| 
								 | 
							
								    the Django-specific prefix ``"argon2$"`` prepended.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    See :doc:`argon2 </lib/passlib.hash.argon2>` for more details,
							 | 
						||
| 
								 | 
							
								    the usage and behavior is identical.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.10's :class:`!Argon2PasswordHasher` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.7
							 | 
						||
| 
								 | 
							
								    """)
							 | 
						||
| 
								 | 
							
								django_argon2.django_name = "argon2"
							 | 
						||
| 
								 | 
							
								django_argon2._using_clone_attrs += ("django_name",)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# DES
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class django_des_crypt(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """This class implements Django's :class:`des_crypt` wrapper, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a fixed-length salt.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param bool truncate_error:
							 | 
						||
| 
								 | 
							
								        By default, django_des_crypt will silently truncate passwords larger than 8 bytes.
							 | 
						||
| 
								 | 
							
								        Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
							 | 
						||
| 
								 | 
							
								        to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. versionadded:: 1.7
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This should be compatible with the hashes generated by
							 | 
						||
| 
								 | 
							
								    Django 1.4's :class:`!CryptPasswordHasher` class.
							 | 
						||
| 
								 | 
							
								    Note that Django only supports this hash on Unix systems
							 | 
						||
| 
								 | 
							
								    (though :class:`!django_des_crypt` is available cross-platform
							 | 
						||
| 
								 | 
							
								    under Passlib).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged:: 1.6
							 | 
						||
| 
								 | 
							
								        This class will now accept hashes with empty salt strings,
							 | 
						||
| 
								 | 
							
								        since Django 1.4 generates them this way.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_des_crypt"
							 | 
						||
| 
								 | 
							
								    django_name = "crypt"
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt", "salt_size", "truncate_error")
							 | 
						||
| 
								 | 
							
								    ident = u("crypt$")
							 | 
						||
| 
								 | 
							
								    checksum_chars = salt_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								    checksum_size = 11
							 | 
						||
| 
								 | 
							
								    min_salt_size = default_salt_size = 2
							 | 
						||
| 
								 | 
							
								    truncate_size = 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: regarding duplicate salt field:
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # django 1.0 had a "crypt$<salt1>$<salt2><digest>" hash format,
							 | 
						||
| 
								 | 
							
								    # used [a-z0-9] to generate a 5 char salt, stored it in salt1,
							 | 
						||
| 
								 | 
							
								    # duplicated the first two chars of salt1 as salt2.
							 | 
						||
| 
								 | 
							
								    # it would throw an error if salt1 was empty.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # django 1.4 started generating 2 char salt using the full alphabet,
							 | 
						||
| 
								 | 
							
								    # left salt1 empty, and only paid attention to salt2.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # in order to be compatible with django 1.0, the hashes generated
							 | 
						||
| 
								 | 
							
								    # by this function will always include salt1, unless the following
							 | 
						||
| 
								 | 
							
								    # class-level field is disabled (mainly used for testing)
							 | 
						||
| 
								 | 
							
								    use_duplicate_salt = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
							 | 
						||
| 
								 | 
							
								        if chk:
							 | 
						||
| 
								 | 
							
								            # chk should be full des_crypt hash
							 | 
						||
| 
								 | 
							
								            if not salt:
							 | 
						||
| 
								 | 
							
								                # django 1.4 always uses empty salt field,
							 | 
						||
| 
								 | 
							
								                # so extract salt from des_crypt hash <chk>
							 | 
						||
| 
								 | 
							
								                salt = chk[:2]
							 | 
						||
| 
								 | 
							
								            elif salt[:2] != chk[:2]:
							 | 
						||
| 
								 | 
							
								                # django 1.0 stored 5 chars in salt field, and duplicated
							 | 
						||
| 
								 | 
							
								                # the first two chars in <chk>. we keep the full salt,
							 | 
						||
| 
								 | 
							
								                # but make sure the first two chars match as sanity check.
							 | 
						||
| 
								 | 
							
								                raise uh.exc.MalformedHashError(cls,
							 | 
						||
| 
								 | 
							
								                    "first two digits of salt and checksum must match")
							 | 
						||
| 
								 | 
							
								            # in all cases, strip salt chars from <chk>
							 | 
						||
| 
								 | 
							
								            chk = chk[2:]
							 | 
						||
| 
								 | 
							
								        return cls(salt=salt, checksum=chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        salt = self.salt
							 | 
						||
| 
								 | 
							
								        chk = salt[:2] + self.checksum
							 | 
						||
| 
								 | 
							
								        if self.use_duplicate_salt:
							 | 
						||
| 
								 | 
							
								            # filling in salt field, so that we're compatible with django 1.0
							 | 
						||
| 
								 | 
							
								            return uh.render_mc2(self.ident, salt, chk)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            # django 1.4+ style hash
							 | 
						||
| 
								 | 
							
								            return uh.render_mc2(self.ident, "", chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        # NOTE: we lazily import des_crypt,
							 | 
						||
| 
								 | 
							
								        #       since most django deploys won't use django_des_crypt
							 | 
						||
| 
								 | 
							
								        global des_crypt
							 | 
						||
| 
								 | 
							
								        if des_crypt is None:
							 | 
						||
| 
								 | 
							
								            _import_des_crypt()
							 | 
						||
| 
								 | 
							
								        # check for truncation (during .hash() calls only)
							 | 
						||
| 
								 | 
							
								        if self.use_defaults:
							 | 
						||
| 
								 | 
							
								            self._check_truncate_policy(secret)
							 | 
						||
| 
								 | 
							
								        return des_crypt(salt=self.salt[:2])._calc_checksum(secret)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class django_disabled(uh.ifc.DisabledHash, uh.StaticHandler):
							 | 
						||
| 
								 | 
							
								    """This class provides disabled password behavior for Django, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This class does not implement a hash, but instead
							 | 
						||
| 
								 | 
							
								    claims the special hash string ``"!"`` which Django uses
							 | 
						||
| 
								 | 
							
								    to indicate an account's password has been disabled.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    * newly encrypted passwords will hash to ``"!"``.
							 | 
						||
| 
								 | 
							
								    * it rejects all passwords.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. note::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Django 1.6 prepends a randomly generated 40-char alphanumeric string
							 | 
						||
| 
								 | 
							
								        to each unusuable password. This class recognizes such strings,
							 | 
						||
| 
								 | 
							
								        but for backwards compatibility, still returns ``"!"``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        See `<https://code.djangoproject.com/ticket/20079>`_ for why
							 | 
						||
| 
								 | 
							
								        Django appends an alphanumeric string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged:: 1.6.2 added Django 1.6 support
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged:: 1.7 started appending an alphanumeric string.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    name = "django_disabled"
							 | 
						||
| 
								 | 
							
								    _hash_prefix = u("!")
							 | 
						||
| 
								 | 
							
								    suffix_length = 40
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # XXX: move this to StaticHandler, or wherever _hash_prefix is being used?
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def identify(cls, hash):
							 | 
						||
| 
								 | 
							
								        hash = uh.to_unicode_for_identify(hash)
							 | 
						||
| 
								 | 
							
								        return hash.startswith(cls._hash_prefix)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        # generate random suffix to match django's behavior
							 | 
						||
| 
								 | 
							
								        return getrandstr(rng, BASE64_CHARS[:-2], self.suffix_length)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def verify(cls, secret, hash):
							 | 
						||
| 
								 | 
							
								        uh.validate_secret(secret)
							 | 
						||
| 
								 | 
							
								        if not cls.identify(hash):
							 | 
						||
| 
								 | 
							
								            raise uh.exc.InvalidHashError(cls)
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |