Загрузить файлы в «venv/Lib/site-packages/passlib/handlers»
This commit is contained in:
168
venv/Lib/site-packages/passlib/handlers/digests.py
Normal file
168
venv/Lib/site-packages/passlib/handlers/digests.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""passlib.handlers.digests - plain hash digests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import hashlib
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str, to_bytes, render_bytes, consteq
|
||||
from passlib.utils.compat import unicode, str_to_uascii
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
# local
|
||||
__all__ = [
|
||||
"create_hex_hash",
|
||||
"hex_md4",
|
||||
"hex_md5",
|
||||
"hex_sha1",
|
||||
"hex_sha256",
|
||||
"hex_sha512",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# helpers for hexadecimal hashes
|
||||
#=============================================================================
|
||||
class HexDigestHash(uh.StaticHandler):
|
||||
"""this provides a template for supporting passwords stored as plain hexadecimal hashes"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
_hash_func = None # hash function to use - filled in by create_hex_hash()
|
||||
checksum_size = None # filled in by create_hex_hash()
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
|
||||
#: special for detecting if _hash_func is just a stub method.
|
||||
supported = True
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(self._hash_func(secret).hexdigest())
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
def create_hex_hash(digest, module=__name__, django_name=None, required=True):
|
||||
"""
|
||||
create hex-encoded unsalted hasher for specified digest algorithm.
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
If called with unknown/supported digest, won't throw error immediately,
|
||||
but instead return a dummy hasher that will throw error when called.
|
||||
|
||||
set ``required=True`` to restore old behavior.
|
||||
"""
|
||||
info = lookup_hash(digest, required=required)
|
||||
name = "hex_" + info.name
|
||||
if not info.supported:
|
||||
info.digest_size = 0
|
||||
hasher = type(name, (HexDigestHash,), dict(
|
||||
name=name,
|
||||
__module__=module, # so ABCMeta won't clobber it
|
||||
_hash_func=staticmethod(info.const), # sometimes it's a function, sometimes not. so wrap it.
|
||||
checksum_size=info.digest_size*2,
|
||||
__doc__="""This class implements a plain hexadecimal %s hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports no optional or contextual keywords.
|
||||
""" % (info.name,)
|
||||
))
|
||||
if not info.supported:
|
||||
hasher.supported = False
|
||||
if django_name:
|
||||
hasher.django_name = django_name
|
||||
return hasher
|
||||
|
||||
#=============================================================================
|
||||
# predefined handlers
|
||||
#=============================================================================
|
||||
|
||||
# NOTE: some digests below are marked as "required=False", because these may not be present on
|
||||
# FIPS systems (see issue 116). if missing, will return stub hasher that throws error
|
||||
# if an attempt is made to actually use hash/verify with them.
|
||||
|
||||
hex_md4 = create_hex_hash("md4", required=False)
|
||||
hex_md5 = create_hex_hash("md5", django_name="unsalted_md5", required=False)
|
||||
hex_sha1 = create_hex_hash("sha1", required=False)
|
||||
hex_sha256 = create_hex_hash("sha256")
|
||||
hex_sha512 = create_hex_hash("sha512")
|
||||
|
||||
#=============================================================================
|
||||
# htdigest
|
||||
#=============================================================================
|
||||
class htdigest(uh.MinimalHandler):
|
||||
"""htdigest hash function.
|
||||
|
||||
.. todo::
|
||||
document this hash
|
||||
"""
|
||||
name = "htdigest"
|
||||
setting_kwds = ()
|
||||
context_kwds = ("user", "realm", "encoding")
|
||||
default_encoding = "utf-8"
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, user, realm, encoding=None):
|
||||
# NOTE: this was deliberately written so that raw bytes are passed through
|
||||
# unchanged, the encoding kwd is only used to handle unicode values.
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
uh.validate_secret(secret)
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode(encoding)
|
||||
user = to_bytes(user, encoding, "user")
|
||||
realm = to_bytes(realm, encoding, "realm")
|
||||
data = render_bytes("%s:%s:%s", user, realm, secret)
|
||||
return hashlib.md5(data).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
"""normalize hash to native string, and validate it"""
|
||||
hash = to_native_str(hash, param="hash")
|
||||
if len(hash) != 32:
|
||||
raise uh.exc.MalformedHashError(cls, "wrong size")
|
||||
for char in hash:
|
||||
if char not in uh.LC_HEX_CHARS:
|
||||
raise uh.exc.MalformedHashError(cls, "invalid chars in hash")
|
||||
return hash
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, user, realm, encoding="utf-8"):
|
||||
hash = cls._norm_hash(hash)
|
||||
other = cls.hash(secret, user, realm, encoding)
|
||||
return consteq(hash, other)
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
try:
|
||||
cls._norm_hash(hash)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls):
|
||||
return cls.hash("", "", "")
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, user, realm, encoding=None):
|
||||
# NOTE: 'config' is ignored, as this hash has no salting / other configuration.
|
||||
# just have to make sure it's valid.
|
||||
cls._norm_hash(config)
|
||||
return cls.hash(secret, user, realm, encoding)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
512
venv/Lib/site-packages/passlib/handlers/django.py
Normal file
512
venv/Lib/site-packages/passlib/handlers/django.py
Normal file
@@ -0,0 +1,512 @@
|
||||
"""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
|
||||
#=============================================================================
|
||||
214
venv/Lib/site-packages/passlib/handlers/fshp.py
Normal file
214
venv/Lib/site-packages/passlib/handlers/fshp.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""passlib.handlers.fshp
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from base64 import b64encode, b64decode
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.utils.compat import bascii_to_str, iteritems, u,\
|
||||
unicode
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
# local
|
||||
__all__ = [
|
||||
'fshp',
|
||||
]
|
||||
#=============================================================================
|
||||
# sha1-crypt
|
||||
#=============================================================================
|
||||
class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the FSHP password 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:
|
||||
|
||||
:param salt:
|
||||
Optional raw salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any non-negative value.
|
||||
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 480000, must be between 1 and 4294967295, inclusive.
|
||||
|
||||
:param variant:
|
||||
Optionally specifies variant of FSHP to use.
|
||||
|
||||
* ``0`` - uses SHA-1 digest (deprecated).
|
||||
* ``1`` - uses SHA-2/256 digest (default).
|
||||
* ``2`` - uses SHA-2/384 digest.
|
||||
* ``3`` - uses SHA-2/512 digest.
|
||||
|
||||
: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.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "fshp"
|
||||
setting_kwds = ("salt", "salt_size", "rounds", "variant")
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
ident = u("{FSHP")
|
||||
# checksum_size is property() that depends on variant
|
||||
|
||||
#--HasRawSalt--
|
||||
default_salt_size = 16 # current passlib default, FSHP uses 8
|
||||
max_salt_size = None
|
||||
|
||||
#--HasRounds--
|
||||
# FIXME: should probably use different default rounds
|
||||
# based on the variant. setting for default variant (sha256) for now.
|
||||
default_rounds = 480000 # current passlib default, FSHP uses 4096
|
||||
min_rounds = 1 # set by FSHP
|
||||
max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--variants--
|
||||
default_variant = 1
|
||||
_variant_info = {
|
||||
# variant: (hash name, digest size)
|
||||
0: ("sha1", 20),
|
||||
1: ("sha256", 32),
|
||||
2: ("sha384", 48),
|
||||
3: ("sha512", 64),
|
||||
}
|
||||
_variant_aliases = dict(
|
||||
[(unicode(k),k) for k in _variant_info] +
|
||||
[(v[0],k) for k,v in iteritems(_variant_info)]
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# configuration
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def using(cls, variant=None, **kwds):
|
||||
subcls = super(fshp, cls).using(**kwds)
|
||||
if variant is not None:
|
||||
subcls.default_variant = cls._norm_variant(variant)
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
variant = None
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, variant=None, **kwds):
|
||||
# NOTE: variant must be set first, since it controls checksum size, etc.
|
||||
self.use_defaults = kwds.get("use_defaults") # load this early
|
||||
if variant is not None:
|
||||
variant = self._norm_variant(variant)
|
||||
elif self.use_defaults:
|
||||
variant = self.default_variant
|
||||
assert self._norm_variant(variant) == variant, "invalid default variant: %r" % (variant,)
|
||||
else:
|
||||
raise TypeError("no variant specified")
|
||||
self.variant = variant
|
||||
super(fshp, self).__init__(**kwds)
|
||||
|
||||
@classmethod
|
||||
def _norm_variant(cls, variant):
|
||||
if isinstance(variant, bytes):
|
||||
variant = variant.decode("ascii")
|
||||
if isinstance(variant, unicode):
|
||||
try:
|
||||
variant = cls._variant_aliases[variant]
|
||||
except KeyError:
|
||||
raise ValueError("invalid fshp variant")
|
||||
if not isinstance(variant, int):
|
||||
raise TypeError("fshp variant must be int or known alias")
|
||||
if variant not in cls._variant_info:
|
||||
raise ValueError("invalid fshp variant")
|
||||
return variant
|
||||
|
||||
@property
|
||||
def checksum_alg(self):
|
||||
return self._variant_info[self.variant][0]
|
||||
|
||||
@property
|
||||
def checksum_size(self):
|
||||
return self._variant_info[self.variant][1]
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
\{FSHP
|
||||
(\d+)\| # variant
|
||||
(\d+)\| # salt size
|
||||
(\d+)\} # rounds
|
||||
([a-zA-Z0-9+/]+={0,3}) # digest
|
||||
$"""), re.X)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
variant, salt_size, rounds, data = m.group(1,2,3,4)
|
||||
variant = int(variant)
|
||||
salt_size = int(salt_size)
|
||||
rounds = int(rounds)
|
||||
try:
|
||||
data = b64decode(data.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
salt = data[:salt_size]
|
||||
chk = data[salt_size:]
|
||||
return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)
|
||||
|
||||
def to_string(self):
|
||||
chk = self.checksum
|
||||
salt = self.salt
|
||||
data = bascii_to_str(b64encode(salt+chk))
|
||||
return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
# NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed.
|
||||
# this has only a minimal impact on security,
|
||||
# but it is worth noting this deviation.
|
||||
return pbkdf1(
|
||||
digest=self.checksum_alg,
|
||||
secret=self.salt,
|
||||
salt=secret,
|
||||
rounds=self.rounds,
|
||||
keylen=self.checksum_size,
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
359
venv/Lib/site-packages/passlib/handlers/ldap_digests.py
Normal file
359
venv/Lib/site-packages/passlib/handlers/ldap_digests.py
Normal file
@@ -0,0 +1,359 @@
|
||||
"""passlib.handlers.digests - plain hash digests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from base64 import b64encode, b64decode
|
||||
from hashlib import md5, sha1, sha256, sha512
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
# site
|
||||
# pkg
|
||||
from passlib.handlers.misc import plaintext
|
||||
from passlib.utils import unix_crypt_schemes, to_unicode
|
||||
from passlib.utils.compat import uascii_to_str, unicode, u
|
||||
from passlib.utils.decor import classproperty
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"ldap_plaintext",
|
||||
"ldap_md5",
|
||||
"ldap_sha1",
|
||||
"ldap_salted_md5",
|
||||
"ldap_salted_sha1",
|
||||
"ldap_salted_sha256",
|
||||
"ldap_salted_sha512",
|
||||
|
||||
##"get_active_ldap_crypt_schemes",
|
||||
"ldap_des_crypt",
|
||||
"ldap_bsdi_crypt",
|
||||
"ldap_md5_crypt",
|
||||
"ldap_sha1_crypt",
|
||||
"ldap_bcrypt",
|
||||
"ldap_sha256_crypt",
|
||||
"ldap_sha512_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# ldap helpers
|
||||
#=============================================================================
|
||||
class _Base64DigestHelper(uh.StaticHandler):
|
||||
"""helper for ldap_md5 / ldap_sha1"""
|
||||
# XXX: could combine this with hex digests in digests.py
|
||||
|
||||
ident = None # required - prefix identifier
|
||||
_hash_func = None # required - hash function
|
||||
_hash_regex = None # required - regexp to recognize hash
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
|
||||
@classproperty
|
||||
def _hash_prefix(cls):
|
||||
"""tell StaticHandler to strip ident from checksum"""
|
||||
return cls.ident
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = self._hash_func(secret).digest()
|
||||
return b64encode(chk).decode("ascii")
|
||||
|
||||
class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""helper for ldap_salted_md5 / ldap_salted_sha1"""
|
||||
setting_kwds = ("salt", "salt_size")
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
|
||||
ident = None # required - prefix identifier
|
||||
_hash_func = None # required - hash function
|
||||
_hash_regex = None # required - regexp to recognize hash
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
# NOTE: openldap implementation uses 4 byte salt,
|
||||
# but it's been reported (issue 30) that some servers use larger salts.
|
||||
# the semi-related rfc3112 recommends support for up to 16 byte salts.
|
||||
min_salt_size = 4
|
||||
default_salt_size = 4
|
||||
max_salt_size = 16
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
try:
|
||||
data = b64decode(m.group("tmp").encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
cs = cls.checksum_size
|
||||
assert cs
|
||||
return cls(checksum=data[:cs], salt=data[cs:])
|
||||
|
||||
def to_string(self):
|
||||
data = self.checksum + self.salt
|
||||
hash = self.ident + b64encode(data).decode("ascii")
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return self._hash_func(secret + self.salt).digest()
|
||||
|
||||
#=============================================================================
|
||||
# implementations
|
||||
#=============================================================================
|
||||
class ldap_md5(_Base64DigestHelper):
|
||||
"""This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
|
||||
"""
|
||||
name = "ldap_md5"
|
||||
ident = u("{MD5}")
|
||||
_hash_func = md5
|
||||
_hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$"))
|
||||
|
||||
class ldap_sha1(_Base64DigestHelper):
|
||||
"""This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
|
||||
"""
|
||||
name = "ldap_sha1"
|
||||
ident = u("{SHA}")
|
||||
_hash_func = sha1
|
||||
_hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$"))
|
||||
|
||||
class ldap_salted_md5(_SaltedBase64DigestHelper):
|
||||
"""This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 4 bytes for compatibility with the LDAP spec,
|
||||
but some systems use larger salts, and Passlib supports
|
||||
any value between 4-16.
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This format now supports variable length salts, instead of a fix 4 bytes.
|
||||
"""
|
||||
name = "ldap_salted_md5"
|
||||
ident = u("{SMD5}")
|
||||
checksum_size = 16
|
||||
_hash_func = md5
|
||||
_hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
|
||||
|
||||
class ldap_salted_sha1(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA1" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 4 bytes for compatibility with the LDAP spec,
|
||||
but some systems use larger salts, and Passlib supports
|
||||
any value between 4-16.
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This format now supports variable length salts, instead of a fix 4 bytes.
|
||||
"""
|
||||
name = "ldap_salted_sha1"
|
||||
ident = u("{SSHA}")
|
||||
checksum_size = 20
|
||||
_hash_func = sha1
|
||||
# NOTE: 32 = ceil((20 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
|
||||
|
||||
|
||||
|
||||
class ldap_salted_sha256(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA2-256" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 8 bytes for compatibility with the LDAP spec,
|
||||
but Passlib supports any value between 4-16.
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
name = "ldap_salted_sha256"
|
||||
ident = u("{SSHA256}")
|
||||
checksum_size = 32
|
||||
default_salt_size = 8
|
||||
_hash_func = sha256
|
||||
# NOTE: 48 = ceil((32 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$"))
|
||||
|
||||
|
||||
class ldap_salted_sha512(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA2-512" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 8 bytes for compatibility with the LDAP spec,
|
||||
but Passlib supports any value between 4-16.
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
name = "ldap_salted_sha512"
|
||||
ident = u("{SSHA512}")
|
||||
checksum_size = 64
|
||||
default_salt_size = 8
|
||||
_hash_func = sha512
|
||||
# NOTE: 91 = ceil((64 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$"))
|
||||
|
||||
|
||||
class ldap_plaintext(plaintext):
|
||||
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class acts much like the generic :class:`!passlib.hash.plaintext` handler,
|
||||
except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix
|
||||
used by RFC2307 passwords.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keyword:
|
||||
|
||||
:type encoding: str
|
||||
:param encoding:
|
||||
This controls the character encoding to use (defaults to ``utf-8``).
|
||||
|
||||
This encoding will be used to encode :class:`!unicode` passwords
|
||||
under Python 2, and decode :class:`!bytes` hashes under Python 3.
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
The ``encoding`` keyword was added.
|
||||
"""
|
||||
# NOTE: this subclasses plaintext, since all it does differently
|
||||
# is override identify()
|
||||
|
||||
name = "ldap_plaintext"
|
||||
_2307_pat = re.compile(u(r"^\{\w+\}.*$"))
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls):
|
||||
# Overridding plaintext.genconfig() since it returns "",
|
||||
# but have to return non-empty value due to identify() below
|
||||
return "!"
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
# NOTE: identifies all strings EXCEPT those with {XXX} prefix
|
||||
hash = uh.to_unicode_for_identify(hash)
|
||||
return bool(hash) and cls._2307_pat.match(hash) is None
|
||||
|
||||
#=============================================================================
|
||||
# {CRYPT} wrappers
|
||||
# the following are wrappers around the base crypt algorithms,
|
||||
# which add the ldap required {CRYPT} prefix
|
||||
#=============================================================================
|
||||
ldap_crypt_schemes = [ 'ldap_' + name for name in unix_crypt_schemes ]
|
||||
|
||||
def _init_ldap_crypt_handlers():
|
||||
# NOTE: I don't like to implicitly modify globals() like this,
|
||||
# but don't want to write out all these handlers out either :)
|
||||
g = globals()
|
||||
for wname in unix_crypt_schemes:
|
||||
name = 'ldap_' + wname
|
||||
g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True)
|
||||
del g
|
||||
_init_ldap_crypt_handlers()
|
||||
|
||||
##_lcn_host = None
|
||||
##def get_host_ldap_crypt_schemes():
|
||||
## global _lcn_host
|
||||
## if _lcn_host is None:
|
||||
## from passlib.hosts import host_context
|
||||
## schemes = host_context.schemes()
|
||||
## _lcn_host = [
|
||||
## "ldap_" + name
|
||||
## for name in unix_crypt_names
|
||||
## if name in schemes
|
||||
## ]
|
||||
## return _lcn_host
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
346
venv/Lib/site-packages/passlib/handlers/md5_crypt.py
Normal file
346
venv/Lib/site-packages/passlib/handlers/md5_crypt.py
Normal file
@@ -0,0 +1,346 @@
|
||||
"""passlib.handlers.md5_crypt - md5-crypt algorithm"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt, repeat_string
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"md5_crypt",
|
||||
"apr_md5_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# pure-python backend
|
||||
#=============================================================================
|
||||
_BNULL = b"\x00"
|
||||
_MD5_MAGIC = b"$1$"
|
||||
_APR_MAGIC = b"$apr1$"
|
||||
|
||||
# pre-calculated offsets used to speed up C digest stage (see notes below).
|
||||
# sequence generated using the following:
|
||||
##perms_order = "p,pp,ps,psp,sp,spp".split(",")
|
||||
##def offset(i):
|
||||
## key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
|
||||
## ("p" if i % 7 else "") + ("" if i % 2 else "p"))
|
||||
## return perms_order.index(key)
|
||||
##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
|
||||
_c_digest_offsets = (
|
||||
(0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
|
||||
(4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
|
||||
(4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
|
||||
)
|
||||
|
||||
# map used to transpose bytes when encoding final digest
|
||||
_transpose_map = (12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11)
|
||||
|
||||
def _raw_md5_crypt(pwd, salt, use_apr=False):
|
||||
"""perform raw md5-crypt calculation
|
||||
|
||||
this function provides a pure-python implementation of the internals
|
||||
for the MD5-Crypt algorithms; it doesn't handle any of the
|
||||
parsing/validation of the hash strings themselves.
|
||||
|
||||
:arg pwd: password chars/bytes to hash
|
||||
:arg salt: salt chars to use
|
||||
:arg use_apr: use apache variant
|
||||
|
||||
:returns:
|
||||
encoded checksum chars
|
||||
"""
|
||||
# NOTE: regarding 'apr' format:
|
||||
# really, apache? you had to invent a whole new "$apr1$" format,
|
||||
# when all you did was change the ident incorporated into the hash?
|
||||
# would love to find webpage explaining why just using a portable
|
||||
# implementation of $1$ wasn't sufficient. *nothing else* was changed.
|
||||
|
||||
#===================================================================
|
||||
# init & validate inputs
|
||||
#===================================================================
|
||||
|
||||
# validate secret
|
||||
# XXX: not sure what official unicode policy is, using this as default
|
||||
if isinstance(pwd, unicode):
|
||||
pwd = pwd.encode("utf-8")
|
||||
assert isinstance(pwd, bytes), "pwd not unicode or bytes"
|
||||
if _BNULL in pwd:
|
||||
raise uh.exc.NullPasswordError(md5_crypt)
|
||||
pwd_len = len(pwd)
|
||||
|
||||
# validate salt - should have been taken care of by caller
|
||||
assert isinstance(salt, unicode), "salt not unicode"
|
||||
salt = salt.encode("ascii")
|
||||
assert len(salt) < 9, "salt too large"
|
||||
# NOTE: spec says salts larger than 8 bytes should be truncated,
|
||||
# instead of causing an error. this function assumes that's been
|
||||
# taken care of by the handler class.
|
||||
|
||||
# load APR specific constants
|
||||
if use_apr:
|
||||
magic = _APR_MAGIC
|
||||
else:
|
||||
magic = _MD5_MAGIC
|
||||
|
||||
#===================================================================
|
||||
# digest B - used as subinput to digest A
|
||||
#===================================================================
|
||||
db = md5(pwd + salt + pwd).digest()
|
||||
|
||||
#===================================================================
|
||||
# digest A - used to initialize first round of digest C
|
||||
#===================================================================
|
||||
# start out with pwd + magic + salt
|
||||
a_ctx = md5(pwd + magic + salt)
|
||||
a_ctx_update = a_ctx.update
|
||||
|
||||
# add pwd_len bytes of b, repeating b as many times as needed.
|
||||
a_ctx_update(repeat_string(db, pwd_len))
|
||||
|
||||
# add null chars & first char of password
|
||||
# NOTE: this may have historically been a bug,
|
||||
# where they meant to use db[0] instead of B_NULL,
|
||||
# but the original code memclear'ed db,
|
||||
# and now all implementations have to use this.
|
||||
i = pwd_len
|
||||
evenchar = pwd[:1]
|
||||
while i:
|
||||
a_ctx_update(_BNULL if i & 1 else evenchar)
|
||||
i >>= 1
|
||||
|
||||
# finish A
|
||||
da = a_ctx.digest()
|
||||
|
||||
#===================================================================
|
||||
# digest C - for a 1000 rounds, combine A, S, and P
|
||||
# digests in various ways; in order to burn CPU time.
|
||||
#===================================================================
|
||||
|
||||
# NOTE: the original MD5-Crypt implementation performs the C digest
|
||||
# calculation using the following loop:
|
||||
#
|
||||
##dc = da
|
||||
##i = 0
|
||||
##while i < rounds:
|
||||
## tmp_ctx = md5(pwd if i & 1 else dc)
|
||||
## if i % 3:
|
||||
## tmp_ctx.update(salt)
|
||||
## if i % 7:
|
||||
## tmp_ctx.update(pwd)
|
||||
## tmp_ctx.update(dc if i & 1 else pwd)
|
||||
## dc = tmp_ctx.digest()
|
||||
## i += 1
|
||||
#
|
||||
# The code Passlib uses (below) implements an equivalent algorithm,
|
||||
# it's just been heavily optimized to pre-calculate a large number
|
||||
# of things beforehand. It works off of a couple of observations
|
||||
# about the original algorithm:
|
||||
#
|
||||
# 1. each round is a combination of 'dc', 'salt', and 'pwd'; and the exact
|
||||
# combination is determined by whether 'i' a multiple of 2,3, and/or 7.
|
||||
# 2. since lcm(2,3,7)==42, the series of combinations will repeat
|
||||
# every 42 rounds.
|
||||
# 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
|
||||
# while odd rounds 1-41 consist of hash(round-specific-constant + dc)
|
||||
#
|
||||
# Using these observations, the following code...
|
||||
# * calculates the round-specific combination of salt & pwd for each round 0-41
|
||||
# * runs through as many 42-round blocks as possible (23)
|
||||
# * runs through as many pairs of rounds as needed for remaining rounds (17)
|
||||
# * this results in the required 42*23+2*17=1000 rounds required by md5_crypt.
|
||||
#
|
||||
# this cuts out a lot of the control overhead incurred when running the
|
||||
# original loop 1000 times in python, resulting in ~20% increase in
|
||||
# speed under CPython (though still 2x slower than glibc crypt)
|
||||
|
||||
# prepare the 6 combinations of pwd & salt which are needed
|
||||
# (order of 'perms' must match how _c_digest_offsets was generated)
|
||||
pwd_pwd = pwd+pwd
|
||||
pwd_salt = pwd+salt
|
||||
perms = [pwd, pwd_pwd, pwd_salt, pwd_salt+pwd, salt+pwd, salt+pwd_pwd]
|
||||
|
||||
# build up list of even-round & odd-round constants,
|
||||
# and store in 21-element list as (even,odd) pairs.
|
||||
data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
|
||||
|
||||
# perform 23 blocks of 42 rounds each (for a total of 966 rounds)
|
||||
dc = da
|
||||
blocks = 23
|
||||
while blocks:
|
||||
for even, odd in data:
|
||||
dc = md5(odd + md5(dc + even).digest()).digest()
|
||||
blocks -= 1
|
||||
|
||||
# perform 17 more pairs of rounds (34 more rounds, for a total of 1000)
|
||||
for even, odd in data[:17]:
|
||||
dc = md5(odd + md5(dc + even).digest()).digest()
|
||||
|
||||
#===================================================================
|
||||
# encode digest using appropriate transpose map
|
||||
#===================================================================
|
||||
return h64.encode_transposed_bytes(dc, _transpose_map).decode("ascii")
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class _MD5_Common(uh.HasSalt, uh.GenericHandler):
|
||||
"""common code for md5_crypt and apr_md5_crypt"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
# name - set in subclass
|
||||
setting_kwds = ("salt", "salt_size")
|
||||
# ident - set in subclass
|
||||
checksum_size = 22
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
max_salt_size = 8
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
|
||||
@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)
|
||||
|
||||
# _calc_checksum() - provided by subclass
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class md5_crypt(uh.HasManyBackends, _MD5_Common):
|
||||
"""This class implements the MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts 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 0-8 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 8, but can be any value between 0 and 8.
|
||||
(This is mainly needed when generating Cisco-compatible hashes,
|
||||
which require ``salt_size=4``).
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "md5_crypt"
|
||||
ident = u("$1$")
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
# FIXME: can't find definitive policy on how md5-crypt handles non-ascii.
|
||||
# all backends currently coerce -> utf-8
|
||||
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", '$1$test$pi/xDtU5WFVRqYS6BMU8X/'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.ident + self.salt
|
||||
hash = safe_crypt(secret, config)
|
||||
if hash is None:
|
||||
# py3's crypt.crypt() can't handle non-utf8 bytes.
|
||||
# fallback to builtin alg, which is always available.
|
||||
return self._calc_checksum_builtin(secret)
|
||||
if not hash.startswith(config) or len(hash) != len(config) + 23:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-22:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# builtin backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_builtin(cls):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
|
||||
return True
|
||||
|
||||
def _calc_checksum_builtin(self, secret):
|
||||
return _raw_md5_crypt(secret, self.salt)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class apr_md5_crypt(_MD5_Common):
|
||||
"""This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts 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 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
: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
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "apr_md5_crypt"
|
||||
ident = u("$apr1$")
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
return _raw_md5_crypt(secret, self.salt, use_apr=True)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
Reference in New Issue
Block a user