Загрузить файлы в «venv/Lib/site-packages/passlib/handlers»
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
|
||||||
|
#=============================================================================
|
||||||
Reference in New Issue
Block a user