mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Initial commit
This commit is contained in:
1
venv/Lib/site-packages/passlib/handlers/__init__.py
Normal file
1
venv/Lib/site-packages/passlib/handlers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""passlib.handlers -- holds implementations of all passlib's builtin hash formats"""
|
1009
venv/Lib/site-packages/passlib/handlers/argon2.py
Normal file
1009
venv/Lib/site-packages/passlib/handlers/argon2.py
Normal file
File diff suppressed because it is too large
Load Diff
1243
venv/Lib/site-packages/passlib/handlers/bcrypt.py
Normal file
1243
venv/Lib/site-packages/passlib/handlers/bcrypt.py
Normal file
File diff suppressed because it is too large
Load Diff
440
venv/Lib/site-packages/passlib/handlers/cisco.py
Normal file
440
venv/Lib/site-packages/passlib/handlers/cisco.py
Normal file
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
passlib.handlers.cisco -- Cisco password hashes
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import right_pad_string, to_unicode, repeat_string, to_bytes
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import unicode, u, join_byte_values, \
|
||||
join_byte_elems, iter_byte_values, uascii_to_str
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"cisco_pix",
|
||||
"cisco_asa",
|
||||
"cisco_type7",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# utils
|
||||
#=============================================================================
|
||||
|
||||
#: dummy bytes used by spoil_digest var in cisco_pix._calc_checksum()
|
||||
_DUMMY_BYTES = b'\xFF' * 32
|
||||
|
||||
#=============================================================================
|
||||
# cisco pix firewall hash
|
||||
#=============================================================================
|
||||
class cisco_pix(uh.HasUserContext, uh.StaticHandler):
|
||||
"""
|
||||
This class implements the password hash used by older Cisco PIX firewalls,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
It does a single round of hashing, and relies on the username
|
||||
as the salt.
|
||||
|
||||
This class only allows passwords <= 16 bytes, anything larger
|
||||
will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_pix.hash`,
|
||||
and be silently rejected if passed to :meth:`~cisco_pix.verify`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`,
|
||||
:meth:`~passlib.ifc.PasswordHash.genhash`, and
|
||||
:meth:`~passlib.ifc.PasswordHash.verify` methods
|
||||
all support the following extra keyword:
|
||||
|
||||
:param str user:
|
||||
String containing name of user account this password is associated with.
|
||||
|
||||
This is *required* in order to correctly hash passwords associated
|
||||
with a user account on the Cisco device, as it is used to salt
|
||||
the hash.
|
||||
|
||||
Conversely, this *must* be omitted or set to ``""`` in order to correctly
|
||||
hash passwords which don't have an associated user account
|
||||
(such as the "enable" password).
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.7.1
|
||||
|
||||
Passwords > 16 bytes are now rejected / throw error instead of being silently truncated,
|
||||
to match Cisco behavior. A number of :ref:`bugs <passlib-asa96-bug>` were fixed
|
||||
which caused prior releases to generate unverifiable hashes in certain cases.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "cisco_pix"
|
||||
|
||||
truncate_size = 16
|
||||
|
||||
# NOTE: these are the default policy for PasswordHash,
|
||||
# but want to set them explicitly for now.
|
||||
truncate_error = True
|
||||
truncate_verify_reject = True
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_size = 16
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# custom
|
||||
#--------------------
|
||||
|
||||
#: control flag signalling "cisco_asa" mode, set by cisco_asa class
|
||||
_is_asa = False
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
"""
|
||||
This function implements the "encrypted" hash format used by Cisco
|
||||
PIX & ASA. It's behavior has been confirmed for ASA 9.6,
|
||||
but is presumed correct for PIX & other ASA releases,
|
||||
as it fits with known test vectors, and existing literature.
|
||||
|
||||
While nearly the same, the PIX & ASA hashes have slight differences,
|
||||
so this function performs differently based on the _is_asa class flag.
|
||||
Noteable changes from PIX to ASA include password size limit
|
||||
increased from 16 -> 32, and other internal changes.
|
||||
"""
|
||||
# select PIX vs or ASA mode
|
||||
asa = self._is_asa
|
||||
|
||||
#
|
||||
# encode secret
|
||||
#
|
||||
# per ASA 8.4 documentation,
|
||||
# http://www.cisco.com/c/en/us/td/docs/security/asa/asa84/configuration/guide/asa_84_cli_config/ref_cli.html#Supported_Character_Sets,
|
||||
# it supposedly uses UTF-8 -- though some double-encoding issues have
|
||||
# been observed when trying to actually *set* a non-ascii password
|
||||
# via ASDM, and access via SSH seems to strip 8-bit chars.
|
||||
#
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
|
||||
#
|
||||
# check if password too large
|
||||
#
|
||||
# Per ASA 9.6 changes listed in
|
||||
# http://www.cisco.com/c/en/us/td/docs/security/asa/roadmap/asa_new_features.html,
|
||||
# prior releases had a maximum limit of 32 characters.
|
||||
# Testing with an ASA 9.6 system bears this out --
|
||||
# setting 32-char password for a user account,
|
||||
# and logins will fail if any chars are appended.
|
||||
# (ASA 9.6 added new PBKDF2-based hash algorithm,
|
||||
# which supports larger passwords).
|
||||
#
|
||||
# Per PIX documentation
|
||||
# http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html,
|
||||
# it would not allow passwords > 16 chars.
|
||||
#
|
||||
# Thus, we unconditionally throw a password size error here,
|
||||
# as nothing valid can come from a larger password.
|
||||
# NOTE: assuming PIX has same behavior, but at 16 char limit.
|
||||
#
|
||||
spoil_digest = None
|
||||
if len(secret) > self.truncate_size:
|
||||
if self.use_defaults:
|
||||
# called from hash()
|
||||
msg = "Password too long (%s allows at most %d bytes)" % \
|
||||
(self.name, self.truncate_size)
|
||||
raise uh.exc.PasswordSizeError(self.truncate_size, msg=msg)
|
||||
else:
|
||||
# called from verify() --
|
||||
# We don't want to throw error, or return early,
|
||||
# as that would let attacker know too much. Instead, we set a
|
||||
# flag to add some dummy data into the md5 digest, so that
|
||||
# output won't match truncated version of secret, or anything
|
||||
# else that's fixed and predictable.
|
||||
spoil_digest = secret + _DUMMY_BYTES
|
||||
|
||||
#
|
||||
# append user to secret
|
||||
#
|
||||
# Policy appears to be:
|
||||
#
|
||||
# * Nothing appended for enable password (user = "")
|
||||
#
|
||||
# * ASA: If user present, but secret is >= 28 chars, nothing appended.
|
||||
#
|
||||
# * 1-2 byte users not allowed.
|
||||
# DEVIATION: we're letting them through, and repeating their
|
||||
# chars ala 3-char user, to simplify testing.
|
||||
# Could issue warning in the future though.
|
||||
#
|
||||
# * 3 byte user has first char repeated, to pad to 4.
|
||||
# (observed under ASA 9.6, assuming true elsewhere)
|
||||
#
|
||||
# * 4 byte users are used directly.
|
||||
#
|
||||
# * 5+ byte users are truncated to 4 bytes.
|
||||
#
|
||||
user = self.user
|
||||
if user:
|
||||
if isinstance(user, unicode):
|
||||
user = user.encode("utf-8")
|
||||
if not asa or len(secret) < 28:
|
||||
secret += repeat_string(user, 4)
|
||||
|
||||
#
|
||||
# pad / truncate result to limit
|
||||
#
|
||||
# While PIX always pads to 16 bytes, ASA increases to 32 bytes IFF
|
||||
# secret+user > 16 bytes. This makes PIX & ASA have different results
|
||||
# where secret size in range(13,16), and user is present --
|
||||
# PIX will truncate to 16, ASA will truncate to 32.
|
||||
#
|
||||
if asa and len(secret) > 16:
|
||||
pad_size = 32
|
||||
else:
|
||||
pad_size = 16
|
||||
secret = right_pad_string(secret, pad_size)
|
||||
|
||||
#
|
||||
# md5 digest
|
||||
#
|
||||
if spoil_digest:
|
||||
# make sure digest won't match truncated version of secret
|
||||
secret += spoil_digest
|
||||
digest = md5(secret).digest()
|
||||
|
||||
#
|
||||
# drop every 4th byte
|
||||
# NOTE: guessing this was done because it makes output exactly
|
||||
# 16 bytes, which may have been a general 'char password[]'
|
||||
# size limit under PIX
|
||||
#
|
||||
digest = join_byte_elems(c for i, c in enumerate(digest) if (i + 1) & 3)
|
||||
|
||||
#
|
||||
# encode using Hash64
|
||||
#
|
||||
return h64.encode_bytes(digest).decode("ascii")
|
||||
|
||||
# NOTE: works, but needs UTs.
|
||||
# @classmethod
|
||||
# def same_as_pix(cls, secret, user=""):
|
||||
# """
|
||||
# test whether (secret + user) combination should
|
||||
# have the same hash under PIX and ASA.
|
||||
#
|
||||
# mainly present to help unittests.
|
||||
# """
|
||||
# # see _calc_checksum() above for details of this logic.
|
||||
# size = len(to_bytes(secret, "utf-8"))
|
||||
# if user and size < 28:
|
||||
# size += 4
|
||||
# return size < 17
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
|
||||
class cisco_asa(cisco_pix):
|
||||
"""
|
||||
This class implements the password hash used by Cisco ASA/PIX 7.0 and newer (2005).
|
||||
Aside from a different internal algorithm, it's use and format is identical
|
||||
to the older :class:`cisco_pix` class.
|
||||
|
||||
For passwords less than 13 characters, this should be identical to :class:`!cisco_pix`,
|
||||
but will generate a different hash for most larger inputs
|
||||
(See the `Format & Algorithm`_ section for the details).
|
||||
|
||||
This class only allows passwords <= 32 bytes, anything larger
|
||||
will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_asa.hash`,
|
||||
and be silently rejected if passed to :meth:`~cisco_asa.verify`.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
.. versionchanged:: 1.7.1
|
||||
|
||||
Passwords > 32 bytes are now rejected / throw error instead of being silently truncated,
|
||||
to match Cisco behavior. A number of :ref:`bugs <passlib-asa96-bug>` were fixed
|
||||
which caused prior releases to generate unverifiable hashes in certain cases.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "cisco_asa"
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 32
|
||||
|
||||
#--------------------
|
||||
# cisco_pix
|
||||
#--------------------
|
||||
_is_asa = True
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# type 7
|
||||
#=============================================================================
|
||||
class cisco_type7(uh.GenericHandler):
|
||||
"""
|
||||
This class implements the "Type 7" password encoding used by Cisco IOS,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
It has a simple 4-5 bit salt, but is nonetheless a reversible encoding
|
||||
instead of a real hash.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: int
|
||||
:param salt:
|
||||
This may be an optional salt integer drawn from ``range(0,16)``.
|
||||
If omitted, one will be chosen at random.
|
||||
|
||||
: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`` values that are out of range.
|
||||
|
||||
Note that while this class outputs digests in upper-case hexadecimal,
|
||||
it will accept lower-case as well.
|
||||
|
||||
This class also provides the following additional method:
|
||||
|
||||
.. automethod:: decode
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "cisco_type7"
|
||||
setting_kwds = ("salt",)
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_chars = uh.UPPER_HEX_CHARS
|
||||
|
||||
#--------------------
|
||||
# HasSalt
|
||||
#--------------------
|
||||
|
||||
# NOTE: encoding could handle max_salt_value=99, but since key is only 52
|
||||
# chars in size, not sure what appropriate behavior is for that edge case.
|
||||
min_salt_value = 0
|
||||
max_salt_value = 52
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def using(cls, salt=None, **kwds):
|
||||
subcls = super(cisco_type7, cls).using(**kwds)
|
||||
if salt is not None:
|
||||
salt = subcls._norm_salt(salt, relaxed=kwds.get("relaxed"))
|
||||
subcls._generate_salt = staticmethod(lambda: salt)
|
||||
return subcls
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
if len(hash) < 2:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
salt = int(hash[:2]) # may throw ValueError
|
||||
return cls(salt=salt, checksum=hash[2:].upper())
|
||||
|
||||
def __init__(self, salt=None, **kwds):
|
||||
super(cisco_type7, self).__init__(**kwds)
|
||||
if salt is not None:
|
||||
salt = self._norm_salt(salt)
|
||||
elif self.use_defaults:
|
||||
salt = self._generate_salt()
|
||||
assert self._norm_salt(salt) == salt, "generated invalid salt: %r" % (salt,)
|
||||
else:
|
||||
raise TypeError("no salt specified")
|
||||
self.salt = salt
|
||||
|
||||
@classmethod
|
||||
def _norm_salt(cls, salt, relaxed=False):
|
||||
"""
|
||||
validate & normalize salt value.
|
||||
.. note::
|
||||
the salt for this algorithm is an integer 0-52, not a string
|
||||
"""
|
||||
if not isinstance(salt, int):
|
||||
raise uh.exc.ExpectedTypeError(salt, "integer", "salt")
|
||||
if 0 <= salt <= cls.max_salt_value:
|
||||
return salt
|
||||
msg = "salt/offset must be in 0..52 range"
|
||||
if relaxed:
|
||||
warn(msg, uh.PasslibHashWarning)
|
||||
return 0 if salt < 0 else cls.max_salt_value
|
||||
else:
|
||||
raise ValueError(msg)
|
||||
|
||||
@staticmethod
|
||||
def _generate_salt():
|
||||
return uh.rng.randint(0, 15)
|
||||
|
||||
def to_string(self):
|
||||
return "%02d%s" % (self.salt, uascii_to_str(self.checksum))
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# XXX: no idea what unicode policy is, but all examples are
|
||||
# 7-bit ascii compatible, so using UTF-8
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper()
|
||||
|
||||
@classmethod
|
||||
def decode(cls, hash, encoding="utf-8"):
|
||||
"""decode hash, returning original password.
|
||||
|
||||
:arg hash: encoded password
|
||||
:param encoding: optional encoding to use (defaults to ``UTF-8``).
|
||||
:returns: password as unicode
|
||||
"""
|
||||
self = cls.from_string(hash)
|
||||
tmp = unhexlify(self.checksum.encode("ascii"))
|
||||
raw = self._cipher(tmp, self.salt)
|
||||
return raw.decode(encoding) if encoding else raw
|
||||
|
||||
# type7 uses a xor-based vingere variant, using the following secret key:
|
||||
_key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87")
|
||||
|
||||
@classmethod
|
||||
def _cipher(cls, data, salt):
|
||||
"""xor static key against data - encrypts & decrypts"""
|
||||
key = cls._key
|
||||
key_size = len(key)
|
||||
return join_byte_values(
|
||||
value ^ ord(key[(salt + idx) % key_size])
|
||||
for idx, value in enumerate(iter_byte_values(data))
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
607
venv/Lib/site-packages/passlib/handlers/des_crypt.py
Normal file
607
venv/Lib/site-packages/passlib/handlers/des_crypt.py
Normal file
@@ -0,0 +1,607 @@
|
||||
"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt, to_unicode
|
||||
from passlib.utils.binary import h64, h64big
|
||||
from passlib.utils.compat import byte_elem_value, u, uascii_to_str, unicode, suppress_cause
|
||||
from passlib.crypto.des import des_encrypt_int_block
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"des_crypt",
|
||||
"bsdi_crypt",
|
||||
"bigcrypt",
|
||||
"crypt16",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# pure-python backend for des_crypt family
|
||||
#=============================================================================
|
||||
_BNULL = b'\x00'
|
||||
|
||||
def _crypt_secret_to_key(secret):
|
||||
"""convert secret to 64-bit DES key.
|
||||
|
||||
this only uses the first 8 bytes of the secret,
|
||||
and discards the high 8th bit of each byte at that.
|
||||
a null parity bit is inserted after every 7th bit of the output.
|
||||
"""
|
||||
# NOTE: this would set the parity bits correctly,
|
||||
# but des_encrypt_int_block() would just ignore them...
|
||||
##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
|
||||
## for i, c in enumerate(secret[:8]))
|
||||
return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
|
||||
for i, c in enumerate(secret[:8]))
|
||||
|
||||
def _raw_des_crypt(secret, salt):
|
||||
"""pure-python backed for des_crypt"""
|
||||
assert len(salt) == 2
|
||||
|
||||
# NOTE: some OSes will accept non-HASH64 characters in the salt,
|
||||
# but what value they assign these characters varies wildy,
|
||||
# so just rejecting them outright.
|
||||
# the same goes for single-character salts...
|
||||
# some OSes duplicate the char, some insert a '.' char,
|
||||
# and openbsd does (something) which creates an invalid hash.
|
||||
salt_value = h64.decode_int12(salt)
|
||||
|
||||
# gotta do something - no official policy since this predates unicode
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
assert isinstance(secret, bytes)
|
||||
|
||||
# forbidding NULL char because underlying crypt() rejects them too.
|
||||
if _BNULL in secret:
|
||||
raise uh.exc.NullPasswordError(des_crypt)
|
||||
|
||||
# convert first 8 bytes of secret string into an integer
|
||||
key_value = _crypt_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result = des_encrypt_int_block(key_value, 0, salt_value, 25)
|
||||
|
||||
# run h64 encode on result
|
||||
return h64big.encode_int64(result)
|
||||
|
||||
def _bsdi_secret_to_key(secret):
|
||||
"""convert secret to DES key used by bsdi_crypt"""
|
||||
key_value = _crypt_secret_to_key(secret)
|
||||
idx = 8
|
||||
end = len(secret)
|
||||
while idx < end:
|
||||
next = idx + 8
|
||||
tmp_value = _crypt_secret_to_key(secret[idx:next])
|
||||
key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
|
||||
idx = next
|
||||
return key_value
|
||||
|
||||
def _raw_bsdi_crypt(secret, rounds, salt):
|
||||
"""pure-python backend for bsdi_crypt"""
|
||||
|
||||
# decode salt
|
||||
salt_value = h64.decode_int24(salt)
|
||||
|
||||
# gotta do something - no official policy since this predates unicode
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
assert isinstance(secret, bytes)
|
||||
|
||||
# forbidding NULL char because underlying crypt() rejects them too.
|
||||
if _BNULL in secret:
|
||||
raise uh.exc.NullPasswordError(bsdi_crypt)
|
||||
|
||||
# convert secret string into an integer
|
||||
key_value = _bsdi_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
|
||||
|
||||
# run h64 encode on result
|
||||
return h64big.encode_int64(result)
|
||||
|
||||
#=============================================================================
|
||||
# handlers
|
||||
#=============================================================================
|
||||
class des_crypt(uh.TruncateMixin, uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, 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
|
||||
|
||||
: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
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "des_crypt"
|
||||
setting_kwds = ("salt", "truncate_error")
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
checksum_size = 11
|
||||
|
||||
#--------------------
|
||||
# HasSalt
|
||||
#--------------------
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 8
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
# FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
|
||||
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>[./a-z0-9]{11})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
salt, chk = hash[:2], hash[2:]
|
||||
return cls(salt=salt, checksum=chk or None)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# digest calculation
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
|
||||
return self._calc_checksum_backend(secret)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", 'abgOeLfPimXQo'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
# NOTE: we let safe_crypt() encode unicode secret -> utf8;
|
||||
# no official policy since des-crypt predates unicode
|
||||
hash = safe_crypt(secret, self.salt)
|
||||
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(self.salt) or len(hash) != 13:
|
||||
raise uh.exc.CryptBackendError(self, self.salt, hash)
|
||||
return hash[2:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# 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_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 5001, must be between 1 and 16777215, inclusive.
|
||||
|
||||
: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
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
:meth:`hash` will now issue a warning if an even number of rounds is used
|
||||
(see :ref:`bsdi-crypt-security-issues` regarding weak DES keys).
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "bsdi_crypt"
|
||||
setting_kwds = ("salt", "rounds")
|
||||
checksum_size = 11
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 4
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 5001
|
||||
min_rounds = 1
|
||||
max_rounds = 16777215 # (1<<24)-1
|
||||
rounds_cost = "linear"
|
||||
|
||||
# NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
|
||||
# but that seems to be an OS policy, not a algorithm limitation.
|
||||
|
||||
#===================================================================
|
||||
# parsing
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
_
|
||||
(?P<rounds>[./a-z0-9]{4})
|
||||
(?P<salt>[./a-z0-9]{4})
|
||||
(?P<chk>[./a-z0-9]{11})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@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)
|
||||
rounds, salt, chk = m.group("rounds", "salt", "chk")
|
||||
return cls(
|
||||
rounds=h64.decode_int24(rounds.encode("ascii")),
|
||||
salt=salt,
|
||||
checksum=chk,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"),
|
||||
self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# validation
|
||||
#===================================================================
|
||||
|
||||
# NOTE: keeping this flag for admin/choose_rounds.py script.
|
||||
# want to eventually expose rounds logic to that script in better way.
|
||||
_avoid_even_rounds = True
|
||||
|
||||
@classmethod
|
||||
def using(cls, **kwds):
|
||||
subcls = super(bsdi_crypt, cls).using(**kwds)
|
||||
if not subcls.default_rounds & 1:
|
||||
# issue warning if caller set an even 'rounds' value.
|
||||
warn("bsdi_crypt rounds should be odd, as even rounds may reveal weak DES keys",
|
||||
uh.exc.PasslibSecurityWarning)
|
||||
return subcls
|
||||
|
||||
@classmethod
|
||||
def _generate_rounds(cls):
|
||||
rounds = super(bsdi_crypt, cls)._generate_rounds()
|
||||
# ensure autogenerated rounds are always odd
|
||||
# NOTE: doing this even for default_rounds so needs_update() doesn't get
|
||||
# caught in a loop.
|
||||
# FIXME: this technically might generate a rounds value 1 larger
|
||||
# than the requested upper bound - but better to err on side of safety.
|
||||
return rounds|1
|
||||
|
||||
#===================================================================
|
||||
# migration
|
||||
#===================================================================
|
||||
|
||||
def _calc_needs_update(self, **kwds):
|
||||
# mark bsdi_crypt hashes as deprecated if they have even rounds.
|
||||
if not self.rounds & 1:
|
||||
return True
|
||||
# hand off to base implementation
|
||||
return super(bsdi_crypt, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# backends
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", '_/...lLDAxARksGCHin.'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.to_string()
|
||||
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[:9]) or len(hash) != 20:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-11:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# 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_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class bigcrypt(uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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 22 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
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "bigcrypt"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
# NOTE: checksum chars must be multiple of 11
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#===================================================================
|
||||
# internal helpers
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>([./a-z0-9]{11})+)?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@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)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _norm_checksum(self, checksum, relaxed=False):
|
||||
checksum = super(bigcrypt, self)._norm_checksum(checksum, relaxed=relaxed)
|
||||
if len(checksum) % 11:
|
||||
raise uh.exc.InvalidHashError(self)
|
||||
return checksum
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
|
||||
idx = 8
|
||||
end = len(secret)
|
||||
while idx < end:
|
||||
next = idx + 8
|
||||
chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
|
||||
idx = next
|
||||
return chk.decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, crypt16 will silently truncate passwords larger than 16 bytes.
|
||||
Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
: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
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "crypt16"
|
||||
setting_kwds = ("salt", "truncate_error")
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_size = 22
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# HasSalt
|
||||
#--------------------
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 16
|
||||
|
||||
#===================================================================
|
||||
# internal helpers
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>[./a-z0-9]{22})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@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)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
|
||||
# parse salt value
|
||||
try:
|
||||
salt_value = h64.decode_int12(self.salt.encode("ascii"))
|
||||
except ValueError: # pragma: no cover - caught by class
|
||||
raise suppress_cause(ValueError("invalid chars in salt"))
|
||||
|
||||
# convert first 8 byts of secret string into an integer,
|
||||
key1 = _crypt_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
|
||||
|
||||
# convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
|
||||
key2 = _crypt_secret_to_key(secret[8:16])
|
||||
|
||||
# run data through des using input of 0
|
||||
result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
|
||||
|
||||
# done
|
||||
chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
|
||||
return chk.decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
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
|
||||
#=============================================================================
|
269
venv/Lib/site-packages/passlib/handlers/misc.py
Normal file
269
venv/Lib/site-packages/passlib/handlers/misc.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""passlib.handlers.misc - misc generic handlers
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import sys
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str, str_consteq
|
||||
from passlib.utils.compat import unicode, u, unicode_or_bytes_types
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"unix_disabled",
|
||||
"unix_fallback",
|
||||
"plaintext",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class unix_fallback(uh.ifc.DisabledHash, uh.StaticHandler):
|
||||
"""This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class does not implement a hash, but instead provides fallback
|
||||
behavior as found in /etc/shadow on most unix variants.
|
||||
If used, should be the last scheme in the context.
|
||||
|
||||
* this class will positively identify all hash strings.
|
||||
* for security, passwords will always hash to ``!``.
|
||||
* it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used).
|
||||
* by default it rejects all passwords if the hash is an empty string,
|
||||
but if ``enable_wildcard=True`` is passed to verify(),
|
||||
all passwords will be allowed through if the hash is an empty string.
|
||||
|
||||
.. deprecated:: 1.6
|
||||
This has been deprecated due to its "wildcard" feature,
|
||||
and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
|
||||
"""
|
||||
name = "unix_fallback"
|
||||
context_kwds = ("enable_wildcard",)
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
if isinstance(hash, unicode_or_bytes_types):
|
||||
return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
|
||||
def __init__(self, enable_wildcard=False, **kwds):
|
||||
warn("'unix_fallback' is deprecated, "
|
||||
"and will be removed in Passlib 1.8; "
|
||||
"please use 'unix_disabled' instead.",
|
||||
DeprecationWarning)
|
||||
super(unix_fallback, self).__init__(**kwds)
|
||||
self.enable_wildcard = enable_wildcard
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if self.checksum:
|
||||
# NOTE: hash will generally be "!", but we want to preserve
|
||||
# it in case it's something else, like "*".
|
||||
return self.checksum
|
||||
else:
|
||||
return u("!")
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, enable_wildcard=False):
|
||||
uh.validate_secret(secret)
|
||||
if not isinstance(hash, unicode_or_bytes_types):
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
elif hash:
|
||||
return False
|
||||
else:
|
||||
return enable_wildcard
|
||||
|
||||
_MARKER_CHARS = u("*!")
|
||||
_MARKER_BYTES = b"*!"
|
||||
|
||||
class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler):
|
||||
"""This class provides disabled password behavior for unix shadow files,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class does not implement a hash, but instead matches the "disabled account"
|
||||
strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
|
||||
will simply return the disabled account marker. It will reject all passwords,
|
||||
no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
method supports one optional keyword:
|
||||
|
||||
:type marker: str
|
||||
:param marker:
|
||||
Optional marker string which overrides the platform default
|
||||
used to indicate a disabled account.
|
||||
|
||||
If not specified, this will default to ``"*"`` on BSD systems,
|
||||
and use the Linux default ``"!"`` for all other platforms.
|
||||
(:attr:`!unix_disabled.default_marker` will contain the default value)
|
||||
|
||||
.. versionadded:: 1.6
|
||||
This class was added as a replacement for the now-deprecated
|
||||
:class:`unix_fallback` class, which had some undesirable features.
|
||||
"""
|
||||
name = "unix_disabled"
|
||||
setting_kwds = ("marker",)
|
||||
context_kwds = ()
|
||||
|
||||
_disable_prefixes = tuple(str(_MARKER_CHARS))
|
||||
|
||||
# TODO: rename attr to 'marker'...
|
||||
if 'bsd' in sys.platform: # pragma: no cover -- runtime detection
|
||||
default_marker = u("*")
|
||||
else:
|
||||
# use the linux default for other systems
|
||||
# (glibc also supports adding old hash after the marker
|
||||
# so it can be restored later).
|
||||
default_marker = u("!")
|
||||
|
||||
@classmethod
|
||||
def using(cls, marker=None, **kwds):
|
||||
subcls = super(unix_disabled, cls).using(**kwds)
|
||||
if marker is not None:
|
||||
if not cls.identify(marker):
|
||||
raise ValueError("invalid marker: %r" % marker)
|
||||
subcls.default_marker = marker
|
||||
return subcls
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
# NOTE: technically, anything in the /etc/shadow password field
|
||||
# which isn't valid crypt() output counts as "disabled".
|
||||
# but that's rather ambiguous, and it's hard to predict what
|
||||
# valid output is for unknown crypt() implementations.
|
||||
# so to be on the safe side, we only match things *known*
|
||||
# to be disabled field indicators, and will add others
|
||||
# as they are found. things beginning w/ "$" should *never* match.
|
||||
#
|
||||
# things currently matched:
|
||||
# * linux uses "!"
|
||||
# * bsd uses "*"
|
||||
# * linux may use "!" + hash to disable but preserve original hash
|
||||
# * linux counts empty string as "any password";
|
||||
# this code recognizes it, but treats it the same as "!"
|
||||
if isinstance(hash, unicode):
|
||||
start = _MARKER_CHARS
|
||||
elif isinstance(hash, bytes):
|
||||
start = _MARKER_BYTES
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
return not hash or hash[0] in start
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
uh.validate_secret(secret)
|
||||
if not cls.identify(hash): # handles typecheck
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, **kwds):
|
||||
if kwds:
|
||||
uh.warn_hash_settings_deprecation(cls, kwds)
|
||||
return cls.using(**kwds).hash(secret)
|
||||
uh.validate_secret(secret)
|
||||
marker = cls.default_marker
|
||||
assert marker and cls.identify(marker)
|
||||
return to_native_str(marker, param="marker")
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, marker=None):
|
||||
if not cls.identify(config):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
elif config:
|
||||
# preserve the existing str,since it might contain a disabled password hash ("!" + hash)
|
||||
uh.validate_secret(secret)
|
||||
return to_native_str(config, param="config")
|
||||
else:
|
||||
if marker is not None:
|
||||
cls = cls.using(marker=marker)
|
||||
return cls.hash(secret)
|
||||
|
||||
@classmethod
|
||||
def disable(cls, hash=None):
|
||||
out = cls.hash("")
|
||||
if hash is not None:
|
||||
hash = to_native_str(hash, param="hash")
|
||||
if cls.identify(hash):
|
||||
# extract original hash, so that we normalize marker
|
||||
hash = cls.enable(hash)
|
||||
if hash:
|
||||
out += hash
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def enable(cls, hash):
|
||||
hash = to_native_str(hash, param="hash")
|
||||
for prefix in cls._disable_prefixes:
|
||||
if hash.startswith(prefix):
|
||||
orig = hash[len(prefix):]
|
||||
if orig:
|
||||
return orig
|
||||
else:
|
||||
raise ValueError("cannot restore original hash")
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
class plaintext(uh.MinimalHandler):
|
||||
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
|
||||
|
||||
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 is subclassed by ldap_plaintext
|
||||
|
||||
name = "plaintext"
|
||||
setting_kwds = ()
|
||||
context_kwds = ("encoding",)
|
||||
default_encoding = "utf-8"
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
if isinstance(hash, unicode_or_bytes_types):
|
||||
return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, encoding=None):
|
||||
uh.validate_secret(secret)
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
return to_native_str(secret, encoding, "secret")
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, encoding=None):
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
hash = to_native_str(hash, encoding, "hash")
|
||||
if not cls.identify(hash):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return str_consteq(cls.hash(secret, encoding), hash)
|
||||
|
||||
@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, encoding=None):
|
||||
# NOTE: 'config' is ignored, as this hash has no salting / etc
|
||||
if not cls.identify(config):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return cls.hash(secret, encoding=encoding)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
244
venv/Lib/site-packages/passlib/handlers/mssql.py
Normal file
244
venv/Lib/site-packages/passlib/handlers/mssql.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""passlib.handlers.mssql - MS-SQL Password Hash
|
||||
|
||||
Notes
|
||||
=====
|
||||
MS-SQL has used a number of hash algs over the years,
|
||||
most of which were exposed through the undocumented
|
||||
'pwdencrypt' and 'pwdcompare' sql functions.
|
||||
|
||||
Known formats
|
||||
-------------
|
||||
6.5
|
||||
snefru hash, ascii encoded password
|
||||
no examples found
|
||||
|
||||
7.0
|
||||
snefru hash, unicode (what encoding?)
|
||||
saw ref that these blobs were 16 bytes in size
|
||||
no examples found
|
||||
|
||||
2000
|
||||
byte string using displayed as 0x hex, using 0x0100 prefix.
|
||||
contains hashes of password and upper-case password.
|
||||
|
||||
2007
|
||||
same as 2000, but without the upper-case hash.
|
||||
|
||||
refs
|
||||
----------
|
||||
https://blogs.msdn.com/b/lcris/archive/2007/04/30/sql-server-2005-about-login-password-hashes.aspx?Redirected=true
|
||||
http://us.generation-nt.com/securing-passwords-hash-help-35429432.html
|
||||
http://forum.md5decrypter.co.uk/topic230-mysql-and-mssql-get-password-hashes.aspx
|
||||
http://www.theregister.co.uk/2002/07/08/cracking_ms_sql_server_passwords/
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import consteq
|
||||
from passlib.utils.compat import bascii_to_str, unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"mssql2000",
|
||||
"mssql2005",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# mssql 2000
|
||||
#=============================================================================
|
||||
def _raw_mssql(secret, salt):
|
||||
assert isinstance(secret, unicode)
|
||||
assert isinstance(salt, bytes)
|
||||
return sha1(secret.encode("utf-16-le") + salt).digest()
|
||||
|
||||
BIDENT = b"0x0100"
|
||||
##BIDENT2 = b("\x01\x00")
|
||||
UIDENT = u("0x0100")
|
||||
|
||||
def _ident_mssql(hash, csize, bsize):
|
||||
"""common identify for mssql 2000/2005"""
|
||||
if isinstance(hash, unicode):
|
||||
if len(hash) == csize and hash.startswith(UIDENT):
|
||||
return True
|
||||
elif isinstance(hash, bytes):
|
||||
if len(hash) == csize and hash.startswith(BIDENT):
|
||||
return True
|
||||
##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
|
||||
## return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
return False
|
||||
|
||||
def _parse_mssql(hash, csize, bsize, handler):
|
||||
"""common parser for mssql 2000/2005; returns 4 byte salt + checksum"""
|
||||
if isinstance(hash, unicode):
|
||||
if len(hash) == csize and hash.startswith(UIDENT):
|
||||
try:
|
||||
return unhexlify(hash[6:].encode("utf-8"))
|
||||
except TypeError: # throw when bad char found
|
||||
pass
|
||||
elif isinstance(hash, bytes):
|
||||
# assumes ascii-compat encoding
|
||||
assert isinstance(hash, bytes)
|
||||
if len(hash) == csize and hash.startswith(BIDENT):
|
||||
try:
|
||||
return unhexlify(hash[6:])
|
||||
except TypeError: # throw when bad char found
|
||||
pass
|
||||
##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
|
||||
## return hash[2:]
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
raise uh.exc.InvalidHashError(handler)
|
||||
|
||||
class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length 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 must be 4 bytes in length.
|
||||
|
||||
: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.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "mssql2000"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_size = 40
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# 0100 - 2 byte identifier
|
||||
# 4 byte salt
|
||||
# 20 byte checksum
|
||||
# 20 byte checksum
|
||||
# = 46 bytes
|
||||
# encoded '0x' + 92 chars = 94
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
return _ident_mssql(hash, 94, 46)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
data = _parse_mssql(hash, 94, 46, cls)
|
||||
return cls(salt=data[:4], checksum=data[4:])
|
||||
|
||||
def to_string(self):
|
||||
raw = self.salt + self.checksum
|
||||
# raw bytes format - BIDENT2 + raw
|
||||
return "0x0100" + bascii_to_str(hexlify(raw).upper())
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
salt = self.salt
|
||||
return _raw_mssql(secret, salt) + _raw_mssql(secret.upper(), salt)
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
# NOTE: we only compare against the upper-case hash
|
||||
# XXX: add 'full' just to verify both checksums?
|
||||
uh.validate_secret(secret)
|
||||
self = cls.from_string(hash)
|
||||
chk = self.checksum
|
||||
if chk is None:
|
||||
raise uh.exc.MissingDigestError(cls)
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
result = _raw_mssql(secret.upper(), self.salt)
|
||||
return consteq(result, chk[20:])
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length 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 must be 4 bytes in length.
|
||||
|
||||
: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.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "mssql2005"
|
||||
setting_kwds = ("salt",)
|
||||
|
||||
checksum_size = 20
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# 0x0100 - 2 byte identifier
|
||||
# 4 byte salt
|
||||
# 20 byte checksum
|
||||
# = 26 bytes
|
||||
# encoded '0x' + 52 chars = 54
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
return _ident_mssql(hash, 54, 26)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
data = _parse_mssql(hash, 54, 26, cls)
|
||||
return cls(salt=data[:4], checksum=data[4:])
|
||||
|
||||
def to_string(self):
|
||||
raw = self.salt + self.checksum
|
||||
# raw bytes format - BIDENT2 + raw
|
||||
return "0x0100" + bascii_to_str(hexlify(raw)).upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
return _raw_mssql(secret, self.salt)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
128
venv/Lib/site-packages/passlib/handlers/mysql.py
Normal file
128
venv/Lib/site-packages/passlib/handlers/mysql.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""passlib.handlers.mysql
|
||||
|
||||
MySQL 3.2.3 / OLD_PASSWORD()
|
||||
|
||||
This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1.
|
||||
|
||||
See :mod:`passlib.handlers.mysql_41` for the new algorithm was put in place in version 4.1
|
||||
|
||||
This algorithm is known to be very insecure, and should only be used to verify existing password hashes.
|
||||
|
||||
http://djangosnippets.org/snippets/1508/
|
||||
|
||||
MySQL 4.1.1 / NEW PASSWORD
|
||||
This implements Mysql new PASSWORD algorithm, introduced in version 4.1.
|
||||
|
||||
This function is unsalted, and therefore not very secure against rainbow attacks.
|
||||
It should only be used when dealing with mysql passwords,
|
||||
for all other purposes, you should use a salted hash function.
|
||||
|
||||
Description taken from http://dev.mysql.com/doc/refman/6.0/en/password-hashing.html
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str
|
||||
from passlib.utils.compat import bascii_to_str, unicode, u, \
|
||||
byte_elem_value, str_to_uascii
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
'mysql323',
|
||||
'mysq41',
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# backend
|
||||
#=============================================================================
|
||||
class mysql323(uh.StaticHandler):
|
||||
"""This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "mysql323"
|
||||
checksum_size = 16
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: no idea if mysql has a policy about handling unicode passwords
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
|
||||
MASK_32 = 0xffffffff
|
||||
MASK_31 = 0x7fffffff
|
||||
WHITE = b' \t'
|
||||
|
||||
nr1 = 0x50305735
|
||||
nr2 = 0x12345671
|
||||
add = 7
|
||||
for c in secret:
|
||||
if c in WHITE:
|
||||
continue
|
||||
tmp = byte_elem_value(c)
|
||||
nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32
|
||||
nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32
|
||||
add = (add+tmp) & MASK_32
|
||||
return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class mysql41(uh.StaticHandler):
|
||||
"""This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "mysql41"
|
||||
_hash_prefix = u("*")
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 40
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: no idea if mysql has a policy about handling unicode passwords
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
172
venv/Lib/site-packages/passlib/handlers/oracle.py
Normal file
172
venv/Lib/site-packages/passlib/handlers/oracle.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""passlib.handlers.oracle - Oracle DB Password Hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode, xor_bytes
|
||||
from passlib.utils.compat import irange, u, \
|
||||
uascii_to_str, unicode, str_to_uascii
|
||||
from passlib.crypto.des import des_encrypt_block
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"oracle10g",
|
||||
"oracle11g"
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# oracle10
|
||||
#=============================================================================
|
||||
def des_cbc_encrypt(key, value, iv=b'\x00' * 8, pad=b'\x00'):
|
||||
"""performs des-cbc encryption, returns only last block.
|
||||
|
||||
this performs a specific DES-CBC encryption implementation
|
||||
as needed by the Oracle10 hash. it probably won't be useful for
|
||||
other purposes as-is.
|
||||
|
||||
input value is null-padded to multiple of 8 bytes.
|
||||
|
||||
:arg key: des key as bytes
|
||||
:arg value: value to encrypt, as bytes.
|
||||
:param iv: optional IV
|
||||
:param pad: optional pad byte
|
||||
|
||||
:returns: last block of DES-CBC encryption of all ``value``'s byte blocks.
|
||||
"""
|
||||
value += pad * (-len(value) % 8) # null pad to multiple of 8
|
||||
hash = iv # start things off
|
||||
for offset in irange(0,len(value),8):
|
||||
chunk = xor_bytes(hash, value[offset:offset+8])
|
||||
hash = des_encrypt_block(key, chunk)
|
||||
return hash
|
||||
|
||||
# magic string used as initial des key by oracle10
|
||||
ORACLE10_MAGIC = b"\x01\x23\x45\x67\x89\xAB\xCD\xEF"
|
||||
|
||||
class oracle10(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements the password hash used by Oracle up to version 10g, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It does a single round of hashing, and relies on the username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keywords:
|
||||
|
||||
:type user: str
|
||||
:param user: name of oracle user account this password is associated with.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "oracle10"
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 16
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: not sure how oracle handles unicode.
|
||||
# online docs about 10g hash indicate it puts ascii chars
|
||||
# in a 2-byte encoding w/ the high byte set to null.
|
||||
# they don't say how it handles other chars, or what encoding.
|
||||
#
|
||||
# so for now, encoding secret & user to utf-16-be,
|
||||
# since that fits, and if secret/user is bytes,
|
||||
# we assume utf-8, and decode first.
|
||||
#
|
||||
# this whole mess really needs someone w/ an oracle system,
|
||||
# and some answers :)
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
user = to_unicode(self.user, "utf-8", param="user")
|
||||
input = (user+secret).upper().encode("utf-16-be")
|
||||
hash = des_cbc_encrypt(ORACLE10_MAGIC, input)
|
||||
hash = des_cbc_encrypt(hash, input)
|
||||
return hexlify(hash).decode("ascii").upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# oracle11
|
||||
#=============================================================================
|
||||
class oracle11(uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the Oracle11g password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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 20 hexadecimal characters.
|
||||
|
||||
: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
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "oracle11"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_size = 40
|
||||
checksum_chars = uh.UPPER_HEX_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 20
|
||||
salt_chars = uh.UPPER_HEX_CHARS
|
||||
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$"), re.I)
|
||||
|
||||
@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)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk.upper())
|
||||
|
||||
def to_string(self):
|
||||
chk = self.checksum
|
||||
hash = u("S:%s%s") % (chk.upper(), self.salt.upper())
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest()
|
||||
return str_to_uascii(chk).upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
475
venv/Lib/site-packages/passlib/handlers/pbkdf2.py
Normal file
475
venv/Lib/site-packages/passlib/handlers/pbkdf2.py
Normal file
@@ -0,0 +1,475 @@
|
||||
"""passlib.handlers.pbkdf - PBKDF2 based hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from base64 import b64encode, b64decode
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode
|
||||
from passlib.utils.binary import ab64_decode, ab64_encode
|
||||
from passlib.utils.compat import str_to_bascii, u, uascii_to_str, unicode
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"pbkdf2_sha1",
|
||||
"pbkdf2_sha256",
|
||||
"pbkdf2_sha512",
|
||||
"cta_pbkdf2_sha1",
|
||||
"dlitz_pbkdf2_sha1",
|
||||
"grub_pbkdf2_sha512",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""base class for various pbkdf2_{digest} algorithms"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--GenericHandler--
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = None # set by subclass
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--this class--
|
||||
_digest = None # name of subclass-specified hash
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check.
|
||||
# the underlying pbkdf2 specifies no bounds for either.
|
||||
|
||||
# NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends...
|
||||
# >8 bytes of entropy in salt, >1000 rounds
|
||||
# increased due to time since rfc established
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
|
||||
salt = ab64_decode(salt.encode("ascii"))
|
||||
if chk:
|
||||
chk = ab64_decode(chk.encode("ascii"))
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = ab64_encode(self.salt).decode("ascii")
|
||||
chk = ab64_encode(self.checksum).decode("ascii")
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using UTF8
|
||||
return pbkdf2_hmac(self._digest, secret, self.salt, self.rounds, self.checksum_size)
|
||||
|
||||
def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
|
||||
"""create new Pbkdf2DigestHandler subclass for a specific hash"""
|
||||
name = 'pbkdf2_' + hash_name
|
||||
if ident is None:
|
||||
ident = u("$pbkdf2-%s$") % (hash_name,)
|
||||
base = Pbkdf2DigestHandler
|
||||
return type(name, (base,), dict(
|
||||
__module__=module, # so ABCMeta won't clobber it.
|
||||
name=name,
|
||||
ident=ident,
|
||||
_digest = hash_name,
|
||||
default_rounds=rounds,
|
||||
checksum_size=digest_size,
|
||||
encoded_checksum_size=(digest_size*4+2)//3,
|
||||
__doc__="""This class implements a generic ``PBKDF2-HMAC-%(digest)s``-based 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:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to %(dsc)d bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to %(dr)d, 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.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
""" % dict(digest=hash_name.upper(), dsc=base.default_salt_size, dr=rounds)
|
||||
))
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# derived handlers
|
||||
#------------------------------------------------------------------------
|
||||
pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$"))
|
||||
pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000)
|
||||
pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000)
|
||||
|
||||
ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
|
||||
ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
|
||||
ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True)
|
||||
|
||||
#=============================================================================
|
||||
# cryptacular's pbkdf2 hash
|
||||
#=============================================================================
|
||||
|
||||
# bytes used by cta hash for base64 values 63 & 64
|
||||
CTA_ALTCHARS = b"-_"
|
||||
|
||||
class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements Cryptacular's PBKDF2-based crypt algorithm, 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: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, it may be any length.
|
||||
If not specified, a one will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 60000, 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.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "cta_pbkdf2_sha1"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
ident = u("$p5k2$")
|
||||
checksum_size = 20
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. underlying algorithm (and reference implementation)
|
||||
# allows effectively unbounded values for both of these parameters.
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = pbkdf2_sha1.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# hash $p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0=
|
||||
# ident $p5k2$
|
||||
# rounds 1000
|
||||
# salt ZxK4ZBJCfQg=
|
||||
# chk jJZVscWtO--p1-xIZl6jhO2LKR0=
|
||||
# NOTE: rounds in hex
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
# NOTE: passlib deviation - forbidding zero-padded rounds
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16, handler=cls)
|
||||
salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS)
|
||||
if chk:
|
||||
chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS)
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = b64encode(self.salt, CTA_ALTCHARS).decode("ascii")
|
||||
chk = b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk, rounds_base=16)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha1", secret, self.salt, self.rounds, 20)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# dlitz's pbkdf2 hash
|
||||
#=============================================================================
|
||||
class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, 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 specified, it may be any length, but must use the characters in the regexp range ``[./0-9A-Za-z]``.
|
||||
If not specified, a 16 character salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 60000, 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.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "dlitz_pbkdf2_sha1"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
ident = u("$p5k2$")
|
||||
_stub_checksum = u("0" * 48 + "=")
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. underlying algorithm (and reference implementation)
|
||||
# allows effectively unbounded values for both of these parameters.
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
# NOTE: for security, the default here is set to match pbkdf2_sha1,
|
||||
# even though this hash's extra block makes it twice as slow.
|
||||
default_rounds = pbkdf2_sha1.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# hash $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
|
||||
# ident $p5k2$
|
||||
# rounds c
|
||||
# salt u9HvcT4d
|
||||
# chk Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
|
||||
# rounds in lowercase hex, no zero padding
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16,
|
||||
default_rounds=400, handler=cls)
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
rounds = self.rounds
|
||||
if rounds == 400:
|
||||
rounds = None # omit rounds measurement if == 400
|
||||
return uh.render_mc3(self.ident, rounds, self.salt, self.checksum, rounds_base=16)
|
||||
|
||||
def _get_config(self):
|
||||
rounds = self.rounds
|
||||
if rounds == 400:
|
||||
rounds = None # omit rounds measurement if == 400
|
||||
return uh.render_mc3(self.ident, rounds, self.salt, None, rounds_base=16)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
salt = self._get_config()
|
||||
result = pbkdf2_hmac("sha1", secret, salt, self.rounds, 24)
|
||||
return ab64_encode(result).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# crowd
|
||||
#=============================================================================
|
||||
class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the PBKDF2 hash used by Atlassian.
|
||||
|
||||
It supports a fixed-length salt, and a fixed number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be exactly 16 bytes.
|
||||
If not specified, a salt will be autogenerated (this is recommended).
|
||||
|
||||
: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
|
||||
"""
|
||||
#--GenericHandler--
|
||||
name = "atlassian_pbkdf2_sha1"
|
||||
setting_kwds =("salt",)
|
||||
ident = u("{PKCS5S2}")
|
||||
checksum_size = 32
|
||||
|
||||
#--HasRawSalt--
|
||||
min_salt_size = max_salt_size = 16
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
ident = cls.ident
|
||||
if not hash.startswith(ident):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
data = b64decode(hash[len(ident):].encode("ascii"))
|
||||
salt, chk = data[:16], data[16:]
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
data = self.salt + self.checksum
|
||||
hash = self.ident + b64encode(data).decode("ascii")
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# TODO: find out what crowd's policy is re: unicode
|
||||
# crowd seems to use a fixed number of rounds.
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha1", secret, self.salt, 10000, 32)
|
||||
|
||||
#=============================================================================
|
||||
# grub
|
||||
#=============================================================================
|
||||
class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements Grub's pbkdf2-hmac-sha512 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: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a 64 byte salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 64 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 19000, 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.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
name = "grub_pbkdf2_sha512"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
|
||||
ident = u("grub.pbkdf2.sha512.")
|
||||
checksum_size = 64
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. the underlying pbkdf2 specifies no bounds for either,
|
||||
# and it's not clear what grub specifies.
|
||||
|
||||
default_salt_size = 64
|
||||
max_salt_size = 1024
|
||||
|
||||
default_rounds = pbkdf2_sha512.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."),
|
||||
handler=cls)
|
||||
salt = unhexlify(salt.encode("ascii"))
|
||||
if chk:
|
||||
chk = unhexlify(chk.encode("ascii"))
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = hexlify(self.salt).decode("ascii").upper()
|
||||
chk = hexlify(self.checksum).decode("ascii").upper()
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u("."))
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# TODO: find out what grub's policy is re: unicode
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha512", secret, self.salt, self.rounds, 64)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
135
venv/Lib/site-packages/passlib/handlers/phpass.py
Normal file
135
venv/Lib/site-packages/passlib/handlers/phpass.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""passlib.handlers.phpass - PHPass Portable Crypt
|
||||
|
||||
phppass located - http://www.openwall.com/phpass/
|
||||
algorithm described - http://www.openwall.com/articles/PHP-Users-Passwords
|
||||
|
||||
phpass context - blowfish, bsdi_crypt, phpass
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import u, uascii_to_str, unicode
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"phpass",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# phpass
|
||||
#=============================================================================
|
||||
class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the PHPass Portable Hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-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, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 19, must be between 7 and 30, inclusive.
|
||||
This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
|
||||
|
||||
:type ident: str
|
||||
:param ident:
|
||||
phpBB3 uses ``H`` instead of ``P`` for its identifier,
|
||||
this may be set to ``H`` in order to generate phpBB3 compatible hashes.
|
||||
it defaults to ``P``.
|
||||
|
||||
: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 = "phpass"
|
||||
setting_kwds = ("salt", "rounds", "ident")
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 8
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 19
|
||||
min_rounds = 7
|
||||
max_rounds = 30
|
||||
rounds_cost = "log2"
|
||||
|
||||
#--HasManyIdents--
|
||||
default_ident = u("$P$")
|
||||
ident_values = (u("$P$"), u("$H$"))
|
||||
ident_aliases = {u("P"):u("$P$"), u("H"):u("$H$")}
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
#$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0
|
||||
# $P$
|
||||
# 9
|
||||
# IQRaTwmf
|
||||
# eRo7ud9Fh4E2PdI0S3r.L0
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
ident, data = cls._parse_ident(hash)
|
||||
rounds, salt, chk = data[0], data[1:9], data[9:]
|
||||
return cls(
|
||||
ident=ident,
|
||||
rounds=h64.decode_int6(rounds.encode("ascii")),
|
||||
salt=salt,
|
||||
checksum=chk or None,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s%s%s") % (self.ident,
|
||||
h64.encode_int6(self.rounds).decode("ascii"),
|
||||
self.salt,
|
||||
self.checksum or u(''))
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: can't find definitive policy on how phpass handles non-ascii.
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
real_rounds = 1<<self.rounds
|
||||
result = md5(self.salt.encode("ascii") + secret).digest()
|
||||
r = 0
|
||||
while r < real_rounds:
|
||||
result = md5(result + secret).digest()
|
||||
r += 1
|
||||
return h64.encode_bytes(result).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
55
venv/Lib/site-packages/passlib/handlers/postgres.py
Normal file
55
venv/Lib/site-packages/passlib/handlers/postgres.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""passlib.handlers.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_bytes
|
||||
from passlib.utils.compat import str_to_uascii, unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"postgres_md5",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class postgres_md5(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements the Postgres MD5 Password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It does a single round of hashing, and relies on the username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keywords:
|
||||
|
||||
:type user: str
|
||||
:param user: name of postgres user account this password is associated with.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "postgres_md5"
|
||||
_hash_prefix = u("md5")
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
#===================================================================
|
||||
# primary interface
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
user = to_bytes(self.user, "utf-8", param="user")
|
||||
return str_to_uascii(md5(secret + user).hexdigest())
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
29
venv/Lib/site-packages/passlib/handlers/roundup.py
Normal file
29
venv/Lib/site-packages/passlib/handlers/roundup.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""passlib.handlers.roundup - Roundup issue tracker hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.utils.compat import u
|
||||
# local
|
||||
__all__ = [
|
||||
"roundup_plaintext",
|
||||
"ldap_hex_md5",
|
||||
"ldap_hex_sha1",
|
||||
]
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
roundup_plaintext = uh.PrefixWrapper("roundup_plaintext", "plaintext",
|
||||
prefix=u("{plaintext}"), lazy=True)
|
||||
|
||||
# NOTE: these are here because they're currently only known to be used by roundup
|
||||
ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u("{MD5}"), lazy=True)
|
||||
ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u("{SHA}"), lazy=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
582
venv/Lib/site-packages/passlib/handlers/scram.py
Normal file
582
venv/Lib/site-packages/passlib/handlers/scram.py
Normal file
@@ -0,0 +1,582 @@
|
||||
"""passlib.handlers.scram - hash for SCRAM credential storage"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import consteq, saslprep, to_native_str, splitcomma
|
||||
from passlib.utils.binary import ab64_decode, ab64_encode
|
||||
from passlib.utils.compat import bascii_to_str, iteritems, u, native_string_types
|
||||
from passlib.crypto.digest import pbkdf2_hmac, norm_hash_name
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"scram",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scram credentials hash
|
||||
#=============================================================================
|
||||
class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class provides a format for storing SCRAM passwords, 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: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a 12 byte salt will be autogenerated
|
||||
(this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 12 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 100000, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type algs: list of strings
|
||||
:param algs:
|
||||
Specify list of digest algorithms to use.
|
||||
|
||||
By default each scram hash will contain digests for SHA-1,
|
||||
SHA-256, and SHA-512. This can be overridden by specify either be a
|
||||
list such as ``["sha-1", "sha-256"]``, or a comma-separated string
|
||||
such as ``"sha-1, sha-256"``. Names are case insensitive, and may
|
||||
use :mod:`!hashlib` or `IANA <http://www.iana.org/assignments/hash-function-text-names>`_
|
||||
hash names.
|
||||
|
||||
: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
|
||||
|
||||
In addition to the standard :ref:`password-hash-api` methods,
|
||||
this class also provides the following methods for manipulating Passlib
|
||||
scram hashes in ways useful for pluging into a SCRAM protocol stack:
|
||||
|
||||
.. automethod:: extract_digest_info
|
||||
.. automethod:: extract_digest_algs
|
||||
.. automethod:: derive_digest
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
# NOTE: unlike most GenericHandler classes, the 'checksum' attr of
|
||||
# ScramHandler is actually a map from digest_name -> digest, so
|
||||
# many of the standard methods have been overridden.
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide
|
||||
# a sanity check; the underlying pbkdf2 specifies no bounds for either.
|
||||
|
||||
#--GenericHandler--
|
||||
name = "scram"
|
||||
setting_kwds = ("salt", "salt_size", "rounds", "algs")
|
||||
ident = u("$scram$")
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 12
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 100000
|
||||
min_rounds = 1
|
||||
max_rounds = 2**32-1
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--custom--
|
||||
|
||||
# default algorithms when creating new hashes.
|
||||
default_algs = ["sha-1", "sha-256", "sha-512"]
|
||||
|
||||
# list of algs verify prefers to use, in order.
|
||||
_verify_algs = ["sha-256", "sha-512", "sha-224", "sha-384", "sha-1"]
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
|
||||
# 'checksum' is different from most GenericHandler subclasses,
|
||||
# in that it contains a dict mapping from alg -> digest,
|
||||
# or None if no checksum present.
|
||||
|
||||
# list of algorithms to create/compare digests for.
|
||||
algs = None
|
||||
|
||||
#===================================================================
|
||||
# scram frontend helpers
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def extract_digest_info(cls, hash, alg):
|
||||
"""return (salt, rounds, digest) for specific hash algorithm.
|
||||
|
||||
:type hash: str
|
||||
:arg hash:
|
||||
:class:`!scram` hash stored for desired user
|
||||
|
||||
:type alg: str
|
||||
:arg alg:
|
||||
Name of digest algorithm (e.g. ``"sha-1"``) requested by client.
|
||||
|
||||
This value is run through :func:`~passlib.crypto.digest.norm_hash_name`,
|
||||
so it is case-insensitive, and can be the raw SCRAM
|
||||
mechanism name (e.g. ``"SCRAM-SHA-1"``), the IANA name,
|
||||
or the hashlib name.
|
||||
|
||||
:raises KeyError:
|
||||
If the hash does not contain an entry for the requested digest
|
||||
algorithm.
|
||||
|
||||
:returns:
|
||||
A tuple containing ``(salt, rounds, digest)``,
|
||||
where *digest* matches the raw bytes returned by
|
||||
SCRAM's :func:`Hi` function for the stored password,
|
||||
the provided *salt*, and the iteration count (*rounds*).
|
||||
*salt* and *digest* are both raw (unencoded) bytes.
|
||||
"""
|
||||
# XXX: this could be sped up by writing custom parsing routine
|
||||
# that just picks out relevant digest, and doesn't bother
|
||||
# with full structure validation each time it's called.
|
||||
alg = norm_hash_name(alg, 'iana')
|
||||
self = cls.from_string(hash)
|
||||
chkmap = self.checksum
|
||||
if not chkmap:
|
||||
raise ValueError("scram hash contains no digests")
|
||||
return self.salt, self.rounds, chkmap[alg]
|
||||
|
||||
@classmethod
|
||||
def extract_digest_algs(cls, hash, format="iana"):
|
||||
"""Return names of all algorithms stored in a given hash.
|
||||
|
||||
:type hash: str
|
||||
:arg hash:
|
||||
The :class:`!scram` hash to parse
|
||||
|
||||
:type format: str
|
||||
:param format:
|
||||
This changes the naming convention used by the
|
||||
returned algorithm names. By default the names
|
||||
are IANA-compatible; possible values are ``"iana"`` or ``"hashlib"``.
|
||||
|
||||
:returns:
|
||||
Returns a list of digest algorithms; e.g. ``["sha-1"]``
|
||||
"""
|
||||
# XXX: this could be sped up by writing custom parsing routine
|
||||
# that just picks out relevant names, and doesn't bother
|
||||
# with full structure validation each time it's called.
|
||||
algs = cls.from_string(hash).algs
|
||||
if format == "iana":
|
||||
return algs
|
||||
else:
|
||||
return [norm_hash_name(alg, format) for alg in algs]
|
||||
|
||||
@classmethod
|
||||
def derive_digest(cls, password, salt, rounds, alg):
|
||||
"""helper to create SaltedPassword digest for SCRAM.
|
||||
|
||||
This performs the step in the SCRAM protocol described as::
|
||||
|
||||
SaltedPassword := Hi(Normalize(password), salt, i)
|
||||
|
||||
:type password: unicode or utf-8 bytes
|
||||
:arg password: password to run through digest
|
||||
|
||||
:type salt: bytes
|
||||
:arg salt: raw salt data
|
||||
|
||||
:type rounds: int
|
||||
:arg rounds: number of iterations.
|
||||
|
||||
:type alg: str
|
||||
:arg alg: name of digest to use (e.g. ``"sha-1"``).
|
||||
|
||||
:returns:
|
||||
raw bytes of ``SaltedPassword``
|
||||
"""
|
||||
if isinstance(password, bytes):
|
||||
password = password.decode("utf-8")
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8,
|
||||
# and handle normalizing alg name.
|
||||
return pbkdf2_hmac(alg, saslprep(password), salt, rounds)
|
||||
|
||||
#===================================================================
|
||||
# serialization
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_native_str(hash, "ascii", "hash")
|
||||
if not hash.startswith("$scram$"):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
parts = hash[7:].split("$")
|
||||
if len(parts) != 3:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
rounds_str, salt_str, chk_str = parts
|
||||
|
||||
# decode rounds
|
||||
rounds = int(rounds_str)
|
||||
if rounds_str != str(rounds): # forbid zero padding, etc.
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
|
||||
# decode salt
|
||||
try:
|
||||
salt = ab64_decode(salt_str.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
|
||||
# decode algs/digest list
|
||||
if not chk_str:
|
||||
# scram hashes MUST have something here.
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
elif "=" in chk_str:
|
||||
# comma-separated list of 'alg=digest' pairs
|
||||
algs = None
|
||||
chkmap = {}
|
||||
for pair in chk_str.split(","):
|
||||
alg, digest = pair.split("=")
|
||||
try:
|
||||
chkmap[alg] = ab64_decode(digest.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
else:
|
||||
# comma-separated list of alg names, no digests
|
||||
algs = chk_str
|
||||
chkmap = None
|
||||
|
||||
# return new object
|
||||
return cls(
|
||||
rounds=rounds,
|
||||
salt=salt,
|
||||
checksum=chkmap,
|
||||
algs=algs,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
salt = bascii_to_str(ab64_encode(self.salt))
|
||||
chkmap = self.checksum
|
||||
chk_str = ",".join(
|
||||
"%s=%s" % (alg, bascii_to_str(ab64_encode(chkmap[alg])))
|
||||
for alg in self.algs
|
||||
)
|
||||
return '$scram$%d$%s$%s' % (self.rounds, salt, chk_str)
|
||||
|
||||
#===================================================================
|
||||
# variant constructor
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def using(cls, default_algs=None, algs=None, **kwds):
|
||||
# parse aliases
|
||||
if algs is not None:
|
||||
assert default_algs is None
|
||||
default_algs = algs
|
||||
|
||||
# create subclass
|
||||
subcls = super(scram, cls).using(**kwds)
|
||||
|
||||
# fill in algs
|
||||
if default_algs is not None:
|
||||
subcls.default_algs = cls._norm_algs(default_algs)
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, algs=None, **kwds):
|
||||
super(scram, self).__init__(**kwds)
|
||||
|
||||
# init algs
|
||||
digest_map = self.checksum
|
||||
if algs is not None:
|
||||
if digest_map is not None:
|
||||
raise RuntimeError("checksum & algs kwds are mutually exclusive")
|
||||
algs = self._norm_algs(algs)
|
||||
elif digest_map is not None:
|
||||
# derive algs list from digest map (if present).
|
||||
algs = self._norm_algs(digest_map.keys())
|
||||
elif self.use_defaults:
|
||||
algs = list(self.default_algs)
|
||||
assert self._norm_algs(algs) == algs, "invalid default algs: %r" % (algs,)
|
||||
else:
|
||||
raise TypeError("no algs list specified")
|
||||
self.algs = algs
|
||||
|
||||
def _norm_checksum(self, checksum, relaxed=False):
|
||||
if not isinstance(checksum, dict):
|
||||
raise uh.exc.ExpectedTypeError(checksum, "dict", "checksum")
|
||||
for alg, digest in iteritems(checksum):
|
||||
if alg != norm_hash_name(alg, 'iana'):
|
||||
raise ValueError("malformed algorithm name in scram hash: %r" %
|
||||
(alg,))
|
||||
if len(alg) > 9:
|
||||
raise ValueError("SCRAM limits algorithm names to "
|
||||
"9 characters: %r" % (alg,))
|
||||
if not isinstance(digest, bytes):
|
||||
raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests")
|
||||
# TODO: verify digest size (if digest is known)
|
||||
if 'sha-1' not in checksum:
|
||||
# NOTE: required because of SCRAM spec.
|
||||
raise ValueError("sha-1 must be in algorithm list of scram hash")
|
||||
return checksum
|
||||
|
||||
@classmethod
|
||||
def _norm_algs(cls, algs):
|
||||
"""normalize algs parameter"""
|
||||
if isinstance(algs, native_string_types):
|
||||
algs = splitcomma(algs)
|
||||
algs = sorted(norm_hash_name(alg, 'iana') for alg in algs)
|
||||
if any(len(alg)>9 for alg in algs):
|
||||
raise ValueError("SCRAM limits alg names to max of 9 characters")
|
||||
if 'sha-1' not in algs:
|
||||
# NOTE: required because of SCRAM spec (rfc 5802)
|
||||
raise ValueError("sha-1 must be in algorithm list of scram hash")
|
||||
return algs
|
||||
|
||||
#===================================================================
|
||||
# migration
|
||||
#===================================================================
|
||||
def _calc_needs_update(self, **kwds):
|
||||
# marks hashes as deprecated if they don't include at least all default_algs.
|
||||
# XXX: should we deprecate if they aren't exactly the same,
|
||||
# to permit removing legacy hashes?
|
||||
if not set(self.algs).issuperset(self.default_algs):
|
||||
return True
|
||||
|
||||
# hand off to base implementation
|
||||
return super(scram, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# digest methods
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret, alg=None):
|
||||
rounds = self.rounds
|
||||
salt = self.salt
|
||||
hash = self.derive_digest
|
||||
if alg:
|
||||
# if requested, generate digest for specific alg
|
||||
return hash(secret, salt, rounds, alg)
|
||||
else:
|
||||
# by default, return dict containing digests for all algs
|
||||
return dict(
|
||||
(alg, hash(secret, salt, rounds, alg))
|
||||
for alg in self.algs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, full=False):
|
||||
uh.validate_secret(secret)
|
||||
self = cls.from_string(hash)
|
||||
chkmap = self.checksum
|
||||
if not chkmap:
|
||||
raise ValueError("expected %s hash, got %s config string instead" %
|
||||
(cls.name, cls.name))
|
||||
|
||||
# NOTE: to make the verify method efficient, we just calculate hash
|
||||
# of shortest digest by default. apps can pass in "full=True" to
|
||||
# check entire hash for consistency.
|
||||
if full:
|
||||
correct = failed = False
|
||||
for alg, digest in iteritems(chkmap):
|
||||
other = self._calc_checksum(secret, alg)
|
||||
# NOTE: could do this length check in norm_algs(),
|
||||
# but don't need to be that strict, and want to be able
|
||||
# to parse hashes containing algs not supported by platform.
|
||||
# it's fine if we fail here though.
|
||||
if len(digest) != len(other):
|
||||
raise ValueError("mis-sized %s digest in scram hash: %r != %r"
|
||||
% (alg, len(digest), len(other)))
|
||||
if consteq(other, digest):
|
||||
correct = True
|
||||
else:
|
||||
failed = True
|
||||
if correct and failed:
|
||||
raise ValueError("scram hash verified inconsistently, "
|
||||
"may be corrupted")
|
||||
else:
|
||||
return correct
|
||||
else:
|
||||
# XXX: should this just always use sha1 hash? would be faster.
|
||||
# otherwise only verify against one hash, pick one w/ best security.
|
||||
for alg in self._verify_algs:
|
||||
if alg in chkmap:
|
||||
other = self._calc_checksum(secret, alg)
|
||||
return consteq(other, chkmap[alg])
|
||||
# there should always be sha-1 at the very least,
|
||||
# or something went wrong inside _norm_algs()
|
||||
raise AssertionError("sha-1 digest not found!")
|
||||
|
||||
#===================================================================
|
||||
#
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# code used for testing scram against protocol examples during development.
|
||||
#=============================================================================
|
||||
##def _test_reference_scram():
|
||||
## "quick hack testing scram reference vectors"
|
||||
## # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801
|
||||
## from passlib.utils.compat import print_
|
||||
##
|
||||
## engine = _scram_engine(
|
||||
## alg="sha-1",
|
||||
## salt='QSXCR+Q6sek8bf92'.decode("base64"),
|
||||
## rounds=4096,
|
||||
## password=u("pencil"),
|
||||
## )
|
||||
## print_(engine.digest.encode("base64").rstrip())
|
||||
##
|
||||
## msg = engine.format_auth_msg(
|
||||
## username="user",
|
||||
## client_nonce = "fyko+d2lbbFgONRv9qkxdawL",
|
||||
## server_nonce = "3rfcNHYJY1ZVvWVs7j",
|
||||
## header='c=biws',
|
||||
## )
|
||||
##
|
||||
## cp = engine.get_encoded_client_proof(msg)
|
||||
## assert cp == "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", cp
|
||||
##
|
||||
## ss = engine.get_encoded_server_sig(msg)
|
||||
## assert ss == "rmF9pqV8S7suAoZWja4dJRkFsKQ=", ss
|
||||
##
|
||||
##class _scram_engine(object):
|
||||
## """helper class for verifying scram hash behavior
|
||||
## against SCRAM protocol examples. not officially part of Passlib.
|
||||
##
|
||||
## takes in alg, salt, rounds, and a digest or password.
|
||||
##
|
||||
## can calculate the various keys & messages of the scram protocol.
|
||||
##
|
||||
## """
|
||||
## #=========================================================
|
||||
## # init
|
||||
## #=========================================================
|
||||
##
|
||||
## @classmethod
|
||||
## def from_string(cls, hash, alg):
|
||||
## "create record from scram hash, for given alg"
|
||||
## return cls(alg, *scram.extract_digest_info(hash, alg))
|
||||
##
|
||||
## def __init__(self, alg, salt, rounds, digest=None, password=None):
|
||||
## self.alg = norm_hash_name(alg)
|
||||
## self.salt = salt
|
||||
## self.rounds = rounds
|
||||
## self.password = password
|
||||
## if password:
|
||||
## data = scram.derive_digest(password, salt, rounds, alg)
|
||||
## if digest and data != digest:
|
||||
## raise ValueError("password doesn't match digest")
|
||||
## else:
|
||||
## digest = data
|
||||
## elif not digest:
|
||||
## raise TypeError("must provide password or digest")
|
||||
## self.digest = digest
|
||||
##
|
||||
## #=========================================================
|
||||
## # frontend methods
|
||||
## #=========================================================
|
||||
## def get_hash(self, data):
|
||||
## "return hash of raw data"
|
||||
## return hashlib.new(iana_to_hashlib(self.alg), data).digest()
|
||||
##
|
||||
## def get_client_proof(self, msg):
|
||||
## "return client proof of specified auth msg text"
|
||||
## return xor_bytes(self.client_key, self.get_client_sig(msg))
|
||||
##
|
||||
## def get_encoded_client_proof(self, msg):
|
||||
## return self.get_client_proof(msg).encode("base64").rstrip()
|
||||
##
|
||||
## def get_client_sig(self, msg):
|
||||
## "return client signature of specified auth msg text"
|
||||
## return self.get_hmac(self.stored_key, msg)
|
||||
##
|
||||
## def get_server_sig(self, msg):
|
||||
## "return server signature of specified auth msg text"
|
||||
## return self.get_hmac(self.server_key, msg)
|
||||
##
|
||||
## def get_encoded_server_sig(self, msg):
|
||||
## return self.get_server_sig(msg).encode("base64").rstrip()
|
||||
##
|
||||
## def format_server_response(self, client_nonce, server_nonce):
|
||||
## return 'r={client_nonce}{server_nonce},s={salt},i={rounds}'.format(
|
||||
## client_nonce=client_nonce,
|
||||
## server_nonce=server_nonce,
|
||||
## rounds=self.rounds,
|
||||
## salt=self.encoded_salt,
|
||||
## )
|
||||
##
|
||||
## def format_auth_msg(self, username, client_nonce, server_nonce,
|
||||
## header='c=biws'):
|
||||
## return (
|
||||
## 'n={username},r={client_nonce}'
|
||||
## ','
|
||||
## 'r={client_nonce}{server_nonce},s={salt},i={rounds}'
|
||||
## ','
|
||||
## '{header},r={client_nonce}{server_nonce}'
|
||||
## ).format(
|
||||
## username=username,
|
||||
## client_nonce=client_nonce,
|
||||
## server_nonce=server_nonce,
|
||||
## salt=self.encoded_salt,
|
||||
## rounds=self.rounds,
|
||||
## header=header,
|
||||
## )
|
||||
##
|
||||
## #=========================================================
|
||||
## # helpers to calculate & cache constant data
|
||||
## #=========================================================
|
||||
## def _calc_get_hmac(self):
|
||||
## return get_prf("hmac-" + iana_to_hashlib(self.alg))[0]
|
||||
##
|
||||
## def _calc_client_key(self):
|
||||
## return self.get_hmac(self.digest, b("Client Key"))
|
||||
##
|
||||
## def _calc_stored_key(self):
|
||||
## return self.get_hash(self.client_key)
|
||||
##
|
||||
## def _calc_server_key(self):
|
||||
## return self.get_hmac(self.digest, b("Server Key"))
|
||||
##
|
||||
## def _calc_encoded_salt(self):
|
||||
## return self.salt.encode("base64").rstrip()
|
||||
##
|
||||
## #=========================================================
|
||||
## # hacks for calculated attributes
|
||||
## #=========================================================
|
||||
##
|
||||
## def __getattr__(self, attr):
|
||||
## if not attr.startswith("_"):
|
||||
## f = getattr(self, "_calc_" + attr, None)
|
||||
## if f:
|
||||
## value = f()
|
||||
## setattr(self, attr, value)
|
||||
## return value
|
||||
## raise AttributeError("attribute not found")
|
||||
##
|
||||
## def __dir__(self):
|
||||
## cdir = dir(self.__class__)
|
||||
## attrs = set(cdir)
|
||||
## attrs.update(self.__dict__)
|
||||
## attrs.update(attr[6:] for attr in cdir
|
||||
## if attr.startswith("_calc_"))
|
||||
## return sorted(attrs)
|
||||
## #=========================================================
|
||||
## # eoc
|
||||
## #=========================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
383
venv/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
383
venv/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
@@ -0,0 +1,383 @@
|
||||
"""passlib.handlers.scrypt -- scrypt password hash"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, absolute_import
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.crypto import scrypt as _scrypt
|
||||
from passlib.utils import h64, to_bytes
|
||||
from passlib.utils.binary import h64, b64s_decode, b64s_encode
|
||||
from passlib.utils.compat import u, bascii_to_str, suppress_cause
|
||||
from passlib.utils.decor import classproperty
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"scrypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scrypt format identifiers
|
||||
#=============================================================================
|
||||
|
||||
IDENT_SCRYPT = u("$scrypt$") # identifier used by passlib
|
||||
IDENT_7 = u("$7$") # used by official scrypt spec
|
||||
|
||||
_UDOLLAR = u("$")
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.HasManyIdents,
|
||||
uh.GenericHandler):
|
||||
"""This class implements an SCrypt-based password [#scrypt-home]_ hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, a variable number of rounds,
|
||||
as well as some custom tuning parameters unique to scrypt (see below).
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, one will be auto-generated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 16, but must be within ``range(1,32)``.
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike many hash algorithms, increasing the rounds value
|
||||
will increase both the time *and memory* required to hash a password.
|
||||
|
||||
:type block_size: int
|
||||
:param block_size:
|
||||
Optional block size to pass to scrypt hash function (the ``r`` parameter).
|
||||
Useful for tuning scrypt to optimal performance for your CPU architecture.
|
||||
Defaults to 8.
|
||||
|
||||
:type parallelism: int
|
||||
:param parallelism:
|
||||
Optional parallelism to pass to scrypt hash function (the ``p`` parameter).
|
||||
Defaults to 1.
|
||||
|
||||
: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.
|
||||
|
||||
.. note::
|
||||
|
||||
The underlying scrypt hash function has a number of limitations
|
||||
on it's parameter values, which forbids certain combinations of settings.
|
||||
The requirements are:
|
||||
|
||||
* ``linear_rounds = 2**<some positive integer>``
|
||||
* ``linear_rounds < 2**(16 * block_size)``
|
||||
* ``block_size * parallelism <= 2**30-1``
|
||||
|
||||
.. todo::
|
||||
|
||||
This class currently does not support configuring default values
|
||||
for ``block_size`` or ``parallelism`` via a :class:`~passlib.context.CryptContext`
|
||||
configuration.
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#------------------------
|
||||
# PasswordHash
|
||||
#------------------------
|
||||
name = "scrypt"
|
||||
setting_kwds = ("ident", "salt", "salt_size", "rounds", "block_size", "parallelism")
|
||||
|
||||
#------------------------
|
||||
# GenericHandler
|
||||
#------------------------
|
||||
# NOTE: scrypt supports arbitrary output sizes. since it's output runs through
|
||||
# pbkdf2-hmac-sha256 before returning, and this could be raised eventually...
|
||||
# but a 256-bit digest is more than sufficient for password hashing.
|
||||
# XXX: make checksum size configurable? could merge w/ argon2 code that does this.
|
||||
checksum_size = 32
|
||||
|
||||
#------------------------
|
||||
# HasManyIdents
|
||||
#------------------------
|
||||
default_ident = IDENT_SCRYPT
|
||||
ident_values = (IDENT_SCRYPT, IDENT_7)
|
||||
|
||||
#------------------------
|
||||
# HasRawSalt
|
||||
#------------------------
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#------------------------
|
||||
# HasRounds
|
||||
#------------------------
|
||||
# TODO: would like to dynamically pick this based on system
|
||||
default_rounds = 16
|
||||
min_rounds = 1
|
||||
max_rounds = 31 # limited by scrypt alg
|
||||
rounds_cost = "log2"
|
||||
|
||||
# TODO: make default block size configurable via using(), and deprecatable via .needs_update()
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
|
||||
#: default parallelism setting (min=1 currently hardcoded in mixin)
|
||||
parallelism = 1
|
||||
|
||||
#: default block size setting
|
||||
block_size = 8
|
||||
|
||||
#===================================================================
|
||||
# variant constructor
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def using(cls, block_size=None, **kwds):
|
||||
subcls = super(scrypt, cls).using(**kwds)
|
||||
if block_size is not None:
|
||||
if isinstance(block_size, uh.native_string_types):
|
||||
block_size = int(block_size)
|
||||
subcls.block_size = subcls._norm_block_size(block_size, relaxed=kwds.get("relaxed"))
|
||||
|
||||
# make sure param combination is valid for scrypt()
|
||||
try:
|
||||
_scrypt.validate(1 << cls.default_rounds, cls.block_size, cls.parallelism)
|
||||
except ValueError as err:
|
||||
raise suppress_cause(ValueError("scrypt: invalid settings combination: " + str(err)))
|
||||
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# parsing
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
return cls(**cls.parse(hash))
|
||||
|
||||
@classmethod
|
||||
def parse(cls, hash):
|
||||
ident, suffix = cls._parse_ident(hash)
|
||||
func = getattr(cls, "_parse_%s_string" % ident.strip(_UDOLLAR), None)
|
||||
if func:
|
||||
return func(suffix)
|
||||
else:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
#
|
||||
# passlib's format:
|
||||
# $scrypt$ln=<logN>,r=<r>,p=<p>$<salt>[$<digest>]
|
||||
# where:
|
||||
# logN, r, p -- decimal-encoded positive integer, no zero-padding
|
||||
# logN -- log cost setting
|
||||
# r -- block size setting (usually 8)
|
||||
# p -- parallelism setting (usually 1)
|
||||
# salt, digest -- b64-nopad encoded bytes
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_scrypt_string(cls, suffix):
|
||||
# break params, salt, and digest sections
|
||||
parts = suffix.split("$")
|
||||
if len(parts) == 3:
|
||||
params, salt, digest = parts
|
||||
elif len(parts) == 2:
|
||||
params, salt = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed hash")
|
||||
|
||||
# break params apart
|
||||
parts = params.split(",")
|
||||
if len(parts) == 3:
|
||||
nstr, bstr, pstr = parts
|
||||
assert nstr.startswith("ln=")
|
||||
assert bstr.startswith("r=")
|
||||
assert pstr.startswith("p=")
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed settings field")
|
||||
|
||||
return dict(
|
||||
ident=IDENT_SCRYPT,
|
||||
rounds=int(nstr[3:]),
|
||||
block_size=int(bstr[2:]),
|
||||
parallelism=int(pstr[2:]),
|
||||
salt=b64s_decode(salt.encode("ascii")),
|
||||
checksum=b64s_decode(digest.encode("ascii")) if digest else None,
|
||||
)
|
||||
|
||||
#
|
||||
# official format specification defined at
|
||||
# https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt
|
||||
# format:
|
||||
# $7$<N><rrrrr><ppppp><salt...>[$<digest>]
|
||||
# 0 12345 67890 1
|
||||
# where:
|
||||
# All bytes use h64-little-endian encoding
|
||||
# N: 6-bit log cost setting
|
||||
# r: 30-bit block size setting
|
||||
# p: 30-bit parallelism setting
|
||||
# salt: variable length salt bytes
|
||||
# digest: fixed 32-byte digest
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_7_string(cls, suffix):
|
||||
# XXX: annoyingly, official spec embeds salt *raw*, yet doesn't specify a hash encoding.
|
||||
# so assuming only h64 chars are valid for salt, and are ASCII encoded.
|
||||
|
||||
# split into params & digest
|
||||
parts = suffix.encode("ascii").split(b"$")
|
||||
if len(parts) == 2:
|
||||
params, digest = parts
|
||||
elif len(parts) == 1:
|
||||
params, = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError()
|
||||
|
||||
# parse params & return
|
||||
if len(params) < 11:
|
||||
raise uh.exc.MalformedHashError(cls, "params field too short")
|
||||
return dict(
|
||||
ident=IDENT_7,
|
||||
rounds=h64.decode_int6(params[:1]),
|
||||
block_size=h64.decode_int30(params[1:6]),
|
||||
parallelism=h64.decode_int30(params[6:11]),
|
||||
salt=params[11:],
|
||||
checksum=h64.decode_bytes(digest) if digest else None,
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
def to_string(self):
|
||||
ident = self.ident
|
||||
if ident == IDENT_SCRYPT:
|
||||
return "$scrypt$ln=%d,r=%d,p=%d$%s$%s" % (
|
||||
self.rounds,
|
||||
self.block_size,
|
||||
self.parallelism,
|
||||
bascii_to_str(b64s_encode(self.salt)),
|
||||
bascii_to_str(b64s_encode(self.checksum)),
|
||||
)
|
||||
else:
|
||||
assert ident == IDENT_7
|
||||
salt = self.salt
|
||||
try:
|
||||
salt.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
raise suppress_cause(NotImplementedError("scrypt $7$ hashes dont support non-ascii salts"))
|
||||
return bascii_to_str(b"".join([
|
||||
b"$7$",
|
||||
h64.encode_int6(self.rounds),
|
||||
h64.encode_int30(self.block_size),
|
||||
h64.encode_int30(self.parallelism),
|
||||
self.salt,
|
||||
b"$",
|
||||
h64.encode_bytes(self.checksum)
|
||||
]))
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, block_size=None, **kwds):
|
||||
super(scrypt, self).__init__(**kwds)
|
||||
|
||||
# init block size
|
||||
if block_size is None:
|
||||
assert uh.validate_default_value(self, self.block_size, self._norm_block_size,
|
||||
param="block_size")
|
||||
else:
|
||||
self.block_size = self._norm_block_size(block_size)
|
||||
|
||||
# NOTE: if hash contains invalid complex constraint, relying on error
|
||||
# being raised by scrypt call in _calc_checksum()
|
||||
|
||||
@classmethod
|
||||
def _norm_block_size(cls, block_size, relaxed=False):
|
||||
return uh.norm_integer(cls, block_size, min=1, param="block_size", relaxed=relaxed)
|
||||
|
||||
def _generate_salt(self):
|
||||
salt = super(scrypt, self)._generate_salt()
|
||||
if self.ident == IDENT_7:
|
||||
# this format doesn't support non-ascii salts.
|
||||
# as workaround, we take raw bytes, encoded to base64
|
||||
salt = b64s_encode(salt)
|
||||
return salt
|
||||
|
||||
#===================================================================
|
||||
# backend configuration
|
||||
# NOTE: this following HasManyBackends' API, but provides it's own implementation,
|
||||
# which actually switches the backend that 'passlib.crypto.scrypt.scrypt()' uses.
|
||||
#===================================================================
|
||||
|
||||
@classproperty
|
||||
def backends(cls):
|
||||
return _scrypt.backend_values
|
||||
|
||||
@classmethod
|
||||
def get_backend(cls):
|
||||
return _scrypt.backend
|
||||
|
||||
@classmethod
|
||||
def has_backend(cls, name="any"):
|
||||
try:
|
||||
cls.set_backend(name, dryrun=True)
|
||||
return True
|
||||
except uh.exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def set_backend(cls, name="any", dryrun=False):
|
||||
_scrypt._set_backend(name, dryrun=dryrun)
|
||||
|
||||
#===================================================================
|
||||
# digest calculation
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
secret = to_bytes(secret, param="secret")
|
||||
return _scrypt.scrypt(secret, self.salt, n=(1 << self.rounds), r=self.block_size,
|
||||
p=self.parallelism, keylen=self.checksum_size)
|
||||
|
||||
#===================================================================
|
||||
# hash migration
|
||||
#===================================================================
|
||||
|
||||
def _calc_needs_update(self, **kwds):
|
||||
"""
|
||||
mark hash as needing update if rounds is outside desired bounds.
|
||||
"""
|
||||
# XXX: for now, marking all hashes which don't have matching block_size setting
|
||||
if self.block_size != type(self).block_size:
|
||||
return True
|
||||
return super(scrypt, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
158
venv/Lib/site-packages/passlib/handlers/sha1_crypt.py
Normal file
158
venv/Lib/site-packages/passlib/handlers/sha1_crypt.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""passlib.handlers.sha1_crypt
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import u, unicode, irange
|
||||
from passlib.crypto.digest import compile_hmac
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
]
|
||||
#=============================================================================
|
||||
# sha1-crypt
|
||||
#=============================================================================
|
||||
_BNULL = b'\x00'
|
||||
|
||||
class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the SHA1-Crypt 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:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, an 8 character one will be autogenerated (this is recommended).
|
||||
If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 8 bytes, but can be any value between 0 and 64.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 480000, must be between 1 and 4294967295, inclusive.
|
||||
|
||||
: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 = "sha1_crypt"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
ident = u("$sha1$")
|
||||
checksum_size = 28
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 8
|
||||
max_salt_size = 64
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 480000 # current passlib default
|
||||
min_rounds = 1 # really, this should be higher.
|
||||
max_rounds = 4294967295 # 32-bit integer limit
|
||||
rounds_cost = "linear"
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
@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, config=False):
|
||||
chk = None if config else self.checksum
|
||||
return uh.render_mc3(self.ident, self.rounds, self.salt, chk)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHim'
|
||||
'ExLaiSFlGkAe'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.to_string(config=True)
|
||||
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) + 29:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-28:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# 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):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
if _BNULL in secret:
|
||||
raise uh.exc.NullPasswordError(self)
|
||||
rounds = self.rounds
|
||||
# NOTE: this seed value is NOT the same as the config string
|
||||
result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii")
|
||||
# NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
|
||||
keyed_hmac = compile_hmac("sha1", secret)
|
||||
for _ in irange(rounds):
|
||||
result = keyed_hmac(result)
|
||||
return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
|
||||
|
||||
_chk_offsets = [
|
||||
2,1,0,
|
||||
5,4,3,
|
||||
8,7,6,
|
||||
11,10,9,
|
||||
14,13,12,
|
||||
17,16,15,
|
||||
0,19,18,
|
||||
]
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
534
venv/Lib/site-packages/passlib/handlers/sha2_crypt.py
Normal file
534
venv/Lib/site-packages/passlib/handlers/sha2_crypt.py
Normal file
@@ -0,0 +1,534 @@
|
||||
"""passlib.handlers.sha2_crypt - SHA256-Crypt / SHA512-Crypt"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import hashlib
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt, \
|
||||
repeat_string, to_unicode
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import byte_elem_value, u, \
|
||||
uascii_to_str, unicode
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"sha512_crypt",
|
||||
"sha256_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# pure-python backend, used by both sha256_crypt & sha512_crypt
|
||||
# when crypt.crypt() backend is not available.
|
||||
#=============================================================================
|
||||
_BNULL = b'\x00'
|
||||
|
||||
# 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 sha256_crypt digest
|
||||
_256_transpose_map = (
|
||||
20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5,
|
||||
25, 15, 26, 16, 6, 17, 7, 27, 8, 28, 18, 29, 19, 9, 30, 31,
|
||||
)
|
||||
|
||||
# map used to transpose bytes when encoding final sha512_crypt digest
|
||||
_512_transpose_map = (
|
||||
42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26,
|
||||
5, 47, 48, 27, 6, 7, 49, 28, 29, 8, 50, 51, 30, 9, 10, 52,
|
||||
31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15,
|
||||
16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63,
|
||||
)
|
||||
|
||||
def _raw_sha2_crypt(pwd, salt, rounds, use_512=False):
|
||||
"""perform raw sha256-crypt / sha512-crypt
|
||||
|
||||
this function provides a pure-python implementation of the internals
|
||||
for the SHA256-Crypt and SHA512-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 rounds: linear rounds cost
|
||||
:arg use_512: use sha512-crypt instead of sha256-crypt mode
|
||||
|
||||
:returns:
|
||||
encoded checksum chars
|
||||
"""
|
||||
#===================================================================
|
||||
# init & validate inputs
|
||||
#===================================================================
|
||||
|
||||
# NOTE: the setup portion of this algorithm scales ~linearly in time
|
||||
# with the size of the password, making it vulnerable to a DOS from
|
||||
# unreasonably large inputs. the following code has some optimizations
|
||||
# which would make things even worse, using O(pwd_len**2) memory
|
||||
# when calculating digest P.
|
||||
#
|
||||
# to mitigate these two issues: 1) this code switches to a
|
||||
# O(pwd_len)-memory algorithm for passwords that are much larger
|
||||
# than average, and 2) Passlib enforces a library-wide max limit on
|
||||
# the size of passwords it will allow, to prevent this algorithm and
|
||||
# others from being DOSed in this way (see passlib.exc.PasswordSizeError
|
||||
# for details).
|
||||
|
||||
# validate secret
|
||||
if isinstance(pwd, unicode):
|
||||
# XXX: not sure what official unicode policy is, using this as default
|
||||
pwd = pwd.encode("utf-8")
|
||||
assert isinstance(pwd, bytes)
|
||||
if _BNULL in pwd:
|
||||
raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt)
|
||||
pwd_len = len(pwd)
|
||||
|
||||
# validate rounds
|
||||
assert 1000 <= rounds <= 999999999, "invalid rounds"
|
||||
# NOTE: spec says out-of-range rounds should be clipped, instead of
|
||||
# causing an error. this function assumes that's been taken care of
|
||||
# by the handler class.
|
||||
|
||||
# validate salt
|
||||
assert isinstance(salt, unicode), "salt not unicode"
|
||||
salt = salt.encode("ascii")
|
||||
salt_len = len(salt)
|
||||
assert salt_len < 17, "salt too large"
|
||||
# NOTE: spec says salts larger than 16 bytes should be truncated,
|
||||
# instead of causing an error. this function assumes that's been
|
||||
# taken care of by the handler class.
|
||||
|
||||
# load sha256/512 specific constants
|
||||
if use_512:
|
||||
hash_const = hashlib.sha512
|
||||
transpose_map = _512_transpose_map
|
||||
else:
|
||||
hash_const = hashlib.sha256
|
||||
transpose_map = _256_transpose_map
|
||||
|
||||
#===================================================================
|
||||
# digest B - used as subinput to digest A
|
||||
#===================================================================
|
||||
db = hash_const(pwd + salt + pwd).digest()
|
||||
|
||||
#===================================================================
|
||||
# digest A - used to initialize first round of digest C
|
||||
#===================================================================
|
||||
# start out with pwd + salt
|
||||
a_ctx = hash_const(pwd + 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))
|
||||
|
||||
# for each bit in pwd_len: add b if it's 1, or pwd if it's 0
|
||||
i = pwd_len
|
||||
while i:
|
||||
a_ctx_update(db if i & 1 else pwd)
|
||||
i >>= 1
|
||||
|
||||
# finish A
|
||||
da = a_ctx.digest()
|
||||
|
||||
#===================================================================
|
||||
# digest P from password - used instead of password itself
|
||||
# when calculating digest C.
|
||||
#===================================================================
|
||||
if pwd_len < 96:
|
||||
# this method is faster under python, but uses O(pwd_len**2) memory;
|
||||
# so we don't use it for larger passwords to avoid a potential DOS.
|
||||
dp = repeat_string(hash_const(pwd * pwd_len).digest(), pwd_len)
|
||||
else:
|
||||
# this method is slower under python, but uses a fixed amount of memory.
|
||||
tmp_ctx = hash_const(pwd)
|
||||
tmp_ctx_update = tmp_ctx.update
|
||||
i = pwd_len-1
|
||||
while i:
|
||||
tmp_ctx_update(pwd)
|
||||
i -= 1
|
||||
dp = repeat_string(tmp_ctx.digest(), pwd_len)
|
||||
assert len(dp) == pwd_len
|
||||
|
||||
#===================================================================
|
||||
# digest S - used instead of salt itself when calculating digest C
|
||||
#===================================================================
|
||||
ds = hash_const(salt * (16 + byte_elem_value(da[0]))).digest()[:salt_len]
|
||||
assert len(ds) == salt_len, "salt_len somehow > hash_len!"
|
||||
|
||||
#===================================================================
|
||||
# digest C - for a variable number of rounds, combine A, S, and P
|
||||
# digests in various ways; in order to burn CPU time.
|
||||
#===================================================================
|
||||
|
||||
# NOTE: the original SHA256/512-Crypt specification performs the C digest
|
||||
# calculation using the following loop:
|
||||
#
|
||||
##dc = da
|
||||
##i = 0
|
||||
##while i < rounds:
|
||||
## tmp_ctx = hash_const(dp if i & 1 else dc)
|
||||
## if i % 3:
|
||||
## tmp_ctx.update(ds)
|
||||
## if i % 7:
|
||||
## tmp_ctx.update(dp)
|
||||
## tmp_ctx.update(dc if i & 1 else dp)
|
||||
## 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', 'ds', and 'dp'; determined
|
||||
# by the 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 ds & dp for each round 0-41
|
||||
# * runs through as many 42-round blocks as possible
|
||||
# * runs through as many pairs of rounds as possible for remaining rounds
|
||||
# * performs once last round if the total rounds should be odd.
|
||||
#
|
||||
# this cuts out a lot of the control overhead incurred when running the
|
||||
# original loop 40,000+ times in python, resulting in ~20% increase in
|
||||
# speed under CPython (though still 2x slower than glibc crypt)
|
||||
|
||||
# prepare the 6 combinations of ds & dp which are needed
|
||||
# (order of 'perms' must match how _c_digest_offsets was generated)
|
||||
dp_dp = dp+dp
|
||||
dp_ds = dp+ds
|
||||
perms = [dp, dp_dp, dp_ds, dp_ds+dp, ds+dp, ds+dp_dp]
|
||||
|
||||
# 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 as many full 42-round blocks as possible
|
||||
dc = da
|
||||
blocks, tail = divmod(rounds, 42)
|
||||
while blocks:
|
||||
for even, odd in data:
|
||||
dc = hash_const(odd + hash_const(dc + even).digest()).digest()
|
||||
blocks -= 1
|
||||
|
||||
# perform any leftover rounds
|
||||
if tail:
|
||||
# perform any pairs of rounds
|
||||
pairs = tail>>1
|
||||
for even, odd in data[:pairs]:
|
||||
dc = hash_const(odd + hash_const(dc + even).digest()).digest()
|
||||
|
||||
# if rounds was odd, do one last round (since we started at 0,
|
||||
# last round will be an even-numbered round)
|
||||
if tail & 1:
|
||||
dc = hash_const(dc + data[pairs][0]).digest()
|
||||
|
||||
#===================================================================
|
||||
# encode digest using appropriate transpose map
|
||||
#===================================================================
|
||||
return h64.encode_transposed_bytes(dc, transpose_map).decode("ascii")
|
||||
|
||||
#=============================================================================
|
||||
# handlers
|
||||
#=============================================================================
|
||||
_UROUNDS = u("rounds=")
|
||||
_UDOLLAR = u("$")
|
||||
_UZERO = u("0")
|
||||
|
||||
class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
|
||||
uh.GenericHandler):
|
||||
"""class containing common code shared by sha256_crypt & sha512_crypt"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
# name - set by subclass
|
||||
setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size")
|
||||
# ident - set by subclass
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
# checksum_size - set by subclass
|
||||
|
||||
max_salt_size = 16
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
min_rounds = 1000 # bounds set by spec
|
||||
max_rounds = 999999999 # bounds set by spec
|
||||
rounds_cost = "linear"
|
||||
|
||||
_cdb_use_512 = False # flag for _calc_digest_builtin()
|
||||
_rounds_prefix = None # ident + _UROUNDS
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
implicit_rounds = False
|
||||
|
||||
def __init__(self, implicit_rounds=None, **kwds):
|
||||
super(_SHA2_Common, self).__init__(**kwds)
|
||||
# if user calls hash() w/ 5000 rounds, default to compact form.
|
||||
if implicit_rounds is None:
|
||||
implicit_rounds = (self.use_defaults and self.rounds == 5000)
|
||||
self.implicit_rounds = implicit_rounds
|
||||
|
||||
def _parse_salt(self, salt):
|
||||
# required per SHA2-crypt spec -- truncate config salts rather than throwing error
|
||||
return self._norm_salt(salt, relaxed=self.checksum is None)
|
||||
|
||||
def _parse_rounds(self, rounds):
|
||||
# required per SHA2-crypt spec -- clip config rounds rather than throwing error
|
||||
return self._norm_rounds(rounds, relaxed=self.checksum is None)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
# basic format this parses -
|
||||
# $5$[rounds=<rounds>$]<salt>[$<checksum>]
|
||||
|
||||
# TODO: this *could* use uh.parse_mc3(), except that the rounds
|
||||
# portion has a slightly different grammar.
|
||||
|
||||
# convert to unicode, check for ident prefix, split on dollar signs.
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
ident = cls.ident
|
||||
if not hash.startswith(ident):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
assert len(ident) == 3
|
||||
parts = hash[3:].split(_UDOLLAR)
|
||||
|
||||
# extract rounds value
|
||||
if parts[0].startswith(_UROUNDS):
|
||||
assert len(_UROUNDS) == 7
|
||||
rounds = parts.pop(0)[7:]
|
||||
if rounds.startswith(_UZERO) and rounds != _UZERO:
|
||||
raise uh.exc.ZeroPaddedRoundsError(cls)
|
||||
rounds = int(rounds)
|
||||
implicit_rounds = False
|
||||
else:
|
||||
rounds = 5000
|
||||
implicit_rounds = True
|
||||
|
||||
# rest should be salt and checksum
|
||||
if len(parts) == 2:
|
||||
salt, chk = parts
|
||||
elif len(parts) == 1:
|
||||
salt = parts[0]
|
||||
chk = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
|
||||
# return new object
|
||||
return cls(
|
||||
rounds=rounds,
|
||||
salt=salt,
|
||||
checksum=chk or None,
|
||||
implicit_rounds=implicit_rounds,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
if self.rounds == 5000 and self.implicit_rounds:
|
||||
hash = u("%s%s$%s") % (self.ident, self.salt,
|
||||
self.checksum or u(''))
|
||||
else:
|
||||
hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds,
|
||||
self.salt, self.checksum or u(''))
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# backends
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
|
||||
#: test hash for OS detection -- provided by subclass
|
||||
_test_hash = None
|
||||
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt(*cls._test_hash):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.to_string()
|
||||
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)
|
||||
# NOTE: avoiding full parsing routine via from_string().checksum,
|
||||
# and just extracting the bit we need.
|
||||
cs = self.checksum_size
|
||||
if not hash.startswith(self.ident) or hash[-cs-1] != _UDOLLAR:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-cs:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# 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_sha2_crypt(secret, self.salt, self.rounds,
|
||||
self._cdb_use_512)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class sha256_crypt(_SHA2_Common):
|
||||
"""This class implements the SHA256-Crypt 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:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 535000, must be between 1000 and 999999999, inclusive.
|
||||
|
||||
.. note::
|
||||
per the official specification, when the rounds parameter is set to 5000,
|
||||
it may be omitted from the hash string.
|
||||
|
||||
: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
|
||||
|
||||
..
|
||||
commented out, currently only supported by :meth:`hash`, and not via :meth:`using`:
|
||||
|
||||
:type implicit_rounds: bool
|
||||
:param implicit_rounds:
|
||||
this is an internal option which generally doesn't need to be touched.
|
||||
|
||||
this flag determines whether the hash should omit the rounds parameter
|
||||
when encoding it to a string; this is only permitted by the spec for rounds=5000,
|
||||
and the flag is ignored otherwise. the spec requires the two different
|
||||
encodings be preserved as they are, instead of normalizing them.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "sha256_crypt"
|
||||
ident = u("$5$")
|
||||
checksum_size = 43
|
||||
# NOTE: using 25/75 weighting of builtin & os_crypt backends
|
||||
default_rounds = 535000
|
||||
|
||||
#===================================================================
|
||||
# backends
|
||||
#===================================================================
|
||||
_test_hash = ("test", "$5$rounds=1000$test$QmQADEXMG8POI5W"
|
||||
"Dsaeho0P36yK3Tcrgboabng6bkb/")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# sha 512 crypt
|
||||
#=============================================================================
|
||||
class sha512_crypt(_SHA2_Common):
|
||||
"""This class implements the SHA512-Crypt 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:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 656000, must be between 1000 and 999999999, inclusive.
|
||||
|
||||
.. note::
|
||||
per the official specification, when the rounds parameter is set to 5000,
|
||||
it may be omitted from the hash string.
|
||||
|
||||
: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
|
||||
|
||||
..
|
||||
commented out, currently only supported by :meth:`hash`, and not via :meth:`using`:
|
||||
|
||||
:type implicit_rounds: bool
|
||||
:param implicit_rounds:
|
||||
this is an internal option which generally doesn't need to be touched.
|
||||
|
||||
this flag determines whether the hash should omit the rounds parameter
|
||||
when encoding it to a string; this is only permitted by the spec for rounds=5000,
|
||||
and the flag is ignored otherwise. the spec requires the two different
|
||||
encodings be preserved as they are, instead of normalizing them.
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "sha512_crypt"
|
||||
ident = u("$6$")
|
||||
checksum_size = 86
|
||||
_cdb_use_512 = True
|
||||
# NOTE: using 25/75 weighting of builtin & os_crypt backends
|
||||
default_rounds = 656000
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
_test_hash = ("test", "$6$rounds=1000$test$2M/Lx6Mtobqj"
|
||||
"Ljobw0Wmo4Q5OFx5nVLJvmgseatA6oMn"
|
||||
"yWeBdRDx4DU.1H3eGmse6pgsOgDisWBG"
|
||||
"I5c7TZauS0")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
363
venv/Lib/site-packages/passlib/handlers/sun_md5_crypt.py
Normal file
363
venv/Lib/site-packages/passlib/handlers/sun_md5_crypt.py
Normal file
@@ -0,0 +1,363 @@
|
||||
"""passlib.handlers.sun_md5_crypt - Sun's Md5 Crypt, used on Solaris
|
||||
|
||||
.. warning::
|
||||
|
||||
This implementation may not reproduce
|
||||
the original Solaris behavior in some border cases.
|
||||
See documentation for details.
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import byte_elem_value, irange, u, \
|
||||
uascii_to_str, unicode, str_to_bascii
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"sun_md5_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# backend
|
||||
#=============================================================================
|
||||
# constant data used by alg - Hamlet act 3 scene 1 + null char
|
||||
# exact bytes as in http://www.ibiblio.org/pub/docs/books/gutenberg/etext98/2ws2610.txt
|
||||
# from Project Gutenberg.
|
||||
|
||||
MAGIC_HAMLET = (
|
||||
b"To be, or not to be,--that is the question:--\n"
|
||||
b"Whether 'tis nobler in the mind to suffer\n"
|
||||
b"The slings and arrows of outrageous fortune\n"
|
||||
b"Or to take arms against a sea of troubles,\n"
|
||||
b"And by opposing end them?--To die,--to sleep,--\n"
|
||||
b"No more; and by a sleep to say we end\n"
|
||||
b"The heartache, and the thousand natural shocks\n"
|
||||
b"That flesh is heir to,--'tis a consummation\n"
|
||||
b"Devoutly to be wish'd. To die,--to sleep;--\n"
|
||||
b"To sleep! perchance to dream:--ay, there's the rub;\n"
|
||||
b"For in that sleep of death what dreams may come,\n"
|
||||
b"When we have shuffled off this mortal coil,\n"
|
||||
b"Must give us pause: there's the respect\n"
|
||||
b"That makes calamity of so long life;\n"
|
||||
b"For who would bear the whips and scorns of time,\n"
|
||||
b"The oppressor's wrong, the proud man's contumely,\n"
|
||||
b"The pangs of despis'd love, the law's delay,\n"
|
||||
b"The insolence of office, and the spurns\n"
|
||||
b"That patient merit of the unworthy takes,\n"
|
||||
b"When he himself might his quietus make\n"
|
||||
b"With a bare bodkin? who would these fardels bear,\n"
|
||||
b"To grunt and sweat under a weary life,\n"
|
||||
b"But that the dread of something after death,--\n"
|
||||
b"The undiscover'd country, from whose bourn\n"
|
||||
b"No traveller returns,--puzzles the will,\n"
|
||||
b"And makes us rather bear those ills we have\n"
|
||||
b"Than fly to others that we know not of?\n"
|
||||
b"Thus conscience does make cowards of us all;\n"
|
||||
b"And thus the native hue of resolution\n"
|
||||
b"Is sicklied o'er with the pale cast of thought;\n"
|
||||
b"And enterprises of great pith and moment,\n"
|
||||
b"With this regard, their currents turn awry,\n"
|
||||
b"And lose the name of action.--Soft you now!\n"
|
||||
b"The fair Ophelia!--Nymph, in thy orisons\n"
|
||||
b"Be all my sins remember'd.\n\x00" #<- apparently null at end of C string is included (test vector won't pass otherwise)
|
||||
)
|
||||
|
||||
# NOTE: these sequences are pre-calculated iteration ranges used by X & Y loops w/in rounds function below
|
||||
xr = irange(7)
|
||||
_XY_ROUNDS = [
|
||||
tuple((i,i,i+3) for i in xr), # xrounds 0
|
||||
tuple((i,i+1,i+4) for i in xr), # xrounds 1
|
||||
tuple((i,i+8,(i+11)&15) for i in xr), # yrounds 0
|
||||
tuple((i,(i+9)&15, (i+12)&15) for i in xr), # yrounds 1
|
||||
]
|
||||
del xr
|
||||
|
||||
def raw_sun_md5_crypt(secret, rounds, salt):
|
||||
"""given secret & salt, return encoded sun-md5-crypt checksum"""
|
||||
global MAGIC_HAMLET
|
||||
assert isinstance(secret, bytes)
|
||||
assert isinstance(salt, bytes)
|
||||
|
||||
# validate rounds
|
||||
if rounds <= 0:
|
||||
rounds = 0
|
||||
real_rounds = 4096 + rounds
|
||||
# NOTE: spec seems to imply max 'rounds' is 2**32-1
|
||||
|
||||
# generate initial digest to start off round 0.
|
||||
# NOTE: algorithm 'salt' includes full config string w/ trailing "$"
|
||||
result = md5(secret + salt).digest()
|
||||
assert len(result) == 16
|
||||
|
||||
# NOTE: many things in this function have been inlined (to speed up the loop
|
||||
# as much as possible), to the point that this code barely resembles
|
||||
# the algorithm as described in the docs. in particular:
|
||||
#
|
||||
# * all accesses to a given bit have been inlined using the formula
|
||||
# rbitval(bit) = (rval((bit>>3) & 15) >> (bit & 7)) & 1
|
||||
#
|
||||
# * the calculation of coinflip value R has been inlined
|
||||
#
|
||||
# * the conditional division of coinflip value V has been inlined as
|
||||
# a shift right of 0 or 1.
|
||||
#
|
||||
# * the i, i+3, etc iterations are precalculated in lists.
|
||||
#
|
||||
# * the round-based conditional division of x & y is now performed
|
||||
# by choosing an appropriate precalculated list, so that it only
|
||||
# calculates the 7 bits which will actually be used.
|
||||
#
|
||||
X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS
|
||||
|
||||
# NOTE: % appears to be *slightly* slower than &, so we prefer & if possible
|
||||
|
||||
round = 0
|
||||
while round < real_rounds:
|
||||
# convert last result byte string to list of byte-ints for easy access
|
||||
rval = [ byte_elem_value(c) for c in result ].__getitem__
|
||||
|
||||
# build up X bit by bit
|
||||
x = 0
|
||||
xrounds = X_ROUNDS_1 if (rval((round>>3) & 15)>>(round & 7)) & 1 else X_ROUNDS_0
|
||||
for i, ia, ib in xrounds:
|
||||
a = rval(ia)
|
||||
b = rval(ib)
|
||||
v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1)
|
||||
x |= ((rval((v>>3)&15)>>(v&7))&1) << i
|
||||
|
||||
# build up Y bit by bit
|
||||
y = 0
|
||||
yrounds = Y_ROUNDS_1 if (rval(((round+64)>>3) & 15)>>(round & 7)) & 1 else Y_ROUNDS_0
|
||||
for i, ia, ib in yrounds:
|
||||
a = rval(ia)
|
||||
b = rval(ib)
|
||||
v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1)
|
||||
y |= ((rval((v>>3)&15)>>(v&7))&1) << i
|
||||
|
||||
# extract x'th and y'th bit, xoring them together to yeild "coin flip"
|
||||
coin = ((rval(x>>3) >> (x&7)) ^ (rval(y>>3) >> (y&7))) & 1
|
||||
|
||||
# construct hash for this round
|
||||
h = md5(result)
|
||||
if coin:
|
||||
h.update(MAGIC_HAMLET)
|
||||
h.update(unicode(round).encode("ascii"))
|
||||
result = h.digest()
|
||||
|
||||
round += 1
|
||||
|
||||
# encode output
|
||||
return h64.encode_transposed_bytes(result, _chk_offsets)
|
||||
|
||||
# NOTE: same offsets as md5_crypt
|
||||
_chk_offsets = (
|
||||
12,6,0,
|
||||
13,7,1,
|
||||
14,8,2,
|
||||
15,9,3,
|
||||
5,10,4,
|
||||
11,
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the Sun-MD5-Crypt 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:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, a salt will be autogenerated (this is recommended).
|
||||
If specified, it must be drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
If no salt is specified, this parameter can be used to specify
|
||||
the size (in characters) of the autogenerated salt.
|
||||
It currently defaults to 8.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 34000, must be between 0 and 4294963199, inclusive.
|
||||
|
||||
:type bare_salt: bool
|
||||
:param bare_salt:
|
||||
Optional flag used to enable an alternate salt digest behavior
|
||||
used by some hash strings in this scheme.
|
||||
This flag can be ignored by most users.
|
||||
Defaults to ``False``.
|
||||
(see :ref:`smc-bare-salt` for details).
|
||||
|
||||
: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
|
||||
#===================================================================
|
||||
name = "sun_md5_crypt"
|
||||
setting_kwds = ("salt", "rounds", "bare_salt", "salt_size")
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
checksum_size = 22
|
||||
|
||||
# NOTE: docs say max password length is 255.
|
||||
# release 9u2
|
||||
|
||||
# NOTE: not sure if original crypt has a salt size limit,
|
||||
# all instances that have been seen use 8 chars.
|
||||
default_salt_size = 8
|
||||
max_salt_size = None
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
default_rounds = 34000 # current passlib default
|
||||
min_rounds = 0
|
||||
max_rounds = 4294963199 ##2**32-1-4096
|
||||
# XXX: ^ not sure what it does if past this bound... does 32 int roll over?
|
||||
rounds_cost = "linear"
|
||||
|
||||
ident_values = (u("$md5$"), u("$md5,"))
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
bare_salt = False # flag to indicate legacy hashes that lack "$$" suffix
|
||||
|
||||
#===================================================================
|
||||
# constructor
|
||||
#===================================================================
|
||||
def __init__(self, bare_salt=False, **kwds):
|
||||
self.bare_salt = bare_salt
|
||||
super(sun_md5_crypt, self).__init__(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# internal helpers
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
hash = uh.to_unicode_for_identify(hash)
|
||||
return hash.startswith(cls.ident_values)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
|
||||
#
|
||||
# detect if hash specifies rounds value.
|
||||
# if so, parse and validate it.
|
||||
# by end, set 'rounds' to int value, and 'tail' containing salt+chk
|
||||
#
|
||||
if hash.startswith(u("$md5$")):
|
||||
rounds = 0
|
||||
salt_idx = 5
|
||||
elif hash.startswith(u("$md5,rounds=")):
|
||||
idx = hash.find(u("$"), 12)
|
||||
if idx == -1:
|
||||
raise uh.exc.MalformedHashError(cls, "unexpected end of rounds")
|
||||
rstr = hash[12:idx]
|
||||
try:
|
||||
rounds = int(rstr)
|
||||
except ValueError:
|
||||
raise uh.exc.MalformedHashError(cls, "bad rounds")
|
||||
if rstr != unicode(rounds):
|
||||
raise uh.exc.ZeroPaddedRoundsError(cls)
|
||||
if rounds == 0:
|
||||
# NOTE: not sure if this is forbidden by spec or not;
|
||||
# but allowing it would complicate things,
|
||||
# and it should never occur anyways.
|
||||
raise uh.exc.MalformedHashError(cls, "explicit zero rounds")
|
||||
salt_idx = idx+1
|
||||
else:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
#
|
||||
# salt/checksum separation is kinda weird,
|
||||
# to deal cleanly with some backward-compatible workarounds
|
||||
# implemented by original implementation.
|
||||
#
|
||||
chk_idx = hash.rfind(u("$"), salt_idx)
|
||||
if chk_idx == -1:
|
||||
# ''-config for $-hash
|
||||
salt = hash[salt_idx:]
|
||||
chk = None
|
||||
bare_salt = True
|
||||
elif chk_idx == len(hash)-1:
|
||||
if chk_idx > salt_idx and hash[-2] == u("$"):
|
||||
raise uh.exc.MalformedHashError(cls, "too many '$' separators")
|
||||
# $-config for $$-hash
|
||||
salt = hash[salt_idx:-1]
|
||||
chk = None
|
||||
bare_salt = False
|
||||
elif chk_idx > 0 and hash[chk_idx-1] == u("$"):
|
||||
# $$-hash
|
||||
salt = hash[salt_idx:chk_idx-1]
|
||||
chk = hash[chk_idx+1:]
|
||||
bare_salt = False
|
||||
else:
|
||||
# $-hash
|
||||
salt = hash[salt_idx:chk_idx]
|
||||
chk = hash[chk_idx+1:]
|
||||
bare_salt = True
|
||||
|
||||
return cls(
|
||||
rounds=rounds,
|
||||
salt=salt,
|
||||
checksum=chk,
|
||||
bare_salt=bare_salt,
|
||||
)
|
||||
|
||||
def to_string(self, _withchk=True):
|
||||
ss = u('') if self.bare_salt else u('$')
|
||||
rounds = self.rounds
|
||||
if rounds > 0:
|
||||
hash = u("$md5,rounds=%d$%s%s") % (rounds, self.salt, ss)
|
||||
else:
|
||||
hash = u("$md5$%s%s") % (self.salt, ss)
|
||||
if _withchk:
|
||||
chk = self.checksum
|
||||
hash = u("%s$%s") % (hash, chk)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# primary interface
|
||||
#===================================================================
|
||||
# TODO: if we're on solaris, check for native crypt() support.
|
||||
# this will require extra testing, to make sure native crypt
|
||||
# actually behaves correctly. of particular importance:
|
||||
# when using ""-config, make sure to append "$x" to string.
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: no reference for how sun_md5_crypt handles unicode
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
config = str_to_bascii(self.to_string(_withchk=False))
|
||||
return raw_sun_md5_crypt(secret, self.rounds, config).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
334
venv/Lib/site-packages/passlib/handlers/windows.py
Normal file
334
venv/Lib/site-packages/passlib/handlers/windows.py
Normal file
@@ -0,0 +1,334 @@
|
||||
"""passlib.handlers.nthash - Microsoft Windows -related hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode, right_pad_string
|
||||
from passlib.utils.compat import unicode
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
md4 = lookup_hash("md4").const
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"lmhash",
|
||||
"nthash",
|
||||
"bsd_nthash",
|
||||
"msdcc",
|
||||
"msdcc2",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# lanman hash
|
||||
#=============================================================================
|
||||
class lmhash(uh.TruncateMixin, uh.HasEncodingContext, uh.StaticHandler):
|
||||
"""This class implements the Lan Manager Password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts a single
|
||||
optional keyword:
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, this will silently truncate passwords larger than 14 bytes.
|
||||
Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.verify` methods accept a single
|
||||
optional keyword:
|
||||
|
||||
:type encoding: str
|
||||
:param encoding:
|
||||
|
||||
This specifies what character encoding LMHASH should use when
|
||||
calculating digest. It defaults to ``cp437``, the most
|
||||
common encoding encountered.
|
||||
|
||||
Note that while this class outputs digests in lower-case hexadecimal,
|
||||
it will accept upper-case as well.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "lmhash"
|
||||
setting_kwds = ("truncate_error",)
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 14
|
||||
|
||||
#--------------------
|
||||
# custom
|
||||
#--------------------
|
||||
default_encoding = "cp437"
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
|
||||
return hexlify(self.raw(secret, self.encoding)).decode("ascii")
|
||||
|
||||
# magic constant used by LMHASH
|
||||
_magic = b"KGS!@#$%"
|
||||
|
||||
@classmethod
|
||||
def raw(cls, secret, encoding=None):
|
||||
"""encode password using LANMAN hash algorithm.
|
||||
|
||||
:type secret: unicode or utf-8 encoded bytes
|
||||
:arg secret: secret to hash
|
||||
:type encoding: str
|
||||
:arg encoding:
|
||||
optional encoding to use for unicode inputs.
|
||||
this defaults to ``cp437``, which is the
|
||||
common case for most situations.
|
||||
|
||||
:returns: returns string of raw bytes
|
||||
"""
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
# some nice empircal data re: different encodings is at...
|
||||
# http://www.openwall.com/lists/john-dev/2011/08/01/2
|
||||
# http://www.freerainbowtables.com/phpBB3/viewtopic.php?t=387&p=12163
|
||||
from passlib.crypto.des import des_encrypt_block
|
||||
MAGIC = cls._magic
|
||||
if isinstance(secret, unicode):
|
||||
# perform uppercasing while we're still unicode,
|
||||
# to give a better shot at getting non-ascii chars right.
|
||||
# (though some codepages do NOT upper-case the same as unicode).
|
||||
secret = secret.upper().encode(encoding)
|
||||
elif isinstance(secret, bytes):
|
||||
# FIXME: just trusting ascii upper will work?
|
||||
# and if not, how to do codepage specific case conversion?
|
||||
# we could decode first using <encoding>,
|
||||
# but *that* might not always be right.
|
||||
secret = secret.upper()
|
||||
else:
|
||||
raise TypeError("secret must be unicode or bytes")
|
||||
secret = right_pad_string(secret, 14)
|
||||
return des_encrypt_block(secret[0:7], MAGIC) + \
|
||||
des_encrypt_block(secret[7:14], MAGIC)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# ntlm hash
|
||||
#=============================================================================
|
||||
class nthash(uh.StaticHandler):
|
||||
"""This class implements the NT Password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
|
||||
Note that while this class outputs lower-case hexadecimal digests,
|
||||
it will accept upper-case digests as well.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "nthash"
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
return hexlify(self.raw(secret)).decode("ascii")
|
||||
|
||||
@classmethod
|
||||
def raw(cls, secret):
|
||||
"""encode password using MD4-based NTHASH algorithm
|
||||
|
||||
:arg secret: secret as unicode or utf-8 encoded bytes
|
||||
|
||||
:returns: returns string of raw bytes
|
||||
"""
|
||||
secret = to_unicode(secret, "utf-8", param="secret")
|
||||
# XXX: found refs that say only first 128 chars are used.
|
||||
return md4(secret.encode("utf-16-le")).digest()
|
||||
|
||||
@classmethod
|
||||
def raw_nthash(cls, secret, hex=False):
|
||||
warn("nthash.raw_nthash() is deprecated, and will be removed "
|
||||
"in Passlib 1.8, please use nthash.raw() instead",
|
||||
DeprecationWarning)
|
||||
ret = nthash.raw(secret)
|
||||
return hexlify(ret).decode("ascii") if hex else ret
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$",
|
||||
doc="""The class support FreeBSD's representation of NTHASH
|
||||
(which is compatible with the :ref:`modular-crypt-format`),
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
""")
|
||||
|
||||
##class ntlm_pair(object):
|
||||
## "combined lmhash & nthash"
|
||||
## name = "ntlm_pair"
|
||||
## setting_kwds = ()
|
||||
## _hash_regex = re.compile(u"^(?P<lm>[0-9a-f]{32}):(?P<nt>[0-9][a-f]{32})$",
|
||||
## re.I)
|
||||
##
|
||||
## @classmethod
|
||||
## def identify(cls, hash):
|
||||
## hash = to_unicode(hash, "latin-1", "hash")
|
||||
## return len(hash) == 65 and cls._hash_regex.match(hash) is not None
|
||||
##
|
||||
## @classmethod
|
||||
## def hash(cls, secret, config=None):
|
||||
## if config is not None and not cls.identify(config):
|
||||
## raise uh.exc.InvalidHashError(cls)
|
||||
## return lmhash.hash(secret) + ":" + nthash.hash(secret)
|
||||
##
|
||||
## @classmethod
|
||||
## def verify(cls, secret, hash):
|
||||
## hash = to_unicode(hash, "ascii", "hash")
|
||||
## m = cls._hash_regex.match(hash)
|
||||
## if not m:
|
||||
## raise uh.exc.InvalidHashError(cls)
|
||||
## lm, nt = m.group("lm", "nt")
|
||||
## # NOTE: verify against both in case encoding issue
|
||||
## # causes one not to match.
|
||||
## return lmhash.verify(secret, lm) or nthash.verify(secret, nt)
|
||||
|
||||
#=============================================================================
|
||||
# msdcc v1
|
||||
#=============================================================================
|
||||
class msdcc(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements Microsoft's Domain Cached Credentials password hash,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has a fixed number of rounds, and uses the associated
|
||||
username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
|
||||
have the following optional keywords:
|
||||
|
||||
:type user: str
|
||||
:param user:
|
||||
String containing name of user account this password is associated with.
|
||||
This is required to properly calculate the hash.
|
||||
|
||||
This keyword is case-insensitive, and should contain just the username
|
||||
(e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
|
||||
|
||||
Note that while this class outputs lower-case hexadecimal digests,
|
||||
it will accept upper-case digests as well.
|
||||
"""
|
||||
name = "msdcc"
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
return hexlify(self.raw(secret, self.user)).decode("ascii")
|
||||
|
||||
@classmethod
|
||||
def raw(cls, secret, user):
|
||||
"""encode password using mscash v1 algorithm
|
||||
|
||||
:arg secret: secret as unicode or utf-8 encoded bytes
|
||||
:arg user: username to use as salt
|
||||
|
||||
:returns: returns string of raw bytes
|
||||
"""
|
||||
secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
|
||||
user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
|
||||
return md4(md4(secret).digest() + user).digest()
|
||||
|
||||
#=============================================================================
|
||||
# msdcc2 aka mscash2
|
||||
#=============================================================================
|
||||
class msdcc2(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements version 2 of Microsoft's Domain Cached Credentials
|
||||
password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has a fixed number of rounds, and uses the associated
|
||||
username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
|
||||
have the following extra keyword:
|
||||
|
||||
:type user: str
|
||||
:param user:
|
||||
String containing name of user account this password is associated with.
|
||||
This is required to properly calculate the hash.
|
||||
|
||||
This keyword is case-insensitive, and should contain just the username
|
||||
(e.g. ``Administrator``, not ``SOMEDOMAIN\\Administrator``).
|
||||
"""
|
||||
name = "msdcc2"
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
return hexlify(self.raw(secret, self.user)).decode("ascii")
|
||||
|
||||
@classmethod
|
||||
def raw(cls, secret, user):
|
||||
"""encode password using msdcc v2 algorithm
|
||||
|
||||
:type secret: unicode or utf-8 bytes
|
||||
:arg secret: secret
|
||||
|
||||
:type user: str
|
||||
:arg user: username to use as salt
|
||||
|
||||
:returns: returns string of raw bytes
|
||||
"""
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le")
|
||||
user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le")
|
||||
tmp = md4(md4(secret).digest() + user).digest()
|
||||
return pbkdf2_hmac("sha1", tmp, user, 10240, 16)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
Reference in New Issue
Block a user