Server IP : 85.214.239.14 / Your IP : 3.15.144.162 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /srv/modoboa/env/lib64/python3.5/site-packages/passlib/handlers/ |
Upload File : |
"""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 #=============================================================================