Загрузить файлы в «venv/Lib/site-packages/passlib/handlers»
This commit is contained in:
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