Initial commit
This commit is contained in:
3
backend/venv/Lib/site-packages/passlib/__init__.py
Normal file
3
backend/venv/Lib/site-packages/passlib/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""passlib - suite of password hashing & generation routines"""
|
||||
|
||||
__version__ = '1.7.4'
|
||||
2049
backend/venv/Lib/site-packages/passlib/_data/wordsets/bip39.txt
Normal file
2049
backend/venv/Lib/site-packages/passlib/_data/wordsets/bip39.txt
Normal file
File diff suppressed because it is too large
Load Diff
7776
backend/venv/Lib/site-packages/passlib/_data/wordsets/eff_long.txt
Normal file
7776
backend/venv/Lib/site-packages/passlib/_data/wordsets/eff_long.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1296
backend/venv/Lib/site-packages/passlib/_data/wordsets/eff_short.txt
Normal file
1296
backend/venv/Lib/site-packages/passlib/_data/wordsets/eff_short.txt
Normal file
File diff suppressed because it is too large
Load Diff
1255
backend/venv/Lib/site-packages/passlib/apache.py
Normal file
1255
backend/venv/Lib/site-packages/passlib/apache.py
Normal file
File diff suppressed because it is too large
Load Diff
245
backend/venv/Lib/site-packages/passlib/apps.py
Normal file
245
backend/venv/Lib/site-packages/passlib/apps.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""passlib.apps"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from itertools import chain
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.context import LazyCryptContext
|
||||
from passlib.utils import sys_bits
|
||||
# local
|
||||
__all__ = [
|
||||
'custom_app_context',
|
||||
'django_context',
|
||||
'ldap_context', 'ldap_nocrypt_context',
|
||||
'mysql_context', 'mysql4_context', 'mysql3_context',
|
||||
'phpass_context',
|
||||
'phpbb3_context',
|
||||
'postgres_context',
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# master containing all identifiable hashes
|
||||
#=============================================================================
|
||||
def _load_master_config():
|
||||
from passlib.registry import list_crypt_handlers
|
||||
|
||||
# get master list
|
||||
schemes = list_crypt_handlers()
|
||||
|
||||
# exclude the ones we know have ambiguous or greedy identify() methods.
|
||||
excluded = [
|
||||
# frequently confused for eachother
|
||||
'bigcrypt',
|
||||
'crypt16',
|
||||
|
||||
# no good identifiers
|
||||
'cisco_pix',
|
||||
'cisco_type7',
|
||||
'htdigest',
|
||||
'mysql323',
|
||||
'oracle10',
|
||||
|
||||
# all have same size
|
||||
'lmhash',
|
||||
'msdcc',
|
||||
'msdcc2',
|
||||
'nthash',
|
||||
|
||||
# plaintext handlers
|
||||
'plaintext',
|
||||
'ldap_plaintext',
|
||||
|
||||
# disabled handlers
|
||||
'django_disabled',
|
||||
'unix_disabled',
|
||||
'unix_fallback',
|
||||
]
|
||||
for name in excluded:
|
||||
schemes.remove(name)
|
||||
|
||||
# return config
|
||||
return dict(schemes=schemes, default="sha256_crypt")
|
||||
master_context = LazyCryptContext(onload=_load_master_config)
|
||||
|
||||
#=============================================================================
|
||||
# for quickly bootstrapping new custom applications
|
||||
#=============================================================================
|
||||
custom_app_context = LazyCryptContext(
|
||||
# choose some reasonbly strong schemes
|
||||
schemes=["sha512_crypt", "sha256_crypt"],
|
||||
|
||||
# set some useful global options
|
||||
default="sha256_crypt" if sys_bits < 64 else "sha512_crypt",
|
||||
|
||||
# set a good starting point for rounds selection
|
||||
sha512_crypt__min_rounds = 535000,
|
||||
sha256_crypt__min_rounds = 535000,
|
||||
|
||||
# if the admin user category is selected, make a much stronger hash,
|
||||
admin__sha512_crypt__min_rounds = 1024000,
|
||||
admin__sha256_crypt__min_rounds = 1024000,
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# django
|
||||
#=============================================================================
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1.0
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
_django10_schemes = [
|
||||
"django_salted_sha1",
|
||||
"django_salted_md5",
|
||||
"django_des_crypt",
|
||||
"hex_md5",
|
||||
"django_disabled",
|
||||
]
|
||||
|
||||
django10_context = LazyCryptContext(
|
||||
schemes=_django10_schemes,
|
||||
default="django_salted_sha1",
|
||||
deprecated=["hex_md5"],
|
||||
)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1.4
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
_django14_schemes = [
|
||||
"django_pbkdf2_sha256",
|
||||
"django_pbkdf2_sha1",
|
||||
"django_bcrypt"
|
||||
] + _django10_schemes
|
||||
|
||||
django14_context = LazyCryptContext(
|
||||
schemes=_django14_schemes,
|
||||
deprecated=_django10_schemes,
|
||||
)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1.6
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
_django16_schemes = list(_django14_schemes)
|
||||
_django16_schemes.insert(1, "django_bcrypt_sha256")
|
||||
django16_context = LazyCryptContext(
|
||||
schemes=_django16_schemes,
|
||||
deprecated=_django10_schemes,
|
||||
)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1.10
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
_django_110_schemes = [
|
||||
"django_pbkdf2_sha256",
|
||||
"django_pbkdf2_sha1",
|
||||
"django_argon2",
|
||||
"django_bcrypt",
|
||||
"django_bcrypt_sha256",
|
||||
"django_disabled",
|
||||
]
|
||||
django110_context = LazyCryptContext(schemes=_django_110_schemes)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 2.1
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
_django21_schemes = list(_django_110_schemes)
|
||||
_django21_schemes.remove("django_bcrypt")
|
||||
django21_context = LazyCryptContext(schemes=_django21_schemes)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# latest
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# this will always point to latest version in passlib
|
||||
django_context = django21_context
|
||||
|
||||
#=============================================================================
|
||||
# ldap
|
||||
#=============================================================================
|
||||
|
||||
#: standard ldap schemes
|
||||
std_ldap_schemes = [
|
||||
"ldap_salted_sha512",
|
||||
"ldap_salted_sha256",
|
||||
"ldap_salted_sha1",
|
||||
"ldap_salted_md5",
|
||||
"ldap_sha1",
|
||||
"ldap_md5",
|
||||
"ldap_plaintext",
|
||||
]
|
||||
|
||||
# create context with all std ldap schemes EXCEPT crypt
|
||||
ldap_nocrypt_context = LazyCryptContext(std_ldap_schemes)
|
||||
|
||||
# create context with all possible std ldap + ldap crypt schemes
|
||||
def _iter_ldap_crypt_schemes():
|
||||
from passlib.utils import unix_crypt_schemes
|
||||
return ('ldap_' + name for name in unix_crypt_schemes)
|
||||
|
||||
def _iter_ldap_schemes():
|
||||
"""helper which iterates over supported std ldap schemes"""
|
||||
return chain(std_ldap_schemes, _iter_ldap_crypt_schemes())
|
||||
ldap_context = LazyCryptContext(_iter_ldap_schemes())
|
||||
|
||||
### create context with all std ldap schemes + crypt schemes for localhost
|
||||
##def _iter_host_ldap_schemes():
|
||||
## "helper which iterates over supported std ldap schemes"
|
||||
## from passlib.handlers.ldap_digests import get_host_ldap_crypt_schemes
|
||||
## return chain(std_ldap_schemes, get_host_ldap_crypt_schemes())
|
||||
##ldap_host_context = LazyCryptContext(_iter_host_ldap_schemes())
|
||||
|
||||
#=============================================================================
|
||||
# mysql
|
||||
#=============================================================================
|
||||
mysql3_context = LazyCryptContext(["mysql323"])
|
||||
mysql4_context = LazyCryptContext(["mysql41", "mysql323"], deprecated="mysql323")
|
||||
mysql_context = mysql4_context # tracks latest mysql version supported
|
||||
|
||||
#=============================================================================
|
||||
# postgres
|
||||
#=============================================================================
|
||||
postgres_context = LazyCryptContext(["postgres_md5"])
|
||||
|
||||
#=============================================================================
|
||||
# phpass & variants
|
||||
#=============================================================================
|
||||
def _create_phpass_policy(**kwds):
|
||||
"""helper to choose default alg based on bcrypt availability"""
|
||||
kwds['default'] = 'bcrypt' if hash.bcrypt.has_backend() else 'phpass'
|
||||
return kwds
|
||||
|
||||
phpass_context = LazyCryptContext(
|
||||
schemes=["bcrypt", "phpass", "bsdi_crypt"],
|
||||
onload=_create_phpass_policy,
|
||||
)
|
||||
|
||||
phpbb3_context = LazyCryptContext(["phpass"], phpass__ident="H")
|
||||
|
||||
# TODO: support the drupal phpass variants (see phpass homepage)
|
||||
|
||||
#=============================================================================
|
||||
# roundup
|
||||
#=============================================================================
|
||||
|
||||
_std_roundup_schemes = [ "ldap_hex_sha1", "ldap_hex_md5", "ldap_des_crypt", "roundup_plaintext" ]
|
||||
roundup10_context = LazyCryptContext(_std_roundup_schemes)
|
||||
|
||||
# NOTE: 'roundup15' really applies to roundup 1.4.17+
|
||||
roundup_context = roundup15_context = LazyCryptContext(
|
||||
schemes=_std_roundup_schemes + [ "ldap_pbkdf2_sha1" ],
|
||||
deprecated=_std_roundup_schemes,
|
||||
default = "ldap_pbkdf2_sha1",
|
||||
ldap_pbkdf2_sha1__default_rounds = 10000,
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
2637
backend/venv/Lib/site-packages/passlib/context.py
Normal file
2637
backend/venv/Lib/site-packages/passlib/context.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
"""passlib.crypto -- package containing cryptographic primitives used by passlib"""
|
||||
@@ -0,0 +1,169 @@
|
||||
"""passlib.crypto._blowfish - pure-python eks-blowfish implementation for bcrypt
|
||||
|
||||
This is a pure-python implementation of the EKS-Blowfish algorithm described by
|
||||
Provos and Mazieres in `A Future-Adaptable Password Scheme
|
||||
<http://www.openbsd.org/papers/bcrypt-paper.ps>`_.
|
||||
|
||||
This package contains two submodules:
|
||||
|
||||
* ``_blowfish/base.py`` contains a class implementing the eks-blowfish algorithm
|
||||
using easy-to-examine code.
|
||||
|
||||
* ``_blowfish/unrolled.py`` contains a subclass which replaces some methods
|
||||
of the original class with sped-up versions, mainly using unrolled loops
|
||||
and local variables. this is the class which is actually used by
|
||||
Passlib to perform BCrypt in pure python.
|
||||
|
||||
This module is auto-generated by a script, ``_blowfish/_gen_files.py``.
|
||||
|
||||
Status
|
||||
------
|
||||
This implementation is usable, but is an order of magnitude too slow to be
|
||||
usable with real security. For "ok" security, BCrypt hashes should have at
|
||||
least 2**11 rounds (as of 2011). Assuming a desired response time <= 100ms,
|
||||
this means a BCrypt implementation should get at least 20 rounds/ms in order
|
||||
to be both usable *and* secure. On a 2 ghz cpu, this implementation gets
|
||||
roughly 0.09 rounds/ms under CPython (220x too slow), and 1.9 rounds/ms
|
||||
under PyPy (10x too slow).
|
||||
|
||||
History
|
||||
-------
|
||||
While subsequently modified considerly for Passlib, this code was originally
|
||||
based on `jBcrypt 0.2 <http://www.mindrot.org/projects/jBCrypt/>`_, which was
|
||||
released under the BSD license::
|
||||
|
||||
Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from itertools import chain
|
||||
import struct
|
||||
# pkg
|
||||
from passlib.utils import getrandbytes, rng
|
||||
from passlib.utils.binary import bcrypt64
|
||||
from passlib.utils.compat import BytesIO, unicode, u, native_string_types
|
||||
from passlib.crypto._blowfish.unrolled import BlowfishEngine
|
||||
# local
|
||||
__all__ = [
|
||||
'BlowfishEngine',
|
||||
'raw_bcrypt',
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# bcrypt constants
|
||||
#=============================================================================
|
||||
|
||||
# bcrypt constant data "OrpheanBeholderScryDoubt" as 6 integers
|
||||
BCRYPT_CDATA = [
|
||||
0x4f727068, 0x65616e42, 0x65686f6c,
|
||||
0x64657253, 0x63727944, 0x6f756274
|
||||
]
|
||||
|
||||
# struct used to encode ciphertext as digest (last output byte discarded)
|
||||
digest_struct = struct.Struct(">6I")
|
||||
|
||||
#=============================================================================
|
||||
# base bcrypt helper
|
||||
#
|
||||
# interface designed only for use by passlib.handlers.bcrypt:BCrypt
|
||||
# probably not suitable for other purposes
|
||||
#=============================================================================
|
||||
BNULL = b'\x00'
|
||||
|
||||
def raw_bcrypt(password, ident, salt, log_rounds):
|
||||
"""perform central password hashing step in bcrypt scheme.
|
||||
|
||||
:param password: the password to hash
|
||||
:param ident: identifier w/ minor version (e.g. 2, 2a)
|
||||
:param salt: the binary salt to use (encoded in bcrypt-base64)
|
||||
:param log_rounds: the log2 of the number of rounds (as int)
|
||||
:returns: bcrypt-base64 encoded checksum
|
||||
"""
|
||||
#===================================================================
|
||||
# parse inputs
|
||||
#===================================================================
|
||||
|
||||
# parse ident
|
||||
assert isinstance(ident, native_string_types)
|
||||
add_null_padding = True
|
||||
if ident == u('2a') or ident == u('2y') or ident == u('2b'):
|
||||
pass
|
||||
elif ident == u('2'):
|
||||
add_null_padding = False
|
||||
elif ident == u('2x'):
|
||||
raise ValueError("crypt_blowfish's buggy '2x' hashes are not "
|
||||
"currently supported")
|
||||
else:
|
||||
raise ValueError("unknown ident: %r" % (ident,))
|
||||
|
||||
# decode & validate salt
|
||||
assert isinstance(salt, bytes)
|
||||
salt = bcrypt64.decode_bytes(salt)
|
||||
if len(salt) < 16:
|
||||
raise ValueError("Missing salt bytes")
|
||||
elif len(salt) > 16:
|
||||
salt = salt[:16]
|
||||
|
||||
# prepare password
|
||||
assert isinstance(password, bytes)
|
||||
if add_null_padding:
|
||||
password += BNULL
|
||||
|
||||
# validate rounds
|
||||
if log_rounds < 4 or log_rounds > 31:
|
||||
raise ValueError("Bad number of rounds")
|
||||
|
||||
#===================================================================
|
||||
#
|
||||
# run EKS-Blowfish algorithm
|
||||
#
|
||||
# This uses the "enhanced key schedule" step described by
|
||||
# Provos and Mazieres in "A Future-Adaptable Password Scheme"
|
||||
# http://www.openbsd.org/papers/bcrypt-paper.ps
|
||||
#
|
||||
#===================================================================
|
||||
|
||||
engine = BlowfishEngine()
|
||||
|
||||
# convert password & salt into list of 18 32-bit integers (72 bytes total).
|
||||
pass_words = engine.key_to_words(password)
|
||||
salt_words = engine.key_to_words(salt)
|
||||
|
||||
# truncate salt_words to original 16 byte salt, or loop won't wrap
|
||||
# correctly when passed to .eks_salted_expand()
|
||||
salt_words16 = salt_words[:4]
|
||||
|
||||
# do EKS key schedule setup
|
||||
engine.eks_salted_expand(pass_words, salt_words16)
|
||||
|
||||
# apply password & salt keys to key schedule a bunch more times.
|
||||
rounds = 1<<log_rounds
|
||||
engine.eks_repeated_expand(pass_words, salt_words, rounds)
|
||||
|
||||
# encipher constant data, and encode to bytes as digest.
|
||||
data = list(BCRYPT_CDATA)
|
||||
i = 0
|
||||
while i < 6:
|
||||
data[i], data[i+1] = engine.repeat_encipher(data[i], data[i+1], 64)
|
||||
i += 2
|
||||
raw = digest_struct.pack(*data)[:-1]
|
||||
return bcrypt64.encode_bytes(raw)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,204 @@
|
||||
"""passlib.crypto._blowfish._gen_files - meta script that generates unrolled.py"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import os
|
||||
import textwrap
|
||||
# pkg
|
||||
from passlib.utils.compat import irange
|
||||
# local
|
||||
|
||||
#=============================================================================
|
||||
# helpers
|
||||
#=============================================================================
|
||||
def varlist(name, count):
|
||||
return ", ".join(name + str(x) for x in irange(count))
|
||||
|
||||
|
||||
def indent_block(block, padding):
|
||||
"""ident block of text"""
|
||||
lines = block.split("\n")
|
||||
return "\n".join(
|
||||
padding + line if line else ""
|
||||
for line in lines
|
||||
)
|
||||
|
||||
BFSTR = """\
|
||||
((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff)
|
||||
""".strip()
|
||||
|
||||
def render_encipher(write, indent=0):
|
||||
for i in irange(0, 15, 2):
|
||||
write(indent, """\
|
||||
# Feistel substitution on left word (round %(i)d)
|
||||
r ^= %(left)s ^ p%(i1)d
|
||||
|
||||
# Feistel substitution on right word (round %(i1)d)
|
||||
l ^= %(right)s ^ p%(i2)d
|
||||
""", i=i, i1=i+1, i2=i+2,
|
||||
left=BFSTR, right=BFSTR.replace("l","r"),
|
||||
)
|
||||
|
||||
def write_encipher_function(write, indent=0):
|
||||
write(indent, """\
|
||||
def encipher(self, l, r):
|
||||
\"""blowfish encipher a single 64-bit block encoded as two 32-bit ints\"""
|
||||
|
||||
(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9,
|
||||
p10, p11, p12, p13, p14, p15, p16, p17) = self.P
|
||||
S0, S1, S2, S3 = self.S
|
||||
|
||||
l ^= p0
|
||||
|
||||
""")
|
||||
render_encipher(write, indent+1)
|
||||
|
||||
write(indent+1, """\
|
||||
|
||||
return r ^ p17, l
|
||||
|
||||
""")
|
||||
|
||||
def write_expand_function(write, indent=0):
|
||||
write(indent, """\
|
||||
def expand(self, key_words):
|
||||
\"""unrolled version of blowfish key expansion\"""
|
||||
##assert len(key_words) >= 18, "size of key_words must be >= 18"
|
||||
|
||||
P, S = self.P, self.S
|
||||
S0, S1, S2, S3 = S
|
||||
|
||||
#=============================================================
|
||||
# integrate key
|
||||
#=============================================================
|
||||
""")
|
||||
for i in irange(18):
|
||||
write(indent+1, """\
|
||||
p%(i)d = P[%(i)d] ^ key_words[%(i)d]
|
||||
""", i=i)
|
||||
write(indent+1, """\
|
||||
|
||||
#=============================================================
|
||||
# update P
|
||||
#=============================================================
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[0] and P[1]
|
||||
#------------------------------------------------
|
||||
l, r = p0, 0
|
||||
|
||||
""")
|
||||
|
||||
render_encipher(write, indent+1)
|
||||
|
||||
write(indent+1, """\
|
||||
|
||||
p0, p1 = l, r = r ^ p17, l
|
||||
|
||||
""")
|
||||
|
||||
for i in irange(2, 18, 2):
|
||||
write(indent+1, """\
|
||||
#------------------------------------------------
|
||||
# update P[%(i)d] and P[%(i1)d]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
""", i=i, i1=i+1)
|
||||
|
||||
render_encipher(write, indent+1)
|
||||
|
||||
write(indent+1, """\
|
||||
p%(i)d, p%(i1)d = l, r = r ^ p17, l
|
||||
|
||||
""", i=i, i1=i+1)
|
||||
|
||||
write(indent+1, """\
|
||||
|
||||
#------------------------------------------------
|
||||
# save changes to original P array
|
||||
#------------------------------------------------
|
||||
P[:] = (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9,
|
||||
p10, p11, p12, p13, p14, p15, p16, p17)
|
||||
|
||||
#=============================================================
|
||||
# update S
|
||||
#=============================================================
|
||||
|
||||
for box in S:
|
||||
j = 0
|
||||
while j < 256:
|
||||
l ^= p0
|
||||
|
||||
""")
|
||||
|
||||
render_encipher(write, indent+3)
|
||||
|
||||
write(indent+3, """\
|
||||
|
||||
box[j], box[j+1] = l, r = r ^ p17, l
|
||||
j += 2
|
||||
""")
|
||||
|
||||
#=============================================================================
|
||||
# main
|
||||
#=============================================================================
|
||||
|
||||
def main():
|
||||
target = os.path.join(os.path.dirname(__file__), "unrolled.py")
|
||||
fh = file(target, "w")
|
||||
|
||||
def write(indent, msg, **kwds):
|
||||
literal = kwds.pop("literal", False)
|
||||
if kwds:
|
||||
msg %= kwds
|
||||
if not literal:
|
||||
msg = textwrap.dedent(msg.rstrip(" "))
|
||||
if indent:
|
||||
msg = indent_block(msg, " " * (indent*4))
|
||||
fh.write(msg)
|
||||
|
||||
write(0, """\
|
||||
\"""passlib.crypto._blowfish.unrolled - unrolled loop implementation of bcrypt,
|
||||
autogenerated by _gen_files.py
|
||||
|
||||
currently this override the encipher() and expand() methods
|
||||
with optimized versions, and leaves the other base.py methods alone.
|
||||
\"""
|
||||
#=================================================================
|
||||
# imports
|
||||
#=================================================================
|
||||
# pkg
|
||||
from passlib.crypto._blowfish.base import BlowfishEngine as _BlowfishEngine
|
||||
# local
|
||||
__all__ = [
|
||||
"BlowfishEngine",
|
||||
]
|
||||
#=================================================================
|
||||
#
|
||||
#=================================================================
|
||||
class BlowfishEngine(_BlowfishEngine):
|
||||
|
||||
""")
|
||||
|
||||
write_encipher_function(write, indent=1)
|
||||
write_expand_function(write, indent=1)
|
||||
|
||||
write(0, """\
|
||||
#=================================================================
|
||||
# eoc
|
||||
#=================================================================
|
||||
|
||||
#=================================================================
|
||||
# eof
|
||||
#=================================================================
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
441
backend/venv/Lib/site-packages/passlib/crypto/_blowfish/base.py
Normal file
441
backend/venv/Lib/site-packages/passlib/crypto/_blowfish/base.py
Normal file
@@ -0,0 +1,441 @@
|
||||
"""passlib.crypto._blowfish.base - unoptimized pure-python blowfish engine"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import struct
|
||||
# pkg
|
||||
from passlib.utils import repeat_string
|
||||
# local
|
||||
__all__ = [
|
||||
"BlowfishEngine",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# blowfish constants
|
||||
#=============================================================================
|
||||
BLOWFISH_P = BLOWFISH_S = None
|
||||
|
||||
def _init_constants():
|
||||
global BLOWFISH_P, BLOWFISH_S
|
||||
|
||||
# NOTE: blowfish's spec states these numbers are the hex representation
|
||||
# of the fractional portion of PI, in order.
|
||||
|
||||
# Initial contents of key schedule - 18 integers
|
||||
BLOWFISH_P = [
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b,
|
||||
]
|
||||
|
||||
# all 4 blowfish S boxes in one array - 256 integers per S box
|
||||
BLOWFISH_S = [
|
||||
# sbox 1
|
||||
[
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
|
||||
],
|
||||
# sbox 2
|
||||
[
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
|
||||
],
|
||||
# sbox 3
|
||||
[
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
|
||||
],
|
||||
# sbox 4
|
||||
[
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
|
||||
]
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# engine
|
||||
#=============================================================================
|
||||
class BlowfishEngine(object):
|
||||
|
||||
def __init__(self):
|
||||
if BLOWFISH_P is None:
|
||||
_init_constants()
|
||||
self.P = list(BLOWFISH_P)
|
||||
self.S = [ list(box) for box in BLOWFISH_S ]
|
||||
|
||||
#===================================================================
|
||||
# common helpers
|
||||
#===================================================================
|
||||
@staticmethod
|
||||
def key_to_words(data, size=18):
|
||||
"""convert data to tuple of <size> 4-byte integers, repeating or
|
||||
truncating data as needed to reach specified size"""
|
||||
assert isinstance(data, bytes)
|
||||
dlen = len(data)
|
||||
if not dlen:
|
||||
# return all zeros - original C code would just read the NUL after
|
||||
# the password, so mimicing that behavior for this edge case.
|
||||
return [0]*size
|
||||
|
||||
# repeat data until it fills up 4*size bytes
|
||||
data = repeat_string(data, size<<2)
|
||||
|
||||
# unpack
|
||||
return struct.unpack(">%dI" % (size,), data)
|
||||
|
||||
#===================================================================
|
||||
# blowfish routines
|
||||
#===================================================================
|
||||
def encipher(self, l, r):
|
||||
"""loop version of blowfish encipher routine"""
|
||||
P, S = self.P, self.S
|
||||
l ^= P[0]
|
||||
i = 1
|
||||
while i < 17:
|
||||
# Feistel substitution on left word
|
||||
r = ((((S[0][l >> 24] + S[1][(l >> 16) & 0xff]) ^ S[2][(l >> 8) & 0xff]) +
|
||||
S[3][l & 0xff]) & 0xffffffff) ^ P[i] ^ r
|
||||
# swap vars so even rounds do Feistel substition on right word
|
||||
l, r = r, l
|
||||
i += 1
|
||||
return r ^ P[17], l
|
||||
|
||||
# NOTE: decipher is same as above, just with reversed(P) instead.
|
||||
|
||||
def expand(self, key_words):
|
||||
"""perform stock Blowfish keyschedule setup"""
|
||||
assert len(key_words) >= 18, "key_words must be at least as large as P"
|
||||
P, S, encipher = self.P, self.S, self.encipher
|
||||
|
||||
i = 0
|
||||
while i < 18:
|
||||
P[i] ^= key_words[i]
|
||||
i += 1
|
||||
|
||||
i = l = r = 0
|
||||
while i < 18:
|
||||
P[i], P[i+1] = l,r = encipher(l,r)
|
||||
i += 2
|
||||
|
||||
for box in S:
|
||||
i = 0
|
||||
while i < 256:
|
||||
box[i], box[i+1] = l,r = encipher(l,r)
|
||||
i += 2
|
||||
|
||||
#===================================================================
|
||||
# eks-blowfish routines
|
||||
#===================================================================
|
||||
def eks_salted_expand(self, key_words, salt_words):
|
||||
"""perform EKS' salted version of Blowfish keyschedule setup"""
|
||||
# NOTE: this is the same as expand(), except for the addition
|
||||
# of the operations involving *salt_words*.
|
||||
|
||||
assert len(key_words) >= 18, "key_words must be at least as large as P"
|
||||
salt_size = len(salt_words)
|
||||
assert salt_size, "salt_words must not be empty"
|
||||
assert not salt_size & 1, "salt_words must have even length"
|
||||
P, S, encipher = self.P, self.S, self.encipher
|
||||
|
||||
i = 0
|
||||
while i < 18:
|
||||
P[i] ^= key_words[i]
|
||||
i += 1
|
||||
|
||||
s = i = l = r = 0
|
||||
while i < 18:
|
||||
l ^= salt_words[s]
|
||||
r ^= salt_words[s+1]
|
||||
s += 2
|
||||
if s == salt_size:
|
||||
s = 0
|
||||
P[i], P[i+1] = l,r = encipher(l,r) # next()
|
||||
i += 2
|
||||
|
||||
for box in S:
|
||||
i = 0
|
||||
while i < 256:
|
||||
l ^= salt_words[s]
|
||||
r ^= salt_words[s+1]
|
||||
s += 2
|
||||
if s == salt_size:
|
||||
s = 0
|
||||
box[i], box[i+1] = l,r = encipher(l,r) # next()
|
||||
i += 2
|
||||
|
||||
def eks_repeated_expand(self, key_words, salt_words, rounds):
|
||||
"""perform rounds stage of EKS keyschedule setup"""
|
||||
expand = self.expand
|
||||
n = 0
|
||||
while n < rounds:
|
||||
expand(key_words)
|
||||
expand(salt_words)
|
||||
n += 1
|
||||
|
||||
def repeat_encipher(self, l, r, count):
|
||||
"""repeatedly apply encipher operation to a block"""
|
||||
encipher = self.encipher
|
||||
n = 0
|
||||
while n < count:
|
||||
l, r = encipher(l, r)
|
||||
n += 1
|
||||
return l, r
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,771 @@
|
||||
"""passlib.crypto._blowfish.unrolled - unrolled loop implementation of bcrypt,
|
||||
autogenerated by _gen_files.py
|
||||
|
||||
currently this override the encipher() and expand() methods
|
||||
with optimized versions, and leaves the other base.py methods alone.
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# pkg
|
||||
from passlib.crypto._blowfish.base import BlowfishEngine as _BlowfishEngine
|
||||
# local
|
||||
__all__ = [
|
||||
"BlowfishEngine",
|
||||
]
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class BlowfishEngine(_BlowfishEngine):
|
||||
|
||||
def encipher(self, l, r):
|
||||
"""blowfish encipher a single 64-bit block encoded as two 32-bit ints"""
|
||||
|
||||
(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9,
|
||||
p10, p11, p12, p13, p14, p15, p16, p17) = self.P
|
||||
S0, S1, S2, S3 = self.S
|
||||
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
|
||||
return r ^ p17, l
|
||||
|
||||
def expand(self, key_words):
|
||||
"""unrolled version of blowfish key expansion"""
|
||||
##assert len(key_words) >= 18, "size of key_words must be >= 18"
|
||||
|
||||
P, S = self.P, self.S
|
||||
S0, S1, S2, S3 = S
|
||||
|
||||
#=============================================================
|
||||
# integrate key
|
||||
#=============================================================
|
||||
p0 = P[0] ^ key_words[0]
|
||||
p1 = P[1] ^ key_words[1]
|
||||
p2 = P[2] ^ key_words[2]
|
||||
p3 = P[3] ^ key_words[3]
|
||||
p4 = P[4] ^ key_words[4]
|
||||
p5 = P[5] ^ key_words[5]
|
||||
p6 = P[6] ^ key_words[6]
|
||||
p7 = P[7] ^ key_words[7]
|
||||
p8 = P[8] ^ key_words[8]
|
||||
p9 = P[9] ^ key_words[9]
|
||||
p10 = P[10] ^ key_words[10]
|
||||
p11 = P[11] ^ key_words[11]
|
||||
p12 = P[12] ^ key_words[12]
|
||||
p13 = P[13] ^ key_words[13]
|
||||
p14 = P[14] ^ key_words[14]
|
||||
p15 = P[15] ^ key_words[15]
|
||||
p16 = P[16] ^ key_words[16]
|
||||
p17 = P[17] ^ key_words[17]
|
||||
|
||||
#=============================================================
|
||||
# update P
|
||||
#=============================================================
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[0] and P[1]
|
||||
#------------------------------------------------
|
||||
l, r = p0, 0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
|
||||
p0, p1 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[2] and P[3]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p2, p3 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[4] and P[5]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p4, p5 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[6] and P[7]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p6, p7 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[8] and P[9]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p8, p9 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[10] and P[11]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p10, p11 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[12] and P[13]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p12, p13 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[14] and P[15]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p14, p15 = l, r = r ^ p17, l
|
||||
|
||||
#------------------------------------------------
|
||||
# update P[16] and P[17]
|
||||
#------------------------------------------------
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
p16, p17 = l, r = r ^ p17, l
|
||||
|
||||
|
||||
#------------------------------------------------
|
||||
# save changes to original P array
|
||||
#------------------------------------------------
|
||||
P[:] = (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9,
|
||||
p10, p11, p12, p13, p14, p15, p16, p17)
|
||||
|
||||
#=============================================================
|
||||
# update S
|
||||
#=============================================================
|
||||
|
||||
for box in S:
|
||||
j = 0
|
||||
while j < 256:
|
||||
l ^= p0
|
||||
|
||||
# Feistel substitution on left word (round 0)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p1
|
||||
|
||||
# Feistel substitution on right word (round 1)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p2
|
||||
# Feistel substitution on left word (round 2)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p3
|
||||
|
||||
# Feistel substitution on right word (round 3)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p4
|
||||
# Feistel substitution on left word (round 4)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p5
|
||||
|
||||
# Feistel substitution on right word (round 5)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p6
|
||||
# Feistel substitution on left word (round 6)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p7
|
||||
|
||||
# Feistel substitution on right word (round 7)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p8
|
||||
# Feistel substitution on left word (round 8)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p9
|
||||
|
||||
# Feistel substitution on right word (round 9)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p10
|
||||
# Feistel substitution on left word (round 10)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p11
|
||||
|
||||
# Feistel substitution on right word (round 11)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p12
|
||||
# Feistel substitution on left word (round 12)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p13
|
||||
|
||||
# Feistel substitution on right word (round 13)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p14
|
||||
# Feistel substitution on left word (round 14)
|
||||
r ^= ((((S0[l >> 24] + S1[(l >> 16) & 0xff]) ^ S2[(l >> 8) & 0xff]) +
|
||||
S3[l & 0xff]) & 0xffffffff) ^ p15
|
||||
|
||||
# Feistel substitution on right word (round 15)
|
||||
l ^= ((((S0[r >> 24] + S1[(r >> 16) & 0xff]) ^ S2[(r >> 8) & 0xff]) +
|
||||
S3[r & 0xff]) & 0xffffffff) ^ p16
|
||||
|
||||
box[j], box[j+1] = l, r = r ^ p17, l
|
||||
j += 2
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
244
backend/venv/Lib/site-packages/passlib/crypto/_md4.py
Normal file
244
backend/venv/Lib/site-packages/passlib/crypto/_md4.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""
|
||||
passlib.crypto._md4 -- fallback implementation of MD4
|
||||
|
||||
Helper implementing insecure and obsolete md4 algorithm.
|
||||
used for NTHASH format, which is also insecure and broken,
|
||||
since it's just md4(password).
|
||||
|
||||
Implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html
|
||||
|
||||
.. note::
|
||||
|
||||
This shouldn't be imported directly, it's merely used conditionally
|
||||
by ``passlib.crypto.lookup_hash()`` when a native implementation can't be found.
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import struct
|
||||
# site
|
||||
from passlib.utils.compat import bascii_to_str, irange, PY3
|
||||
# local
|
||||
__all__ = ["md4"]
|
||||
|
||||
#=============================================================================
|
||||
# utils
|
||||
#=============================================================================
|
||||
def F(x,y,z):
|
||||
return (x&y) | ((~x) & z)
|
||||
|
||||
def G(x,y,z):
|
||||
return (x&y) | (x&z) | (y&z)
|
||||
|
||||
##def H(x,y,z):
|
||||
## return x ^ y ^ z
|
||||
|
||||
MASK_32 = 2**32-1
|
||||
|
||||
#=============================================================================
|
||||
# main class
|
||||
#=============================================================================
|
||||
class md4(object):
|
||||
"""pep-247 compatible implementation of MD4 hash algorithm
|
||||
|
||||
.. attribute:: digest_size
|
||||
|
||||
size of md4 digest in bytes (16 bytes)
|
||||
|
||||
.. method:: update
|
||||
|
||||
update digest by appending additional content
|
||||
|
||||
.. method:: copy
|
||||
|
||||
create clone of digest object, including current state
|
||||
|
||||
.. method:: digest
|
||||
|
||||
return bytes representing md4 digest of current content
|
||||
|
||||
.. method:: hexdigest
|
||||
|
||||
return hexadecimal version of digest
|
||||
"""
|
||||
# FIXME: make this follow hash object PEP better.
|
||||
# FIXME: this isn't threadsafe
|
||||
|
||||
name = "md4"
|
||||
digest_size = digestsize = 16
|
||||
block_size = 64
|
||||
|
||||
_count = 0 # number of 64-byte blocks processed so far (not including _buf)
|
||||
_state = None # list of [a,b,c,d] 32 bit ints used as internal register
|
||||
_buf = None # data processed in 64 byte blocks, this holds leftover from last update
|
||||
|
||||
def __init__(self, content=None):
|
||||
self._count = 0
|
||||
self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
|
||||
self._buf = b''
|
||||
if content:
|
||||
self.update(content)
|
||||
|
||||
# round 1 table - [abcd k s]
|
||||
_round1 = [
|
||||
[0,1,2,3, 0,3],
|
||||
[3,0,1,2, 1,7],
|
||||
[2,3,0,1, 2,11],
|
||||
[1,2,3,0, 3,19],
|
||||
|
||||
[0,1,2,3, 4,3],
|
||||
[3,0,1,2, 5,7],
|
||||
[2,3,0,1, 6,11],
|
||||
[1,2,3,0, 7,19],
|
||||
|
||||
[0,1,2,3, 8,3],
|
||||
[3,0,1,2, 9,7],
|
||||
[2,3,0,1, 10,11],
|
||||
[1,2,3,0, 11,19],
|
||||
|
||||
[0,1,2,3, 12,3],
|
||||
[3,0,1,2, 13,7],
|
||||
[2,3,0,1, 14,11],
|
||||
[1,2,3,0, 15,19],
|
||||
]
|
||||
|
||||
# round 2 table - [abcd k s]
|
||||
_round2 = [
|
||||
[0,1,2,3, 0,3],
|
||||
[3,0,1,2, 4,5],
|
||||
[2,3,0,1, 8,9],
|
||||
[1,2,3,0, 12,13],
|
||||
|
||||
[0,1,2,3, 1,3],
|
||||
[3,0,1,2, 5,5],
|
||||
[2,3,0,1, 9,9],
|
||||
[1,2,3,0, 13,13],
|
||||
|
||||
[0,1,2,3, 2,3],
|
||||
[3,0,1,2, 6,5],
|
||||
[2,3,0,1, 10,9],
|
||||
[1,2,3,0, 14,13],
|
||||
|
||||
[0,1,2,3, 3,3],
|
||||
[3,0,1,2, 7,5],
|
||||
[2,3,0,1, 11,9],
|
||||
[1,2,3,0, 15,13],
|
||||
]
|
||||
|
||||
# round 3 table - [abcd k s]
|
||||
_round3 = [
|
||||
[0,1,2,3, 0,3],
|
||||
[3,0,1,2, 8,9],
|
||||
[2,3,0,1, 4,11],
|
||||
[1,2,3,0, 12,15],
|
||||
|
||||
[0,1,2,3, 2,3],
|
||||
[3,0,1,2, 10,9],
|
||||
[2,3,0,1, 6,11],
|
||||
[1,2,3,0, 14,15],
|
||||
|
||||
[0,1,2,3, 1,3],
|
||||
[3,0,1,2, 9,9],
|
||||
[2,3,0,1, 5,11],
|
||||
[1,2,3,0, 13,15],
|
||||
|
||||
[0,1,2,3, 3,3],
|
||||
[3,0,1,2, 11,9],
|
||||
[2,3,0,1, 7,11],
|
||||
[1,2,3,0, 15,15],
|
||||
]
|
||||
|
||||
def _process(self, block):
|
||||
"""process 64 byte block"""
|
||||
# unpack block into 16 32-bit ints
|
||||
X = struct.unpack("<16I", block)
|
||||
|
||||
# clone state
|
||||
orig = self._state
|
||||
state = list(orig)
|
||||
|
||||
# round 1 - F function - (x&y)|(~x & z)
|
||||
for a,b,c,d,k,s in self._round1:
|
||||
t = (state[a] + F(state[b],state[c],state[d]) + X[k]) & MASK_32
|
||||
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
||||
|
||||
# round 2 - G function
|
||||
for a,b,c,d,k,s in self._round2:
|
||||
t = (state[a] + G(state[b],state[c],state[d]) + X[k] + 0x5a827999) & MASK_32
|
||||
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
||||
|
||||
# round 3 - H function - x ^ y ^ z
|
||||
for a,b,c,d,k,s in self._round3:
|
||||
t = (state[a] + (state[b] ^ state[c] ^ state[d]) + X[k] + 0x6ed9eba1) & MASK_32
|
||||
state[a] = ((t<<s) & MASK_32) + (t>>(32-s))
|
||||
|
||||
# add back into original state
|
||||
for i in irange(4):
|
||||
orig[i] = (orig[i]+state[i]) & MASK_32
|
||||
|
||||
def update(self, content):
|
||||
if not isinstance(content, bytes):
|
||||
if PY3:
|
||||
raise TypeError("expected bytes")
|
||||
else:
|
||||
# replicate behavior of hashlib under py2
|
||||
content = content.encode("ascii")
|
||||
buf = self._buf
|
||||
if buf:
|
||||
content = buf + content
|
||||
idx = 0
|
||||
end = len(content)
|
||||
while True:
|
||||
next = idx + 64
|
||||
if next <= end:
|
||||
self._process(content[idx:next])
|
||||
self._count += 1
|
||||
idx = next
|
||||
else:
|
||||
self._buf = content[idx:]
|
||||
return
|
||||
|
||||
def copy(self):
|
||||
other = md4()
|
||||
other._count = self._count
|
||||
other._state = list(self._state)
|
||||
other._buf = self._buf
|
||||
return other
|
||||
|
||||
def digest(self):
|
||||
# NOTE: backing up state so we can restore it after _process is called,
|
||||
# in case object is updated again (this is only attr altered by this method)
|
||||
orig = list(self._state)
|
||||
|
||||
# final block: buf + 0x80,
|
||||
# then 0x00 padding until congruent w/ 56 mod 64 bytes
|
||||
# then last 8 bytes = msg length in bits
|
||||
buf = self._buf
|
||||
msglen = self._count*512 + len(buf)*8
|
||||
block = buf + b'\x80' + b'\x00' * ((119-len(buf)) % 64) + \
|
||||
struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32)
|
||||
if len(block) == 128:
|
||||
self._process(block[:64])
|
||||
self._process(block[64:])
|
||||
else:
|
||||
assert len(block) == 64
|
||||
self._process(block)
|
||||
|
||||
# render digest & restore un-finalized state
|
||||
out = struct.pack("<4I", *self._state)
|
||||
self._state = orig
|
||||
return out
|
||||
|
||||
def hexdigest(self):
|
||||
return bascii_to_str(hexlify(self.digest()))
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
848
backend/venv/Lib/site-packages/passlib/crypto/des.py
Normal file
848
backend/venv/Lib/site-packages/passlib/crypto/des.py
Normal file
@@ -0,0 +1,848 @@
|
||||
"""passlib.crypto.des -- DES block encryption routines
|
||||
|
||||
History
|
||||
=======
|
||||
These routines (which have since been drastically modified for python)
|
||||
are based on a Java implementation of the des-crypt algorithm,
|
||||
found at `<http://www.dynamic.net.au/christos/crypt/UnixCrypt2.txt>`_.
|
||||
|
||||
The copyright & license for that source is as follows::
|
||||
|
||||
UnixCrypt.java 0.9 96/11/25
|
||||
Copyright (c) 1996 Aki Yoshida. All rights reserved.
|
||||
Permission to use, copy, modify and distribute this software
|
||||
for non-commercial or commercial purposes and without fee is
|
||||
hereby granted provided that this copyright notice appears in
|
||||
all copies.
|
||||
|
||||
---
|
||||
|
||||
Unix crypt(3C) utility
|
||||
@version 0.9, 11/25/96
|
||||
@author Aki Yoshida
|
||||
|
||||
---
|
||||
|
||||
modified April 2001
|
||||
by Iris Van den Broeke, Daniel Deville
|
||||
|
||||
---
|
||||
Unix Crypt.
|
||||
Implements the one way cryptography used by Unix systems for
|
||||
simple password protection.
|
||||
@version $Id: UnixCrypt2.txt,v 1.1.1.1 2005/09/13 22:20:13 christos Exp $
|
||||
@author Greg Wilkins (gregw)
|
||||
|
||||
The netbsd des-crypt implementation has some nice notes on how this all works -
|
||||
http://fxr.googlebit.com/source/lib/libcrypt/crypt.c?v=NETBSD-CURRENT
|
||||
"""
|
||||
|
||||
# TODO: could use an accelerated C version of this module to speed up lmhash,
|
||||
# des-crypt, and ext-des-crypt
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import struct
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils.compat import join_byte_values, byte_elem_value, \
|
||||
irange, irange, int_types
|
||||
# local
|
||||
__all__ = [
|
||||
"expand_des_key",
|
||||
"des_encrypt_block",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# constants
|
||||
#=============================================================================
|
||||
|
||||
# masks/upper limits for various integer sizes
|
||||
INT_24_MASK = 0xffffff
|
||||
INT_56_MASK = 0xffffffffffffff
|
||||
INT_64_MASK = 0xffffffffffffffff
|
||||
|
||||
# mask to clear parity bits from 64-bit key
|
||||
_KDATA_MASK = 0xfefefefefefefefe
|
||||
_KPARITY_MASK = 0x0101010101010101
|
||||
|
||||
# mask used to setup key schedule
|
||||
_KS_MASK = 0xfcfcfcfcffffffff
|
||||
|
||||
#=============================================================================
|
||||
# static DES tables
|
||||
#=============================================================================
|
||||
|
||||
# placeholders filled in by _load_tables()
|
||||
PCXROT = IE3264 = SPE = CF6464 = None
|
||||
|
||||
def _load_tables():
|
||||
"""delay loading tables until they are actually needed"""
|
||||
global PCXROT, IE3264, SPE, CF6464
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Initial key schedule permutation
|
||||
# PC1ROT - bit reverse, then PC1, then Rotate, then PC2
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: this was reordered from original table to make perm3264 logic simpler
|
||||
PC1ROT=(
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000002000, 0x0000000000002000,
|
||||
0x0000000000000020, 0x0000000000000020, 0x0000000000002020, 0x0000000000002020,
|
||||
0x0000000000000400, 0x0000000000000400, 0x0000000000002400, 0x0000000000002400,
|
||||
0x0000000000000420, 0x0000000000000420, 0x0000000000002420, 0x0000000000002420, ),
|
||||
( 0x0000000000000000, 0x2000000000000000, 0x0000000400000000, 0x2000000400000000,
|
||||
0x0000800000000000, 0x2000800000000000, 0x0000800400000000, 0x2000800400000000,
|
||||
0x0008000000000000, 0x2008000000000000, 0x0008000400000000, 0x2008000400000000,
|
||||
0x0008800000000000, 0x2008800000000000, 0x0008800400000000, 0x2008800400000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000040, 0x0000000000000040,
|
||||
0x0000000020000000, 0x0000000020000000, 0x0000000020000040, 0x0000000020000040,
|
||||
0x0000000000200000, 0x0000000000200000, 0x0000000000200040, 0x0000000000200040,
|
||||
0x0000000020200000, 0x0000000020200000, 0x0000000020200040, 0x0000000020200040, ),
|
||||
( 0x0000000000000000, 0x0002000000000000, 0x0800000000000000, 0x0802000000000000,
|
||||
0x0100000000000000, 0x0102000000000000, 0x0900000000000000, 0x0902000000000000,
|
||||
0x4000000000000000, 0x4002000000000000, 0x4800000000000000, 0x4802000000000000,
|
||||
0x4100000000000000, 0x4102000000000000, 0x4900000000000000, 0x4902000000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000040000, 0x0000000000040000,
|
||||
0x0000020000000000, 0x0000020000000000, 0x0000020000040000, 0x0000020000040000,
|
||||
0x0000000000000004, 0x0000000000000004, 0x0000000000040004, 0x0000000000040004,
|
||||
0x0000020000000004, 0x0000020000000004, 0x0000020000040004, 0x0000020000040004, ),
|
||||
( 0x0000000000000000, 0x0000400000000000, 0x0200000000000000, 0x0200400000000000,
|
||||
0x0080000000000000, 0x0080400000000000, 0x0280000000000000, 0x0280400000000000,
|
||||
0x0000008000000000, 0x0000408000000000, 0x0200008000000000, 0x0200408000000000,
|
||||
0x0080008000000000, 0x0080408000000000, 0x0280008000000000, 0x0280408000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000010000000, 0x0000000010000000,
|
||||
0x0000000000001000, 0x0000000000001000, 0x0000000010001000, 0x0000000010001000,
|
||||
0x0000000040000000, 0x0000000040000000, 0x0000000050000000, 0x0000000050000000,
|
||||
0x0000000040001000, 0x0000000040001000, 0x0000000050001000, 0x0000000050001000, ),
|
||||
( 0x0000000000000000, 0x0000001000000000, 0x0000080000000000, 0x0000081000000000,
|
||||
0x1000000000000000, 0x1000001000000000, 0x1000080000000000, 0x1000081000000000,
|
||||
0x0004000000000000, 0x0004001000000000, 0x0004080000000000, 0x0004081000000000,
|
||||
0x1004000000000000, 0x1004001000000000, 0x1004080000000000, 0x1004081000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000080, 0x0000000000000080,
|
||||
0x0000000000080000, 0x0000000000080000, 0x0000000000080080, 0x0000000000080080,
|
||||
0x0000000000800000, 0x0000000000800000, 0x0000000000800080, 0x0000000000800080,
|
||||
0x0000000000880000, 0x0000000000880000, 0x0000000000880080, 0x0000000000880080, ),
|
||||
( 0x0000000000000000, 0x0000000008000000, 0x0000002000000000, 0x0000002008000000,
|
||||
0x0000100000000000, 0x0000100008000000, 0x0000102000000000, 0x0000102008000000,
|
||||
0x0000200000000000, 0x0000200008000000, 0x0000202000000000, 0x0000202008000000,
|
||||
0x0000300000000000, 0x0000300008000000, 0x0000302000000000, 0x0000302008000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000400000, 0x0000000000400000,
|
||||
0x0000000004000000, 0x0000000004000000, 0x0000000004400000, 0x0000000004400000,
|
||||
0x0000000000000800, 0x0000000000000800, 0x0000000000400800, 0x0000000000400800,
|
||||
0x0000000004000800, 0x0000000004000800, 0x0000000004400800, 0x0000000004400800, ),
|
||||
( 0x0000000000000000, 0x0000000000008000, 0x0040000000000000, 0x0040000000008000,
|
||||
0x0000004000000000, 0x0000004000008000, 0x0040004000000000, 0x0040004000008000,
|
||||
0x8000000000000000, 0x8000000000008000, 0x8040000000000000, 0x8040000000008000,
|
||||
0x8000004000000000, 0x8000004000008000, 0x8040004000000000, 0x8040004000008000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000004000, 0x0000000000004000,
|
||||
0x0000000000000008, 0x0000000000000008, 0x0000000000004008, 0x0000000000004008,
|
||||
0x0000000000000010, 0x0000000000000010, 0x0000000000004010, 0x0000000000004010,
|
||||
0x0000000000000018, 0x0000000000000018, 0x0000000000004018, 0x0000000000004018, ),
|
||||
( 0x0000000000000000, 0x0000000200000000, 0x0001000000000000, 0x0001000200000000,
|
||||
0x0400000000000000, 0x0400000200000000, 0x0401000000000000, 0x0401000200000000,
|
||||
0x0020000000000000, 0x0020000200000000, 0x0021000000000000, 0x0021000200000000,
|
||||
0x0420000000000000, 0x0420000200000000, 0x0421000000000000, 0x0421000200000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000010000000000, 0x0000010000000000,
|
||||
0x0000000100000000, 0x0000000100000000, 0x0000010100000000, 0x0000010100000000,
|
||||
0x0000000000100000, 0x0000000000100000, 0x0000010000100000, 0x0000010000100000,
|
||||
0x0000000100100000, 0x0000000100100000, 0x0000010100100000, 0x0000010100100000, ),
|
||||
( 0x0000000000000000, 0x0000000080000000, 0x0000040000000000, 0x0000040080000000,
|
||||
0x0010000000000000, 0x0010000080000000, 0x0010040000000000, 0x0010040080000000,
|
||||
0x0000000800000000, 0x0000000880000000, 0x0000040800000000, 0x0000040880000000,
|
||||
0x0010000800000000, 0x0010000880000000, 0x0010040800000000, 0x0010040880000000, ),
|
||||
)
|
||||
#---------------------------------------------------------------
|
||||
# Subsequent key schedule rotation permutations
|
||||
# PC2ROT - PC2 inverse, then Rotate, then PC2
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: this was reordered from original table to make perm3264 logic simpler
|
||||
PC2ROTA=(
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000200000, 0x0000000000200000, 0x0000000000200000, 0x0000000000200000,
|
||||
0x0000000004000000, 0x0000000004000000, 0x0000000004000000, 0x0000000004000000,
|
||||
0x0000000004200000, 0x0000000004200000, 0x0000000004200000, 0x0000000004200000, ),
|
||||
( 0x0000000000000000, 0x0000000000000800, 0x0000010000000000, 0x0000010000000800,
|
||||
0x0000000000002000, 0x0000000000002800, 0x0000010000002000, 0x0000010000002800,
|
||||
0x0000000010000000, 0x0000000010000800, 0x0000010010000000, 0x0000010010000800,
|
||||
0x0000000010002000, 0x0000000010002800, 0x0000010010002000, 0x0000010010002800, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000100000000, 0x0000000100000000, 0x0000000100000000, 0x0000000100000000,
|
||||
0x0000000000800000, 0x0000000000800000, 0x0000000000800000, 0x0000000000800000,
|
||||
0x0000000100800000, 0x0000000100800000, 0x0000000100800000, 0x0000000100800000, ),
|
||||
( 0x0000000000000000, 0x0000020000000000, 0x0000000080000000, 0x0000020080000000,
|
||||
0x0000000000400000, 0x0000020000400000, 0x0000000080400000, 0x0000020080400000,
|
||||
0x0000000008000000, 0x0000020008000000, 0x0000000088000000, 0x0000020088000000,
|
||||
0x0000000008400000, 0x0000020008400000, 0x0000000088400000, 0x0000020088400000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000000040, 0x0000000000000040, 0x0000000000000040, 0x0000000000000040,
|
||||
0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000,
|
||||
0x0000000000001040, 0x0000000000001040, 0x0000000000001040, 0x0000000000001040, ),
|
||||
( 0x0000000000000000, 0x0000000000000010, 0x0000000000000400, 0x0000000000000410,
|
||||
0x0000000000000080, 0x0000000000000090, 0x0000000000000480, 0x0000000000000490,
|
||||
0x0000000040000000, 0x0000000040000010, 0x0000000040000400, 0x0000000040000410,
|
||||
0x0000000040000080, 0x0000000040000090, 0x0000000040000480, 0x0000000040000490, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000,
|
||||
0x0000000000100000, 0x0000000000100000, 0x0000000000100000, 0x0000000000100000,
|
||||
0x0000000000180000, 0x0000000000180000, 0x0000000000180000, 0x0000000000180000, ),
|
||||
( 0x0000000000000000, 0x0000000000040000, 0x0000000000000020, 0x0000000000040020,
|
||||
0x0000000000000004, 0x0000000000040004, 0x0000000000000024, 0x0000000000040024,
|
||||
0x0000000200000000, 0x0000000200040000, 0x0000000200000020, 0x0000000200040020,
|
||||
0x0000000200000004, 0x0000000200040004, 0x0000000200000024, 0x0000000200040024, ),
|
||||
( 0x0000000000000000, 0x0000000000000008, 0x0000000000008000, 0x0000000000008008,
|
||||
0x0010000000000000, 0x0010000000000008, 0x0010000000008000, 0x0010000000008008,
|
||||
0x0020000000000000, 0x0020000000000008, 0x0020000000008000, 0x0020000000008008,
|
||||
0x0030000000000000, 0x0030000000000008, 0x0030000000008000, 0x0030000000008008, ),
|
||||
( 0x0000000000000000, 0x0000400000000000, 0x0000080000000000, 0x0000480000000000,
|
||||
0x0000100000000000, 0x0000500000000000, 0x0000180000000000, 0x0000580000000000,
|
||||
0x4000000000000000, 0x4000400000000000, 0x4000080000000000, 0x4000480000000000,
|
||||
0x4000100000000000, 0x4000500000000000, 0x4000180000000000, 0x4000580000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000004000, 0x0000000020000000, 0x0000000020004000,
|
||||
0x0001000000000000, 0x0001000000004000, 0x0001000020000000, 0x0001000020004000,
|
||||
0x0200000000000000, 0x0200000000004000, 0x0200000020000000, 0x0200000020004000,
|
||||
0x0201000000000000, 0x0201000000004000, 0x0201000020000000, 0x0201000020004000, ),
|
||||
( 0x0000000000000000, 0x1000000000000000, 0x0004000000000000, 0x1004000000000000,
|
||||
0x0002000000000000, 0x1002000000000000, 0x0006000000000000, 0x1006000000000000,
|
||||
0x0000000800000000, 0x1000000800000000, 0x0004000800000000, 0x1004000800000000,
|
||||
0x0002000800000000, 0x1002000800000000, 0x0006000800000000, 0x1006000800000000, ),
|
||||
( 0x0000000000000000, 0x0040000000000000, 0x2000000000000000, 0x2040000000000000,
|
||||
0x0000008000000000, 0x0040008000000000, 0x2000008000000000, 0x2040008000000000,
|
||||
0x0000001000000000, 0x0040001000000000, 0x2000001000000000, 0x2040001000000000,
|
||||
0x0000009000000000, 0x0040009000000000, 0x2000009000000000, 0x2040009000000000, ),
|
||||
( 0x0000000000000000, 0x0400000000000000, 0x8000000000000000, 0x8400000000000000,
|
||||
0x0000002000000000, 0x0400002000000000, 0x8000002000000000, 0x8400002000000000,
|
||||
0x0100000000000000, 0x0500000000000000, 0x8100000000000000, 0x8500000000000000,
|
||||
0x0100002000000000, 0x0500002000000000, 0x8100002000000000, 0x8500002000000000, ),
|
||||
( 0x0000000000000000, 0x0000800000000000, 0x0800000000000000, 0x0800800000000000,
|
||||
0x0000004000000000, 0x0000804000000000, 0x0800004000000000, 0x0800804000000000,
|
||||
0x0000000400000000, 0x0000800400000000, 0x0800000400000000, 0x0800800400000000,
|
||||
0x0000004400000000, 0x0000804400000000, 0x0800004400000000, 0x0800804400000000, ),
|
||||
( 0x0000000000000000, 0x0080000000000000, 0x0000040000000000, 0x0080040000000000,
|
||||
0x0008000000000000, 0x0088000000000000, 0x0008040000000000, 0x0088040000000000,
|
||||
0x0000200000000000, 0x0080200000000000, 0x0000240000000000, 0x0080240000000000,
|
||||
0x0008200000000000, 0x0088200000000000, 0x0008240000000000, 0x0088240000000000, ),
|
||||
)
|
||||
|
||||
# NOTE: this was reordered from original table to make perm3264 logic simpler
|
||||
PC2ROTB=(
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000000400, 0x0000000000000400, 0x0000000000000400, 0x0000000000000400,
|
||||
0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000,
|
||||
0x0000000000080400, 0x0000000000080400, 0x0000000000080400, 0x0000000000080400, ),
|
||||
( 0x0000000000000000, 0x0000000000800000, 0x0000000000004000, 0x0000000000804000,
|
||||
0x0000000080000000, 0x0000000080800000, 0x0000000080004000, 0x0000000080804000,
|
||||
0x0000000000040000, 0x0000000000840000, 0x0000000000044000, 0x0000000000844000,
|
||||
0x0000000080040000, 0x0000000080840000, 0x0000000080044000, 0x0000000080844000, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000000008, 0x0000000000000008, 0x0000000000000008, 0x0000000000000008,
|
||||
0x0000000040000000, 0x0000000040000000, 0x0000000040000000, 0x0000000040000000,
|
||||
0x0000000040000008, 0x0000000040000008, 0x0000000040000008, 0x0000000040000008, ),
|
||||
( 0x0000000000000000, 0x0000000020000000, 0x0000000200000000, 0x0000000220000000,
|
||||
0x0000000000000080, 0x0000000020000080, 0x0000000200000080, 0x0000000220000080,
|
||||
0x0000000000100000, 0x0000000020100000, 0x0000000200100000, 0x0000000220100000,
|
||||
0x0000000000100080, 0x0000000020100080, 0x0000000200100080, 0x0000000220100080, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000002000, 0x0000000000002000, 0x0000000000002000, 0x0000000000002000,
|
||||
0x0000020000000000, 0x0000020000000000, 0x0000020000000000, 0x0000020000000000,
|
||||
0x0000020000002000, 0x0000020000002000, 0x0000020000002000, 0x0000020000002000, ),
|
||||
( 0x0000000000000000, 0x0000000000000800, 0x0000000100000000, 0x0000000100000800,
|
||||
0x0000000010000000, 0x0000000010000800, 0x0000000110000000, 0x0000000110000800,
|
||||
0x0000000000000004, 0x0000000000000804, 0x0000000100000004, 0x0000000100000804,
|
||||
0x0000000010000004, 0x0000000010000804, 0x0000000110000004, 0x0000000110000804, ),
|
||||
( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
|
||||
0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000,
|
||||
0x0000000000000010, 0x0000000000000010, 0x0000000000000010, 0x0000000000000010,
|
||||
0x0000000000001010, 0x0000000000001010, 0x0000000000001010, 0x0000000000001010, ),
|
||||
( 0x0000000000000000, 0x0000000000000040, 0x0000010000000000, 0x0000010000000040,
|
||||
0x0000000000200000, 0x0000000000200040, 0x0000010000200000, 0x0000010000200040,
|
||||
0x0000000000008000, 0x0000000000008040, 0x0000010000008000, 0x0000010000008040,
|
||||
0x0000000000208000, 0x0000000000208040, 0x0000010000208000, 0x0000010000208040, ),
|
||||
( 0x0000000000000000, 0x0000000004000000, 0x0000000008000000, 0x000000000c000000,
|
||||
0x0400000000000000, 0x0400000004000000, 0x0400000008000000, 0x040000000c000000,
|
||||
0x8000000000000000, 0x8000000004000000, 0x8000000008000000, 0x800000000c000000,
|
||||
0x8400000000000000, 0x8400000004000000, 0x8400000008000000, 0x840000000c000000, ),
|
||||
( 0x0000000000000000, 0x0002000000000000, 0x0200000000000000, 0x0202000000000000,
|
||||
0x1000000000000000, 0x1002000000000000, 0x1200000000000000, 0x1202000000000000,
|
||||
0x0008000000000000, 0x000a000000000000, 0x0208000000000000, 0x020a000000000000,
|
||||
0x1008000000000000, 0x100a000000000000, 0x1208000000000000, 0x120a000000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000400000, 0x0000000000000020, 0x0000000000400020,
|
||||
0x0040000000000000, 0x0040000000400000, 0x0040000000000020, 0x0040000000400020,
|
||||
0x0800000000000000, 0x0800000000400000, 0x0800000000000020, 0x0800000000400020,
|
||||
0x0840000000000000, 0x0840000000400000, 0x0840000000000020, 0x0840000000400020, ),
|
||||
( 0x0000000000000000, 0x0080000000000000, 0x0000008000000000, 0x0080008000000000,
|
||||
0x2000000000000000, 0x2080000000000000, 0x2000008000000000, 0x2080008000000000,
|
||||
0x0020000000000000, 0x00a0000000000000, 0x0020008000000000, 0x00a0008000000000,
|
||||
0x2020000000000000, 0x20a0000000000000, 0x2020008000000000, 0x20a0008000000000, ),
|
||||
( 0x0000000000000000, 0x0000002000000000, 0x0000040000000000, 0x0000042000000000,
|
||||
0x4000000000000000, 0x4000002000000000, 0x4000040000000000, 0x4000042000000000,
|
||||
0x0000400000000000, 0x0000402000000000, 0x0000440000000000, 0x0000442000000000,
|
||||
0x4000400000000000, 0x4000402000000000, 0x4000440000000000, 0x4000442000000000, ),
|
||||
( 0x0000000000000000, 0x0000004000000000, 0x0000200000000000, 0x0000204000000000,
|
||||
0x0000080000000000, 0x0000084000000000, 0x0000280000000000, 0x0000284000000000,
|
||||
0x0000800000000000, 0x0000804000000000, 0x0000a00000000000, 0x0000a04000000000,
|
||||
0x0000880000000000, 0x0000884000000000, 0x0000a80000000000, 0x0000a84000000000, ),
|
||||
( 0x0000000000000000, 0x0000000800000000, 0x0000000400000000, 0x0000000c00000000,
|
||||
0x0000100000000000, 0x0000100800000000, 0x0000100400000000, 0x0000100c00000000,
|
||||
0x0010000000000000, 0x0010000800000000, 0x0010000400000000, 0x0010000c00000000,
|
||||
0x0010100000000000, 0x0010100800000000, 0x0010100400000000, 0x0010100c00000000, ),
|
||||
( 0x0000000000000000, 0x0100000000000000, 0x0001000000000000, 0x0101000000000000,
|
||||
0x0000001000000000, 0x0100001000000000, 0x0001001000000000, 0x0101001000000000,
|
||||
0x0004000000000000, 0x0104000000000000, 0x0005000000000000, 0x0105000000000000,
|
||||
0x0004001000000000, 0x0104001000000000, 0x0005001000000000, 0x0105001000000000, ),
|
||||
)
|
||||
#---------------------------------------------------------------
|
||||
# PCXROT - PC1ROT, PC2ROTA, PC2ROTB listed in order
|
||||
# of the PC1 rotation schedule, as used by des_setkey
|
||||
#---------------------------------------------------------------
|
||||
##ROTATES = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
|
||||
##PCXROT = (
|
||||
## PC1ROT, PC2ROTA, PC2ROTB, PC2ROTB,
|
||||
## PC2ROTB, PC2ROTB, PC2ROTB, PC2ROTB,
|
||||
## PC2ROTA, PC2ROTB, PC2ROTB, PC2ROTB,
|
||||
## PC2ROTB, PC2ROTB, PC2ROTB, PC2ROTA,
|
||||
## )
|
||||
|
||||
# NOTE: modified PCXROT to contain entrys broken into pairs,
|
||||
# to help generate them in format best used by encoder.
|
||||
PCXROT = (
|
||||
(PC1ROT, PC2ROTA), (PC2ROTB, PC2ROTB),
|
||||
(PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTB),
|
||||
(PC2ROTA, PC2ROTB), (PC2ROTB, PC2ROTB),
|
||||
(PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTA),
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Bit reverse, intial permupation, expantion
|
||||
# Initial permutation/expansion table
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: this was reordered from original table to make perm3264 logic simpler
|
||||
IE3264=(
|
||||
( 0x0000000000000000, 0x0000000000800800, 0x0000000000008008, 0x0000000000808808,
|
||||
0x0000008008000000, 0x0000008008800800, 0x0000008008008008, 0x0000008008808808,
|
||||
0x0000000080080000, 0x0000000080880800, 0x0000000080088008, 0x0000000080888808,
|
||||
0x0000008088080000, 0x0000008088880800, 0x0000008088088008, 0x0000008088888808, ),
|
||||
( 0x0000000000000000, 0x0080080000000000, 0x0000800800000000, 0x0080880800000000,
|
||||
0x0800000000000080, 0x0880080000000080, 0x0800800800000080, 0x0880880800000080,
|
||||
0x8008000000000000, 0x8088080000000000, 0x8008800800000000, 0x8088880800000000,
|
||||
0x8808000000000080, 0x8888080000000080, 0x8808800800000080, 0x8888880800000080, ),
|
||||
( 0x0000000000000000, 0x0000000000001000, 0x0000000000000010, 0x0000000000001010,
|
||||
0x0000000010000000, 0x0000000010001000, 0x0000000010000010, 0x0000000010001010,
|
||||
0x0000000000100000, 0x0000000000101000, 0x0000000000100010, 0x0000000000101010,
|
||||
0x0000000010100000, 0x0000000010101000, 0x0000000010100010, 0x0000000010101010, ),
|
||||
( 0x0000000000000000, 0x0000100000000000, 0x0000001000000000, 0x0000101000000000,
|
||||
0x1000000000000000, 0x1000100000000000, 0x1000001000000000, 0x1000101000000000,
|
||||
0x0010000000000000, 0x0010100000000000, 0x0010001000000000, 0x0010101000000000,
|
||||
0x1010000000000000, 0x1010100000000000, 0x1010001000000000, 0x1010101000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000002000, 0x0000000000000020, 0x0000000000002020,
|
||||
0x0000000020000000, 0x0000000020002000, 0x0000000020000020, 0x0000000020002020,
|
||||
0x0000000000200000, 0x0000000000202000, 0x0000000000200020, 0x0000000000202020,
|
||||
0x0000000020200000, 0x0000000020202000, 0x0000000020200020, 0x0000000020202020, ),
|
||||
( 0x0000000000000000, 0x0000200000000000, 0x0000002000000000, 0x0000202000000000,
|
||||
0x2000000000000000, 0x2000200000000000, 0x2000002000000000, 0x2000202000000000,
|
||||
0x0020000000000000, 0x0020200000000000, 0x0020002000000000, 0x0020202000000000,
|
||||
0x2020000000000000, 0x2020200000000000, 0x2020002000000000, 0x2020202000000000, ),
|
||||
( 0x0000000000000000, 0x0000000000004004, 0x0400000000000040, 0x0400000000004044,
|
||||
0x0000000040040000, 0x0000000040044004, 0x0400000040040040, 0x0400000040044044,
|
||||
0x0000000000400400, 0x0000000000404404, 0x0400000000400440, 0x0400000000404444,
|
||||
0x0000000040440400, 0x0000000040444404, 0x0400000040440440, 0x0400000040444444, ),
|
||||
( 0x0000000000000000, 0x0000400400000000, 0x0000004004000000, 0x0000404404000000,
|
||||
0x4004000000000000, 0x4004400400000000, 0x4004004004000000, 0x4004404404000000,
|
||||
0x0040040000000000, 0x0040440400000000, 0x0040044004000000, 0x0040444404000000,
|
||||
0x4044040000000000, 0x4044440400000000, 0x4044044004000000, 0x4044444404000000, ),
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Table that combines the S, P, and E operations.
|
||||
#---------------------------------------------------------------
|
||||
SPE=(
|
||||
( 0x0080088008200000, 0x0000008008000000, 0x0000000000200020, 0x0080088008200020,
|
||||
0x0000000000200000, 0x0080088008000020, 0x0000008008000020, 0x0000000000200020,
|
||||
0x0080088008000020, 0x0080088008200000, 0x0000008008200000, 0x0080080000000020,
|
||||
0x0080080000200020, 0x0000000000200000, 0x0000000000000000, 0x0000008008000020,
|
||||
0x0000008008000000, 0x0000000000000020, 0x0080080000200000, 0x0080088008000000,
|
||||
0x0080088008200020, 0x0000008008200000, 0x0080080000000020, 0x0080080000200000,
|
||||
0x0000000000000020, 0x0080080000000000, 0x0080088008000000, 0x0000008008200020,
|
||||
0x0080080000000000, 0x0080080000200020, 0x0000008008200020, 0x0000000000000000,
|
||||
0x0000000000000000, 0x0080088008200020, 0x0080080000200000, 0x0000008008000020,
|
||||
0x0080088008200000, 0x0000008008000000, 0x0080080000000020, 0x0080080000200000,
|
||||
0x0000008008200020, 0x0080080000000000, 0x0080088008000000, 0x0000000000200020,
|
||||
0x0080088008000020, 0x0000000000000020, 0x0000000000200020, 0x0000008008200000,
|
||||
0x0080088008200020, 0x0080088008000000, 0x0000008008200000, 0x0080080000200020,
|
||||
0x0000000000200000, 0x0080080000000020, 0x0000008008000020, 0x0000000000000000,
|
||||
0x0000008008000000, 0x0000000000200000, 0x0080080000200020, 0x0080088008200000,
|
||||
0x0000000000000020, 0x0000008008200020, 0x0080080000000000, 0x0080088008000020, ),
|
||||
( 0x1000800810004004, 0x0000000000000000, 0x0000800810000000, 0x0000000010004004,
|
||||
0x1000000000004004, 0x1000800800000000, 0x0000800800004004, 0x0000800810000000,
|
||||
0x0000800800000000, 0x1000000010004004, 0x1000000000000000, 0x0000800800004004,
|
||||
0x1000000010000000, 0x0000800810004004, 0x0000000010004004, 0x1000000000000000,
|
||||
0x0000000010000000, 0x1000800800004004, 0x1000000010004004, 0x0000800800000000,
|
||||
0x1000800810000000, 0x0000000000004004, 0x0000000000000000, 0x1000000010000000,
|
||||
0x1000800800004004, 0x1000800810000000, 0x0000800810004004, 0x1000000000004004,
|
||||
0x0000000000004004, 0x0000000010000000, 0x1000800800000000, 0x1000800810004004,
|
||||
0x1000000010000000, 0x0000800810004004, 0x0000800800004004, 0x1000800810000000,
|
||||
0x1000800810004004, 0x1000000010000000, 0x1000000000004004, 0x0000000000000000,
|
||||
0x0000000000004004, 0x1000800800000000, 0x0000000010000000, 0x1000000010004004,
|
||||
0x0000800800000000, 0x0000000000004004, 0x1000800810000000, 0x1000800800004004,
|
||||
0x0000800810004004, 0x0000800800000000, 0x0000000000000000, 0x1000000000004004,
|
||||
0x1000000000000000, 0x1000800810004004, 0x0000800810000000, 0x0000000010004004,
|
||||
0x1000000010004004, 0x0000000010000000, 0x1000800800000000, 0x0000800800004004,
|
||||
0x1000800800004004, 0x1000000000000000, 0x0000000010004004, 0x0000800810000000, ),
|
||||
( 0x0000000000400410, 0x0010004004400400, 0x0010000000000000, 0x0010000000400410,
|
||||
0x0000004004000010, 0x0000000000400400, 0x0010000000400410, 0x0010004004000000,
|
||||
0x0010000000400400, 0x0000004004000000, 0x0000004004400400, 0x0000000000000010,
|
||||
0x0010004004400410, 0x0010000000000010, 0x0000000000000010, 0x0000004004400410,
|
||||
0x0000000000000000, 0x0000004004000010, 0x0010004004400400, 0x0010000000000000,
|
||||
0x0010000000000010, 0x0010004004400410, 0x0000004004000000, 0x0000000000400410,
|
||||
0x0000004004400410, 0x0010000000400400, 0x0010004004000010, 0x0000004004400400,
|
||||
0x0010004004000000, 0x0000000000000000, 0x0000000000400400, 0x0010004004000010,
|
||||
0x0010004004400400, 0x0010000000000000, 0x0000000000000010, 0x0000004004000000,
|
||||
0x0010000000000010, 0x0000004004000010, 0x0000004004400400, 0x0010000000400410,
|
||||
0x0000000000000000, 0x0010004004400400, 0x0010004004000000, 0x0000004004400410,
|
||||
0x0000004004000010, 0x0000000000400400, 0x0010004004400410, 0x0000000000000010,
|
||||
0x0010004004000010, 0x0000000000400410, 0x0000000000400400, 0x0010004004400410,
|
||||
0x0000004004000000, 0x0010000000400400, 0x0010000000400410, 0x0010004004000000,
|
||||
0x0010000000400400, 0x0000000000000000, 0x0000004004400410, 0x0010000000000010,
|
||||
0x0000000000400410, 0x0010004004000010, 0x0010000000000000, 0x0000004004400400, ),
|
||||
( 0x0800100040040080, 0x0000100000001000, 0x0800000000000080, 0x0800100040041080,
|
||||
0x0000000000000000, 0x0000000040041000, 0x0800100000001080, 0x0800000040040080,
|
||||
0x0000100040041000, 0x0800000000001080, 0x0000000000001000, 0x0800100000000080,
|
||||
0x0800000000001080, 0x0800100040040080, 0x0000000040040000, 0x0000000000001000,
|
||||
0x0800000040041080, 0x0000100040040000, 0x0000100000000000, 0x0800000000000080,
|
||||
0x0000100040040000, 0x0800100000001080, 0x0000000040041000, 0x0000100000000000,
|
||||
0x0800100000000080, 0x0000000000000000, 0x0800000040040080, 0x0000100040041000,
|
||||
0x0000100000001000, 0x0800000040041080, 0x0800100040041080, 0x0000000040040000,
|
||||
0x0800000040041080, 0x0800100000000080, 0x0000000040040000, 0x0800000000001080,
|
||||
0x0000100040040000, 0x0000100000001000, 0x0800000000000080, 0x0000000040041000,
|
||||
0x0800100000001080, 0x0000000000000000, 0x0000100000000000, 0x0800000040040080,
|
||||
0x0000000000000000, 0x0800000040041080, 0x0000100040041000, 0x0000100000000000,
|
||||
0x0000000000001000, 0x0800100040041080, 0x0800100040040080, 0x0000000040040000,
|
||||
0x0800100040041080, 0x0800000000000080, 0x0000100000001000, 0x0800100040040080,
|
||||
0x0800000040040080, 0x0000100040040000, 0x0000000040041000, 0x0800100000001080,
|
||||
0x0800100000000080, 0x0000000000001000, 0x0800000000001080, 0x0000100040041000, ),
|
||||
( 0x0000000000800800, 0x0000001000000000, 0x0040040000000000, 0x2040041000800800,
|
||||
0x2000001000800800, 0x0040040000800800, 0x2040041000000000, 0x0000001000800800,
|
||||
0x0000001000000000, 0x2000000000000000, 0x2000000000800800, 0x0040041000000000,
|
||||
0x2040040000800800, 0x2000001000800800, 0x0040041000800800, 0x0000000000000000,
|
||||
0x0040041000000000, 0x0000000000800800, 0x2000001000000000, 0x2040040000000000,
|
||||
0x0040040000800800, 0x2040041000000000, 0x0000000000000000, 0x2000000000800800,
|
||||
0x2000000000000000, 0x2040040000800800, 0x2040041000800800, 0x2000001000000000,
|
||||
0x0000001000800800, 0x0040040000000000, 0x2040040000000000, 0x0040041000800800,
|
||||
0x0040041000800800, 0x2040040000800800, 0x2000001000000000, 0x0000001000800800,
|
||||
0x0000001000000000, 0x2000000000000000, 0x2000000000800800, 0x0040040000800800,
|
||||
0x0000000000800800, 0x0040041000000000, 0x2040041000800800, 0x0000000000000000,
|
||||
0x2040041000000000, 0x0000000000800800, 0x0040040000000000, 0x2000001000000000,
|
||||
0x2040040000800800, 0x0040040000000000, 0x0000000000000000, 0x2040041000800800,
|
||||
0x2000001000800800, 0x0040041000800800, 0x2040040000000000, 0x0000001000000000,
|
||||
0x0040041000000000, 0x2000001000800800, 0x0040040000800800, 0x2040040000000000,
|
||||
0x2000000000000000, 0x2040041000000000, 0x0000001000800800, 0x2000000000800800, ),
|
||||
( 0x4004000000008008, 0x4004000020000000, 0x0000000000000000, 0x0000200020008008,
|
||||
0x4004000020000000, 0x0000200000000000, 0x4004200000008008, 0x0000000020000000,
|
||||
0x4004200000000000, 0x4004200020008008, 0x0000200020000000, 0x0000000000008008,
|
||||
0x0000200000008008, 0x4004000000008008, 0x0000000020008008, 0x4004200020000000,
|
||||
0x0000000020000000, 0x4004200000008008, 0x4004000020008008, 0x0000000000000000,
|
||||
0x0000200000000000, 0x4004000000000000, 0x0000200020008008, 0x4004000020008008,
|
||||
0x4004200020008008, 0x0000000020008008, 0x0000000000008008, 0x4004200000000000,
|
||||
0x4004000000000000, 0x0000200020000000, 0x4004200020000000, 0x0000200000008008,
|
||||
0x4004200000000000, 0x0000000000008008, 0x0000200000008008, 0x4004200020000000,
|
||||
0x0000200020008008, 0x4004000020000000, 0x0000000000000000, 0x0000200000008008,
|
||||
0x0000000000008008, 0x0000200000000000, 0x4004000020008008, 0x0000000020000000,
|
||||
0x4004000020000000, 0x4004200020008008, 0x0000200020000000, 0x4004000000000000,
|
||||
0x4004200020008008, 0x0000200020000000, 0x0000000020000000, 0x4004200000008008,
|
||||
0x4004000000008008, 0x0000000020008008, 0x4004200020000000, 0x0000000000000000,
|
||||
0x0000200000000000, 0x4004000000008008, 0x4004200000008008, 0x0000200020008008,
|
||||
0x0000000020008008, 0x4004200000000000, 0x4004000000000000, 0x4004000020008008, ),
|
||||
( 0x0000400400000000, 0x0020000000000000, 0x0020000000100000, 0x0400000000100040,
|
||||
0x0420400400100040, 0x0400400400000040, 0x0020400400000000, 0x0000000000000000,
|
||||
0x0000000000100000, 0x0420000000100040, 0x0420000000000040, 0x0000400400100000,
|
||||
0x0400000000000040, 0x0020400400100000, 0x0000400400100000, 0x0420000000000040,
|
||||
0x0420000000100040, 0x0000400400000000, 0x0400400400000040, 0x0420400400100040,
|
||||
0x0000000000000000, 0x0020000000100000, 0x0400000000100040, 0x0020400400000000,
|
||||
0x0400400400100040, 0x0420400400000040, 0x0020400400100000, 0x0400000000000040,
|
||||
0x0420400400000040, 0x0400400400100040, 0x0020000000000000, 0x0000000000100000,
|
||||
0x0420400400000040, 0x0000400400100000, 0x0400400400100040, 0x0420000000000040,
|
||||
0x0000400400000000, 0x0020000000000000, 0x0000000000100000, 0x0400400400100040,
|
||||
0x0420000000100040, 0x0420400400000040, 0x0020400400000000, 0x0000000000000000,
|
||||
0x0020000000000000, 0x0400000000100040, 0x0400000000000040, 0x0020000000100000,
|
||||
0x0000000000000000, 0x0420000000100040, 0x0020000000100000, 0x0020400400000000,
|
||||
0x0420000000000040, 0x0000400400000000, 0x0420400400100040, 0x0000000000100000,
|
||||
0x0020400400100000, 0x0400000000000040, 0x0400400400000040, 0x0420400400100040,
|
||||
0x0400000000100040, 0x0020400400100000, 0x0000400400100000, 0x0400400400000040, ),
|
||||
( 0x8008000080082000, 0x0000002080082000, 0x8008002000000000, 0x0000000000000000,
|
||||
0x0000002000002000, 0x8008000080080000, 0x0000000080082000, 0x8008002080082000,
|
||||
0x8008000000000000, 0x0000000000002000, 0x0000002080080000, 0x8008002000000000,
|
||||
0x8008002080080000, 0x8008002000002000, 0x8008000000002000, 0x0000000080082000,
|
||||
0x0000002000000000, 0x8008002080080000, 0x8008000080080000, 0x0000002000002000,
|
||||
0x8008002080082000, 0x8008000000002000, 0x0000000000000000, 0x0000002080080000,
|
||||
0x0000000000002000, 0x0000000080080000, 0x8008002000002000, 0x8008000080082000,
|
||||
0x0000000080080000, 0x0000002000000000, 0x0000002080082000, 0x8008000000000000,
|
||||
0x0000000080080000, 0x0000002000000000, 0x8008000000002000, 0x8008002080082000,
|
||||
0x8008002000000000, 0x0000000000002000, 0x0000000000000000, 0x0000002080080000,
|
||||
0x8008000080082000, 0x8008002000002000, 0x0000002000002000, 0x8008000080080000,
|
||||
0x0000002080082000, 0x8008000000000000, 0x8008000080080000, 0x0000002000002000,
|
||||
0x8008002080082000, 0x0000000080080000, 0x0000000080082000, 0x8008000000002000,
|
||||
0x0000002080080000, 0x8008002000000000, 0x8008002000002000, 0x0000000080082000,
|
||||
0x8008000000000000, 0x0000002080082000, 0x8008002080080000, 0x0000000000000000,
|
||||
0x0000000000002000, 0x8008000080082000, 0x0000002000000000, 0x8008002080080000, ),
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# compressed/interleaved => final permutation table
|
||||
# Compression, final permutation, bit reverse
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: this was reordered from original table to make perm6464 logic simpler
|
||||
CF6464=(
|
||||
( 0x0000000000000000, 0x0000002000000000, 0x0000200000000000, 0x0000202000000000,
|
||||
0x0020000000000000, 0x0020002000000000, 0x0020200000000000, 0x0020202000000000,
|
||||
0x2000000000000000, 0x2000002000000000, 0x2000200000000000, 0x2000202000000000,
|
||||
0x2020000000000000, 0x2020002000000000, 0x2020200000000000, 0x2020202000000000, ),
|
||||
( 0x0000000000000000, 0x0000000200000000, 0x0000020000000000, 0x0000020200000000,
|
||||
0x0002000000000000, 0x0002000200000000, 0x0002020000000000, 0x0002020200000000,
|
||||
0x0200000000000000, 0x0200000200000000, 0x0200020000000000, 0x0200020200000000,
|
||||
0x0202000000000000, 0x0202000200000000, 0x0202020000000000, 0x0202020200000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000020, 0x0000000000002000, 0x0000000000002020,
|
||||
0x0000000000200000, 0x0000000000200020, 0x0000000000202000, 0x0000000000202020,
|
||||
0x0000000020000000, 0x0000000020000020, 0x0000000020002000, 0x0000000020002020,
|
||||
0x0000000020200000, 0x0000000020200020, 0x0000000020202000, 0x0000000020202020, ),
|
||||
( 0x0000000000000000, 0x0000000000000002, 0x0000000000000200, 0x0000000000000202,
|
||||
0x0000000000020000, 0x0000000000020002, 0x0000000000020200, 0x0000000000020202,
|
||||
0x0000000002000000, 0x0000000002000002, 0x0000000002000200, 0x0000000002000202,
|
||||
0x0000000002020000, 0x0000000002020002, 0x0000000002020200, 0x0000000002020202, ),
|
||||
( 0x0000000000000000, 0x0000008000000000, 0x0000800000000000, 0x0000808000000000,
|
||||
0x0080000000000000, 0x0080008000000000, 0x0080800000000000, 0x0080808000000000,
|
||||
0x8000000000000000, 0x8000008000000000, 0x8000800000000000, 0x8000808000000000,
|
||||
0x8080000000000000, 0x8080008000000000, 0x8080800000000000, 0x8080808000000000, ),
|
||||
( 0x0000000000000000, 0x0000000800000000, 0x0000080000000000, 0x0000080800000000,
|
||||
0x0008000000000000, 0x0008000800000000, 0x0008080000000000, 0x0008080800000000,
|
||||
0x0800000000000000, 0x0800000800000000, 0x0800080000000000, 0x0800080800000000,
|
||||
0x0808000000000000, 0x0808000800000000, 0x0808080000000000, 0x0808080800000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000080, 0x0000000000008000, 0x0000000000008080,
|
||||
0x0000000000800000, 0x0000000000800080, 0x0000000000808000, 0x0000000000808080,
|
||||
0x0000000080000000, 0x0000000080000080, 0x0000000080008000, 0x0000000080008080,
|
||||
0x0000000080800000, 0x0000000080800080, 0x0000000080808000, 0x0000000080808080, ),
|
||||
( 0x0000000000000000, 0x0000000000000008, 0x0000000000000800, 0x0000000000000808,
|
||||
0x0000000000080000, 0x0000000000080008, 0x0000000000080800, 0x0000000000080808,
|
||||
0x0000000008000000, 0x0000000008000008, 0x0000000008000800, 0x0000000008000808,
|
||||
0x0000000008080000, 0x0000000008080008, 0x0000000008080800, 0x0000000008080808, ),
|
||||
( 0x0000000000000000, 0x0000001000000000, 0x0000100000000000, 0x0000101000000000,
|
||||
0x0010000000000000, 0x0010001000000000, 0x0010100000000000, 0x0010101000000000,
|
||||
0x1000000000000000, 0x1000001000000000, 0x1000100000000000, 0x1000101000000000,
|
||||
0x1010000000000000, 0x1010001000000000, 0x1010100000000000, 0x1010101000000000, ),
|
||||
( 0x0000000000000000, 0x0000000100000000, 0x0000010000000000, 0x0000010100000000,
|
||||
0x0001000000000000, 0x0001000100000000, 0x0001010000000000, 0x0001010100000000,
|
||||
0x0100000000000000, 0x0100000100000000, 0x0100010000000000, 0x0100010100000000,
|
||||
0x0101000000000000, 0x0101000100000000, 0x0101010000000000, 0x0101010100000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000010, 0x0000000000001000, 0x0000000000001010,
|
||||
0x0000000000100000, 0x0000000000100010, 0x0000000000101000, 0x0000000000101010,
|
||||
0x0000000010000000, 0x0000000010000010, 0x0000000010001000, 0x0000000010001010,
|
||||
0x0000000010100000, 0x0000000010100010, 0x0000000010101000, 0x0000000010101010, ),
|
||||
( 0x0000000000000000, 0x0000000000000001, 0x0000000000000100, 0x0000000000000101,
|
||||
0x0000000000010000, 0x0000000000010001, 0x0000000000010100, 0x0000000000010101,
|
||||
0x0000000001000000, 0x0000000001000001, 0x0000000001000100, 0x0000000001000101,
|
||||
0x0000000001010000, 0x0000000001010001, 0x0000000001010100, 0x0000000001010101, ),
|
||||
( 0x0000000000000000, 0x0000004000000000, 0x0000400000000000, 0x0000404000000000,
|
||||
0x0040000000000000, 0x0040004000000000, 0x0040400000000000, 0x0040404000000000,
|
||||
0x4000000000000000, 0x4000004000000000, 0x4000400000000000, 0x4000404000000000,
|
||||
0x4040000000000000, 0x4040004000000000, 0x4040400000000000, 0x4040404000000000, ),
|
||||
( 0x0000000000000000, 0x0000000400000000, 0x0000040000000000, 0x0000040400000000,
|
||||
0x0004000000000000, 0x0004000400000000, 0x0004040000000000, 0x0004040400000000,
|
||||
0x0400000000000000, 0x0400000400000000, 0x0400040000000000, 0x0400040400000000,
|
||||
0x0404000000000000, 0x0404000400000000, 0x0404040000000000, 0x0404040400000000, ),
|
||||
( 0x0000000000000000, 0x0000000000000040, 0x0000000000004000, 0x0000000000004040,
|
||||
0x0000000000400000, 0x0000000000400040, 0x0000000000404000, 0x0000000000404040,
|
||||
0x0000000040000000, 0x0000000040000040, 0x0000000040004000, 0x0000000040004040,
|
||||
0x0000000040400000, 0x0000000040400040, 0x0000000040404000, 0x0000000040404040, ),
|
||||
( 0x0000000000000000, 0x0000000000000004, 0x0000000000000400, 0x0000000000000404,
|
||||
0x0000000000040000, 0x0000000000040004, 0x0000000000040400, 0x0000000000040404,
|
||||
0x0000000004000000, 0x0000000004000004, 0x0000000004000400, 0x0000000004000404,
|
||||
0x0000000004040000, 0x0000000004040004, 0x0000000004040400, 0x0000000004040404, ),
|
||||
)
|
||||
#===================================================================
|
||||
# eof _load_tables()
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# support
|
||||
#=============================================================================
|
||||
|
||||
def _permute(c, p):
|
||||
"""Returns the permutation of the given 32-bit or 64-bit code with
|
||||
the specified permutation table."""
|
||||
# NOTE: only difference between 32 & 64 bit permutations
|
||||
# is that len(p)==8 for 32 bit, and len(p)==16 for 64 bit.
|
||||
out = 0
|
||||
for r in p:
|
||||
out |= r[c&0xf]
|
||||
c >>= 4
|
||||
return out
|
||||
|
||||
#=============================================================================
|
||||
# packing & unpacking
|
||||
#=============================================================================
|
||||
# FIXME: more properly named _uint8_struct...
|
||||
_uint64_struct = struct.Struct(">Q")
|
||||
|
||||
def _pack64(value):
|
||||
return _uint64_struct.pack(value)
|
||||
|
||||
def _unpack64(value):
|
||||
return _uint64_struct.unpack(value)[0]
|
||||
|
||||
def _pack56(value):
|
||||
return _uint64_struct.pack(value)[1:]
|
||||
|
||||
def _unpack56(value):
|
||||
return _uint64_struct.unpack(b'\x00' + value)[0]
|
||||
|
||||
#=============================================================================
|
||||
# 56->64 key manipulation
|
||||
#=============================================================================
|
||||
|
||||
##def expand_7bit(value):
|
||||
## "expand 7-bit integer => 7-bits + 1 odd-parity bit"
|
||||
## # parity calc adapted from 32-bit even parity alg found at
|
||||
## # http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
|
||||
## assert 0 <= value < 0x80, "value out of range"
|
||||
## return (value<<1) | (0x9669 >> ((value ^ (value >> 4)) & 0xf)) & 1
|
||||
|
||||
_EXPAND_ITER = irange(49,-7,-7)
|
||||
|
||||
def expand_des_key(key):
|
||||
"""convert DES from 7 bytes to 8 bytes (by inserting empty parity bits)"""
|
||||
if isinstance(key, bytes):
|
||||
if len(key) != 7:
|
||||
raise ValueError("key must be 7 bytes in size")
|
||||
elif isinstance(key, int_types):
|
||||
if key < 0 or key > INT_56_MASK:
|
||||
raise ValueError("key must be 56-bit non-negative integer")
|
||||
return _unpack64(expand_des_key(_pack56(key)))
|
||||
else:
|
||||
raise exc.ExpectedTypeError(key, "bytes or int", "key")
|
||||
key = _unpack56(key)
|
||||
# NOTE: the following would insert correctly-valued parity bits in each key,
|
||||
# but the parity bit would just be ignored in des_encrypt_block(),
|
||||
# so not bothering to use it.
|
||||
# XXX: could make parity-restoring optionally available via flag
|
||||
##return join_byte_values(expand_7bit((key >> shift) & 0x7f)
|
||||
## for shift in _EXPAND_ITER)
|
||||
return join_byte_values(((key>>shift) & 0x7f)<<1 for shift in _EXPAND_ITER)
|
||||
|
||||
def shrink_des_key(key):
|
||||
"""convert DES key from 8 bytes to 7 bytes (by discarding the parity bits)"""
|
||||
if isinstance(key, bytes):
|
||||
if len(key) != 8:
|
||||
raise ValueError("key must be 8 bytes in size")
|
||||
return _pack56(shrink_des_key(_unpack64(key)))
|
||||
elif isinstance(key, int_types):
|
||||
if key < 0 or key > INT_64_MASK:
|
||||
raise ValueError("key must be 64-bit non-negative integer")
|
||||
else:
|
||||
raise exc.ExpectedTypeError(key, "bytes or int", "key")
|
||||
key >>= 1
|
||||
result = 0
|
||||
offset = 0
|
||||
while offset < 56:
|
||||
result |= (key & 0x7f)<<offset
|
||||
key >>= 8
|
||||
offset += 7
|
||||
assert not (result & ~INT_64_MASK)
|
||||
return result
|
||||
|
||||
#=============================================================================
|
||||
# des encryption
|
||||
#=============================================================================
|
||||
def des_encrypt_block(key, input, salt=0, rounds=1):
|
||||
"""encrypt single block of data using DES, operates on 8-byte strings.
|
||||
|
||||
:arg key:
|
||||
DES key as 7 byte string, or 8 byte string with parity bits
|
||||
(parity bit values are ignored).
|
||||
|
||||
:arg input:
|
||||
plaintext block to encrypt, as 8 byte string.
|
||||
|
||||
:arg salt:
|
||||
Optional 24-bit integer used to mutate the base DES algorithm in a
|
||||
manner specific to :class:`~passlib.hash.des_crypt` and its variants.
|
||||
The default value ``0`` provides the normal (unsalted) DES behavior.
|
||||
The salt functions as follows:
|
||||
if the ``i``'th bit of ``salt`` is set,
|
||||
bits ``i`` and ``i+24`` are swapped in the DES E-box output.
|
||||
|
||||
:arg rounds:
|
||||
Optional number of rounds of to apply the DES key schedule.
|
||||
the default (``rounds=1``) provides the normal DES behavior,
|
||||
but :class:`~passlib.hash.des_crypt` and its variants use
|
||||
alternate rounds values.
|
||||
|
||||
:raises TypeError: if any of the provided args are of the wrong type.
|
||||
:raises ValueError:
|
||||
if any of the input blocks are the wrong size,
|
||||
or the salt/rounds values are out of range.
|
||||
|
||||
:returns:
|
||||
resulting 8-byte ciphertext block.
|
||||
"""
|
||||
# validate & unpack key
|
||||
if isinstance(key, bytes):
|
||||
if len(key) == 7:
|
||||
key = expand_des_key(key)
|
||||
elif len(key) != 8:
|
||||
raise ValueError("key must be 7 or 8 bytes")
|
||||
key = _unpack64(key)
|
||||
else:
|
||||
raise exc.ExpectedTypeError(key, "bytes", "key")
|
||||
|
||||
# validate & unpack input
|
||||
if isinstance(input, bytes):
|
||||
if len(input) != 8:
|
||||
raise ValueError("input block must be 8 bytes")
|
||||
input = _unpack64(input)
|
||||
else:
|
||||
raise exc.ExpectedTypeError(input, "bytes", "input")
|
||||
|
||||
# hand things off to other func
|
||||
result = des_encrypt_int_block(key, input, salt, rounds)
|
||||
|
||||
# repack result
|
||||
return _pack64(result)
|
||||
|
||||
def des_encrypt_int_block(key, input, salt=0, rounds=1):
|
||||
"""encrypt single block of data using DES, operates on 64-bit integers.
|
||||
|
||||
this function is essentially the same as :func:`des_encrypt_block`,
|
||||
except that it operates on integers, and will NOT automatically
|
||||
expand 56-bit keys if provided (since there's no way to detect them).
|
||||
|
||||
:arg key:
|
||||
DES key as 64-bit integer (the parity bits are ignored).
|
||||
|
||||
:arg input:
|
||||
input block as 64-bit integer
|
||||
|
||||
:arg salt:
|
||||
optional 24-bit integer used to mutate the base DES algorithm.
|
||||
defaults to ``0`` (no mutation applied).
|
||||
|
||||
:arg rounds:
|
||||
optional number of rounds of to apply the DES key schedule.
|
||||
defaults to ``1``.
|
||||
|
||||
:raises TypeError: if any of the provided args are of the wrong type.
|
||||
:raises ValueError:
|
||||
if any of the input blocks are the wrong size,
|
||||
or the salt/rounds values are out of range.
|
||||
|
||||
:returns:
|
||||
resulting ciphertext as 64-bit integer.
|
||||
"""
|
||||
#---------------------------------------------------------------
|
||||
# input validation
|
||||
#---------------------------------------------------------------
|
||||
|
||||
# validate salt, rounds
|
||||
if rounds < 1:
|
||||
raise ValueError("rounds must be positive integer")
|
||||
if salt < 0 or salt > INT_24_MASK:
|
||||
raise ValueError("salt must be 24-bit non-negative integer")
|
||||
|
||||
# validate & unpack key
|
||||
if not isinstance(key, int_types):
|
||||
raise exc.ExpectedTypeError(key, "int", "key")
|
||||
elif key < 0 or key > INT_64_MASK:
|
||||
raise ValueError("key must be 64-bit non-negative integer")
|
||||
|
||||
# validate & unpack input
|
||||
if not isinstance(input, int_types):
|
||||
raise exc.ExpectedTypeError(input, "int", "input")
|
||||
elif input < 0 or input > INT_64_MASK:
|
||||
raise ValueError("input must be 64-bit non-negative integer")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# DES setup
|
||||
#---------------------------------------------------------------
|
||||
# load tables if not already done
|
||||
global SPE, PCXROT, IE3264, CF6464
|
||||
if PCXROT is None:
|
||||
_load_tables()
|
||||
|
||||
# load SPE into local vars to speed things up and remove an array access call
|
||||
SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE
|
||||
|
||||
# NOTE: parity bits are ignored completely
|
||||
# (UTs do fuzz testing to ensure this)
|
||||
|
||||
# generate key schedule
|
||||
# NOTE: generation was modified to output two elements at a time,
|
||||
# so that per-round loop could do two passes at once.
|
||||
def _iter_key_schedule(ks_odd):
|
||||
"""given 64-bit key, iterates over the 8 (even,odd) key schedule pairs"""
|
||||
for p_even, p_odd in PCXROT:
|
||||
ks_even = _permute(ks_odd, p_even)
|
||||
ks_odd = _permute(ks_even, p_odd)
|
||||
yield ks_even & _KS_MASK, ks_odd & _KS_MASK
|
||||
ks_list = list(_iter_key_schedule(key))
|
||||
|
||||
# expand 24 bit salt -> 32 bit per des_crypt & bsdi_crypt
|
||||
salt = (
|
||||
((salt & 0x00003f) << 26) |
|
||||
((salt & 0x000fc0) << 12) |
|
||||
((salt & 0x03f000) >> 2) |
|
||||
((salt & 0xfc0000) >> 16)
|
||||
)
|
||||
|
||||
# init L & R
|
||||
if input == 0:
|
||||
L = R = 0
|
||||
else:
|
||||
L = ((input >> 31) & 0xaaaaaaaa) | (input & 0x55555555)
|
||||
L = _permute(L, IE3264)
|
||||
|
||||
R = ((input >> 32) & 0xaaaaaaaa) | ((input >> 1) & 0x55555555)
|
||||
R = _permute(R, IE3264)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# main DES loop - run for specified number of rounds
|
||||
#---------------------------------------------------------------
|
||||
while rounds:
|
||||
rounds -= 1
|
||||
|
||||
# run over each part of the schedule, 2 parts at a time
|
||||
for ks_even, ks_odd in ks_list:
|
||||
k = ((R>>32) ^ R) & salt # use the salt to flip specific bits
|
||||
B = (k<<32) ^ k ^ R ^ ks_even
|
||||
|
||||
L ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^
|
||||
SPE2[(B>>42)&0x3f] ^ SPE3[(B>>34)&0x3f] ^
|
||||
SPE4[(B>>26)&0x3f] ^ SPE5[(B>>18)&0x3f] ^
|
||||
SPE6[(B>>10)&0x3f] ^ SPE7[(B>>2)&0x3f])
|
||||
|
||||
k = ((L>>32) ^ L) & salt # use the salt to flip specific bits
|
||||
B = (k<<32) ^ k ^ L ^ ks_odd
|
||||
|
||||
R ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^
|
||||
SPE2[(B>>42)&0x3f] ^ SPE3[(B>>34)&0x3f] ^
|
||||
SPE4[(B>>26)&0x3f] ^ SPE5[(B>>18)&0x3f] ^
|
||||
SPE6[(B>>10)&0x3f] ^ SPE7[(B>>2)&0x3f])
|
||||
|
||||
# swap L and R
|
||||
L, R = R, L
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# return final result
|
||||
#---------------------------------------------------------------
|
||||
C = (
|
||||
((L>>3) & 0x0f0f0f0f00000000)
|
||||
|
|
||||
((L<<33) & 0xf0f0f0f000000000)
|
||||
|
|
||||
((R>>35) & 0x000000000f0f0f0f)
|
||||
|
|
||||
((R<<1) & 0x00000000f0f0f0f0)
|
||||
)
|
||||
return _permute(C, CF6464)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1057
backend/venv/Lib/site-packages/passlib/crypto/digest.py
Normal file
1057
backend/venv/Lib/site-packages/passlib/crypto/digest.py
Normal file
File diff suppressed because it is too large
Load Diff
281
backend/venv/Lib/site-packages/passlib/crypto/scrypt/__init__.py
Normal file
281
backend/venv/Lib/site-packages/passlib/crypto/scrypt/__init__.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""
|
||||
passlib.utils.scrypt -- scrypt hash frontend and help utilities
|
||||
|
||||
XXX: add this module to public docs?
|
||||
"""
|
||||
#==========================================================================
|
||||
# imports
|
||||
#==========================================================================
|
||||
from __future__ import absolute_import
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils import to_bytes
|
||||
from passlib.utils.compat import PYPY
|
||||
# local
|
||||
__all__ =[
|
||||
"validate",
|
||||
"scrypt",
|
||||
]
|
||||
|
||||
#==========================================================================
|
||||
# config validation
|
||||
#==========================================================================
|
||||
|
||||
#: internal global constant for setting stdlib scrypt's maxmem (int bytes).
|
||||
#: set to -1 to auto-calculate (see _load_stdlib_backend() below)
|
||||
#: set to 0 for openssl default (32mb according to python docs)
|
||||
#: TODO: standardize this across backends, and expose support via scrypt hash config;
|
||||
#: currently not very configurable, and only applies to stdlib backend.
|
||||
SCRYPT_MAXMEM = -1
|
||||
|
||||
#: max output length in bytes
|
||||
MAX_KEYLEN = ((1 << 32) - 1) * 32
|
||||
|
||||
#: max ``r * p`` limit
|
||||
MAX_RP = (1 << 30) - 1
|
||||
|
||||
# TODO: unittests for this function
|
||||
def validate(n, r, p):
|
||||
"""
|
||||
helper which validates a set of scrypt config parameters.
|
||||
scrypt will take ``O(n * r * p)`` time and ``O(n * r)`` memory.
|
||||
limitations are that ``n = 2**<positive integer>``, ``n < 2**(16*r)``, ``r * p < 2 ** 30``.
|
||||
|
||||
:param n: scrypt rounds
|
||||
:param r: scrypt block size
|
||||
:param p: scrypt parallel factor
|
||||
"""
|
||||
if r < 1:
|
||||
raise ValueError("r must be > 0: r=%r" % r)
|
||||
|
||||
if p < 1:
|
||||
raise ValueError("p must be > 0: p=%r" % p)
|
||||
|
||||
if r * p > MAX_RP:
|
||||
# pbkdf2-hmac-sha256 limitation - it will be requested to generate ``p*(2*r)*64`` bytes,
|
||||
# but pbkdf2 can do max of (2**31-1) blocks, and sha-256 has 32 byte block size...
|
||||
# so ``(2**31-1)*32 >= p*r*128`` -> ``r*p < 2**30``
|
||||
raise ValueError("r * p must be < 2**30: r=%r, p=%r" % (r,p))
|
||||
|
||||
if n < 2 or n & (n - 1):
|
||||
raise ValueError("n must be > 1, and a power of 2: n=%r" % n)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
UINT32_SIZE = 4
|
||||
|
||||
|
||||
def estimate_maxmem(n, r, p, fudge=1.05):
|
||||
"""
|
||||
calculate memory required for parameter combination.
|
||||
assumes parameters have already been validated.
|
||||
|
||||
.. warning::
|
||||
this is derived from OpenSSL's scrypt maxmem formula;
|
||||
and may not be correct for other implementations
|
||||
(additional buffers, different parallelism tradeoffs, etc).
|
||||
"""
|
||||
# XXX: expand to provide upper bound for diff backends, or max across all of them?
|
||||
# NOTE: openssl's scrypt() enforces it's maxmem parameter based on calc located at
|
||||
# <openssl/providers/default/kdfs/scrypt.c>, ending in line containing "Blen + Vlen > maxmem"
|
||||
# using the following formula:
|
||||
# Blen = p * 128 * r
|
||||
# Vlen = 32 * r * (N + 2) * sizeof(uint32_t)
|
||||
# total_bytes = Blen + Vlen
|
||||
maxmem = r * (128 * p + 32 * (n + 2) * UINT32_SIZE)
|
||||
# add fudge factor so we don't have off-by-one mismatch w/ openssl
|
||||
maxmem = int(maxmem * fudge)
|
||||
return maxmem
|
||||
|
||||
|
||||
# TODO: configuration picker (may need psutil for full effect)
|
||||
|
||||
#==========================================================================
|
||||
# hash frontend
|
||||
#==========================================================================
|
||||
|
||||
#: backend function used by scrypt(), filled in by _set_backend()
|
||||
_scrypt = None
|
||||
|
||||
#: name of backend currently in use, exposed for informational purposes.
|
||||
backend = None
|
||||
|
||||
def scrypt(secret, salt, n, r, p=1, keylen=32):
|
||||
"""run SCrypt key derivation function using specified parameters.
|
||||
|
||||
:arg secret:
|
||||
passphrase string (unicode is encoded to bytes using utf-8).
|
||||
|
||||
:arg salt:
|
||||
salt string (unicode is encoded to bytes using utf-8).
|
||||
|
||||
:arg n:
|
||||
integer 'N' parameter
|
||||
|
||||
:arg r:
|
||||
integer 'r' parameter
|
||||
|
||||
:arg p:
|
||||
integer 'p' parameter
|
||||
|
||||
:arg keylen:
|
||||
number of bytes of key to generate.
|
||||
defaults to 32 (the internal block size).
|
||||
|
||||
:returns:
|
||||
a *keylen*-sized bytes instance
|
||||
|
||||
SCrypt imposes a number of constraints on it's input parameters:
|
||||
|
||||
* ``r * p < 2**30`` -- due to a limitation of PBKDF2-HMAC-SHA256.
|
||||
* ``keylen < (2**32 - 1) * 32`` -- due to a limitation of PBKDF2-HMAC-SHA256.
|
||||
* ``n`` must a be a power of 2, and > 1 -- internal limitation of scrypt() implementation
|
||||
|
||||
:raises ValueError: if the provided parameters are invalid (see constraints above).
|
||||
|
||||
.. warning::
|
||||
|
||||
Unless the third-party ``scrypt <https://pypi.python.org/pypi/scrypt/>``_ package
|
||||
is installed, passlib will use a builtin pure-python implementation of scrypt,
|
||||
which is *considerably* slower (and thus requires a much lower / less secure
|
||||
``n`` value in order to be usuable). Installing the :mod:`!scrypt` package
|
||||
is strongly recommended.
|
||||
"""
|
||||
validate(n, r, p)
|
||||
secret = to_bytes(secret, param="secret")
|
||||
salt = to_bytes(salt, param="salt")
|
||||
if keylen < 1:
|
||||
raise ValueError("keylen must be at least 1")
|
||||
if keylen > MAX_KEYLEN:
|
||||
raise ValueError("keylen too large, must be <= %d" % MAX_KEYLEN)
|
||||
return _scrypt(secret, salt, n, r, p, keylen)
|
||||
|
||||
|
||||
def _load_builtin_backend():
|
||||
"""
|
||||
Load pure-python scrypt implementation built into passlib.
|
||||
"""
|
||||
slowdown = 10 if PYPY else 100
|
||||
warn("Using builtin scrypt backend, which is %dx slower than is required "
|
||||
"for adequate security. Installing scrypt support (via 'pip install scrypt') "
|
||||
"is strongly recommended" % slowdown, exc.PasslibSecurityWarning)
|
||||
from ._builtin import ScryptEngine
|
||||
return ScryptEngine.execute
|
||||
|
||||
|
||||
def _load_cffi_backend():
|
||||
"""
|
||||
Try to import the ctypes-based scrypt hash function provided by the
|
||||
``scrypt <https://pypi.python.org/pypi/scrypt/>``_ package.
|
||||
"""
|
||||
try:
|
||||
from scrypt import hash
|
||||
return hash
|
||||
except ImportError:
|
||||
pass
|
||||
# not available, but check to see if package present but outdated / not installed right
|
||||
try:
|
||||
import scrypt
|
||||
except ImportError as err:
|
||||
if "scrypt" not in str(err):
|
||||
# e.g. if cffi isn't set up right
|
||||
# user should try importing scrypt explicitly to diagnose problem.
|
||||
warn("'scrypt' package failed to import correctly (possible installation issue?)",
|
||||
exc.PasslibWarning)
|
||||
# else: package just isn't installed
|
||||
else:
|
||||
warn("'scrypt' package is too old (lacks ``hash()`` method)", exc.PasslibWarning)
|
||||
return None
|
||||
|
||||
|
||||
def _load_stdlib_backend():
|
||||
"""
|
||||
Attempt to load stdlib scrypt() implement and return wrapper.
|
||||
Returns None if not found.
|
||||
"""
|
||||
try:
|
||||
# new in python 3.6, if compiled with openssl >= 1.1
|
||||
from hashlib import scrypt as stdlib_scrypt
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
def stdlib_scrypt_wrapper(secret, salt, n, r, p, keylen):
|
||||
# work out appropriate "maxmem" parameter
|
||||
#
|
||||
# TODO: would like to enforce a single "maxmem" policy across all backends;
|
||||
# and maybe expose this via scrypt hasher config.
|
||||
#
|
||||
# for now, since parameters should all be coming from internally-controlled sources
|
||||
# (password hashes), using policy of "whatever memory the parameters needs".
|
||||
# furthermore, since stdlib scrypt is only place that needs this,
|
||||
# currently calculating exactly what maxmem needs to make things work for stdlib call.
|
||||
# as hack, this can be overriden via SCRYPT_MAXMEM above,
|
||||
# would like to formalize all of this.
|
||||
maxmem = SCRYPT_MAXMEM
|
||||
if maxmem < 0:
|
||||
maxmem = estimate_maxmem(n, r, p)
|
||||
return stdlib_scrypt(password=secret, salt=salt, n=n, r=r, p=p, dklen=keylen,
|
||||
maxmem=maxmem)
|
||||
|
||||
return stdlib_scrypt_wrapper
|
||||
|
||||
|
||||
#: list of potential backends
|
||||
backend_values = ("stdlib", "scrypt", "builtin")
|
||||
|
||||
#: dict mapping backend name -> loader
|
||||
_backend_loaders = dict(
|
||||
stdlib=_load_stdlib_backend,
|
||||
scrypt=_load_cffi_backend, # XXX: rename backend constant to "cffi"?
|
||||
builtin=_load_builtin_backend,
|
||||
)
|
||||
|
||||
|
||||
def _set_backend(name, dryrun=False):
|
||||
"""
|
||||
set backend for scrypt(). if name not specified, loads first available.
|
||||
|
||||
:raises ~passlib.exc.MissingBackendError: if backend can't be found
|
||||
|
||||
.. note:: mainly intended to be called by unittests, and scrypt hash handler
|
||||
"""
|
||||
if name == "any":
|
||||
return
|
||||
elif name == "default":
|
||||
for name in backend_values:
|
||||
try:
|
||||
return _set_backend(name, dryrun=dryrun)
|
||||
except exc.MissingBackendError:
|
||||
continue
|
||||
raise exc.MissingBackendError("no scrypt backends available")
|
||||
else:
|
||||
loader = _backend_loaders.get(name)
|
||||
if not loader:
|
||||
raise ValueError("unknown scrypt backend: %r" % (name,))
|
||||
hash = loader()
|
||||
if not hash:
|
||||
raise exc.MissingBackendError("scrypt backend %r not available" % name)
|
||||
if dryrun:
|
||||
return
|
||||
global _scrypt, backend
|
||||
backend = name
|
||||
_scrypt = hash
|
||||
|
||||
# initialize backend
|
||||
_set_backend("default")
|
||||
|
||||
|
||||
def _has_backend(name):
|
||||
try:
|
||||
_set_backend(name, dryrun=True)
|
||||
return True
|
||||
except exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
#==========================================================================
|
||||
# eof
|
||||
#==========================================================================
|
||||
244
backend/venv/Lib/site-packages/passlib/crypto/scrypt/_builtin.py
Normal file
244
backend/venv/Lib/site-packages/passlib/crypto/scrypt/_builtin.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""passlib.utils.scrypt._builtin -- scrypt() kdf in pure-python"""
|
||||
#==========================================================================
|
||||
# imports
|
||||
#==========================================================================
|
||||
# core
|
||||
import operator
|
||||
import struct
|
||||
# pkg
|
||||
from passlib.utils.compat import izip
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
from passlib.crypto.scrypt._salsa import salsa20
|
||||
# local
|
||||
__all__ =[
|
||||
"ScryptEngine",
|
||||
]
|
||||
|
||||
#==========================================================================
|
||||
# scrypt engine
|
||||
#==========================================================================
|
||||
class ScryptEngine(object):
|
||||
"""
|
||||
helper class used to run scrypt kdf, see scrypt() for frontend
|
||||
|
||||
.. warning::
|
||||
this class does NO validation of the input ranges or types.
|
||||
|
||||
it's not intended to be used directly,
|
||||
but only as a backend for :func:`passlib.utils.scrypt.scrypt()`.
|
||||
"""
|
||||
#=================================================================
|
||||
# instance attrs
|
||||
#=================================================================
|
||||
|
||||
# primary scrypt config parameters
|
||||
n = 0
|
||||
r = 0
|
||||
p = 0
|
||||
|
||||
# derived values & objects
|
||||
smix_bytes = 0
|
||||
iv_bytes = 0
|
||||
bmix_len = 0
|
||||
bmix_half_len = 0
|
||||
bmix_struct = None
|
||||
integerify = None
|
||||
|
||||
#=================================================================
|
||||
# frontend
|
||||
#=================================================================
|
||||
@classmethod
|
||||
def execute(cls, secret, salt, n, r, p, keylen):
|
||||
"""create engine & run scrypt() hash calculation"""
|
||||
return cls(n, r, p).run(secret, salt, keylen)
|
||||
|
||||
#=================================================================
|
||||
# init
|
||||
#=================================================================
|
||||
def __init__(self, n, r, p):
|
||||
# store config
|
||||
self.n = n
|
||||
self.r = r
|
||||
self.p = p
|
||||
self.smix_bytes = r << 7 # num bytes in smix input - 2*r*16*4
|
||||
self.iv_bytes = self.smix_bytes * p
|
||||
self.bmix_len = bmix_len = r << 5 # length of bmix block list - 32*r integers
|
||||
self.bmix_half_len = r << 4
|
||||
assert struct.calcsize("I") == 4
|
||||
self.bmix_struct = struct.Struct("<" + str(bmix_len) + "I")
|
||||
|
||||
# use optimized bmix for certain cases
|
||||
if r == 1:
|
||||
self.bmix = self._bmix_1
|
||||
|
||||
# pick best integerify function - integerify(bmix_block) should
|
||||
# take last 64 bytes of block and return a little-endian integer.
|
||||
# since it's immediately converted % n, we only have to extract
|
||||
# the first 32 bytes if n < 2**32 - which due to the current
|
||||
# internal representation, is already unpacked as a 32-bit int.
|
||||
if n <= 0xFFFFffff:
|
||||
integerify = operator.itemgetter(-16)
|
||||
else:
|
||||
assert n <= 0xFFFFffffFFFFffff
|
||||
ig1 = operator.itemgetter(-16)
|
||||
ig2 = operator.itemgetter(-17)
|
||||
def integerify(X):
|
||||
return ig1(X) | (ig2(X)<<32)
|
||||
self.integerify = integerify
|
||||
|
||||
#=================================================================
|
||||
# frontend
|
||||
#=================================================================
|
||||
def run(self, secret, salt, keylen):
|
||||
"""
|
||||
run scrypt kdf for specified secret, salt, and keylen
|
||||
|
||||
.. note::
|
||||
|
||||
* time cost is ``O(n * r * p)``
|
||||
* mem cost is ``O(n * r)``
|
||||
"""
|
||||
# stretch salt into initial byte array via pbkdf2
|
||||
iv_bytes = self.iv_bytes
|
||||
input = pbkdf2_hmac("sha256", secret, salt, rounds=1, keylen=iv_bytes)
|
||||
|
||||
# split initial byte array into 'p' mflen-sized chunks,
|
||||
# and run each chunk through smix() to generate output chunk.
|
||||
smix = self.smix
|
||||
if self.p == 1:
|
||||
output = smix(input)
|
||||
else:
|
||||
# XXX: *could* use threading here, if really high p values encountered,
|
||||
# but would tradeoff for more memory usage.
|
||||
smix_bytes = self.smix_bytes
|
||||
output = b''.join(
|
||||
smix(input[offset:offset+smix_bytes])
|
||||
for offset in range(0, iv_bytes, smix_bytes)
|
||||
)
|
||||
|
||||
# stretch final byte array into output via pbkdf2
|
||||
return pbkdf2_hmac("sha256", secret, output, rounds=1, keylen=keylen)
|
||||
|
||||
#=================================================================
|
||||
# smix() helper
|
||||
#=================================================================
|
||||
def smix(self, input):
|
||||
"""run SCrypt smix function on a single input block
|
||||
|
||||
:arg input:
|
||||
byte string containing input data.
|
||||
interpreted as 32*r little endian 4 byte integers.
|
||||
|
||||
:returns:
|
||||
byte string containing output data
|
||||
derived by mixing input using n & r parameters.
|
||||
|
||||
.. note:: time & mem cost are both ``O(n * r)``
|
||||
"""
|
||||
# gather locals
|
||||
bmix = self.bmix
|
||||
bmix_struct = self.bmix_struct
|
||||
integerify = self.integerify
|
||||
n = self.n
|
||||
|
||||
# parse input into 32*r integers ('X' in scrypt source)
|
||||
# mem cost -- O(r)
|
||||
buffer = list(bmix_struct.unpack(input))
|
||||
|
||||
# starting with initial buffer contents, derive V s.t.
|
||||
# V[0]=initial_buffer ... V[i] = bmix(V[i-1], V[i-1]) ... V[n-1] = bmix(V[n-2], V[n-2])
|
||||
# final buffer contents should equal bmix(V[n-1], V[n-1])
|
||||
#
|
||||
# time cost -- O(n * r) -- n loops, bmix is O(r)
|
||||
# mem cost -- O(n * r) -- V is n-element array of r-element tuples
|
||||
# NOTE: could do time / memory tradeoff to shrink size of V
|
||||
def vgen():
|
||||
i = 0
|
||||
while i < n:
|
||||
last = tuple(buffer)
|
||||
yield last
|
||||
bmix(last, buffer)
|
||||
i += 1
|
||||
V = list(vgen())
|
||||
|
||||
# generate result from X & V.
|
||||
#
|
||||
# time cost -- O(n * r) -- loops n times, calls bmix() which has O(r) time cost
|
||||
# mem cost -- O(1) -- allocates nothing, calls bmix() which has O(1) mem cost
|
||||
get_v_elem = V.__getitem__
|
||||
n_mask = n - 1
|
||||
i = 0
|
||||
while i < n:
|
||||
j = integerify(buffer) & n_mask
|
||||
result = tuple(a ^ b for a, b in izip(buffer, get_v_elem(j)))
|
||||
bmix(result, buffer)
|
||||
i += 1
|
||||
|
||||
# # NOTE: we could easily support arbitrary values of ``n``, not just powers of 2,
|
||||
# # but very few implementations have that ability, so not enabling it for now...
|
||||
# if not n_is_log_2:
|
||||
# while i < n:
|
||||
# j = integerify(buffer) % n
|
||||
# tmp = tuple(a^b for a,b in izip(buffer, get_v_elem(j)))
|
||||
# bmix(tmp,buffer)
|
||||
# i += 1
|
||||
|
||||
# repack tmp
|
||||
return bmix_struct.pack(*buffer)
|
||||
|
||||
#=================================================================
|
||||
# bmix() helper
|
||||
#=================================================================
|
||||
def bmix(self, source, target):
|
||||
"""
|
||||
block mixing function used by smix()
|
||||
uses salsa20/8 core to mix block contents.
|
||||
|
||||
:arg source:
|
||||
source to read from.
|
||||
should be list of 32*r 4-byte integers
|
||||
(2*r salsa20 blocks).
|
||||
|
||||
:arg target:
|
||||
target to write to.
|
||||
should be list with same size as source.
|
||||
the existing value of this buffer is ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
this operates *in place* on target,
|
||||
so source & target should NOT be same list.
|
||||
|
||||
.. note::
|
||||
|
||||
* time cost is ``O(r)`` -- loops 16*r times, salsa20() has ``O(1)`` cost.
|
||||
|
||||
* memory cost is ``O(1)`` -- salsa20() uses 16 x uint4,
|
||||
all other operations done in-place.
|
||||
"""
|
||||
## assert source is not target
|
||||
# Y[-1] = B[2r-1], Y[i] = hash( Y[i-1] xor B[i])
|
||||
# B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
|
||||
half = self.bmix_half_len # 16*r out of 32*r - start of Y_1
|
||||
tmp = source[-16:] # 'X' in scrypt source
|
||||
siter = iter(source)
|
||||
j = 0
|
||||
while j < half:
|
||||
jn = j+16
|
||||
target[j:jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
|
||||
target[half+j:half+jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
|
||||
j = jn
|
||||
|
||||
def _bmix_1(self, source, target):
|
||||
"""special bmix() method optimized for ``r=1`` case"""
|
||||
B = source[16:]
|
||||
target[:16] = tmp = salsa20(a ^ b for a, b in izip(B, iter(source)))
|
||||
target[16:] = salsa20(a ^ b for a, b in izip(tmp, B))
|
||||
|
||||
#=================================================================
|
||||
# eoc
|
||||
#=================================================================
|
||||
|
||||
#==========================================================================
|
||||
# eof
|
||||
#==========================================================================
|
||||
@@ -0,0 +1,154 @@
|
||||
"""passlib.utils.scrypt._gen_files - meta script that generates _salsa.py"""
|
||||
#==========================================================================
|
||||
# imports
|
||||
#==========================================================================
|
||||
# core
|
||||
import os
|
||||
# pkg
|
||||
# local
|
||||
#==========================================================================
|
||||
# constants
|
||||
#==========================================================================
|
||||
|
||||
_SALSA_OPS = [
|
||||
# row = (target idx, source idx 1, source idx 2, rotate)
|
||||
# interpreted as salsa operation over uint32...
|
||||
# target = (source1+source2)<<rotate
|
||||
|
||||
##/* Operate on columns. */
|
||||
##define R(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
|
||||
##x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9);
|
||||
##x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18);
|
||||
( 4, 0, 12, 7),
|
||||
( 8, 4, 0, 9),
|
||||
( 12, 8, 4, 13),
|
||||
( 0, 12, 8, 18),
|
||||
|
||||
##x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9);
|
||||
##x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18);
|
||||
( 9, 5, 1, 7),
|
||||
( 13, 9, 5, 9),
|
||||
( 1, 13, 9, 13),
|
||||
( 5, 1, 13, 18),
|
||||
|
||||
##x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9);
|
||||
##x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18);
|
||||
( 14, 10, 6, 7),
|
||||
( 2, 14, 10, 9),
|
||||
( 6, 2, 14, 13),
|
||||
( 10, 6, 2, 18),
|
||||
|
||||
##x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9);
|
||||
##x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18);
|
||||
( 3, 15, 11, 7),
|
||||
( 7, 3, 15, 9),
|
||||
( 11, 7, 3, 13),
|
||||
( 15, 11, 7, 18),
|
||||
|
||||
##/* Operate on rows. */
|
||||
##x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9);
|
||||
##x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18);
|
||||
( 1, 0, 3, 7),
|
||||
( 2, 1, 0, 9),
|
||||
( 3, 2, 1, 13),
|
||||
( 0, 3, 2, 18),
|
||||
|
||||
##x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9);
|
||||
##x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18);
|
||||
( 6, 5, 4, 7),
|
||||
( 7, 6, 5, 9),
|
||||
( 4, 7, 6, 13),
|
||||
( 5, 4, 7, 18),
|
||||
|
||||
##x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9);
|
||||
##x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18);
|
||||
( 11, 10, 9, 7),
|
||||
( 8, 11, 10, 9),
|
||||
( 9, 8, 11, 13),
|
||||
( 10, 9, 8, 18),
|
||||
|
||||
##x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9);
|
||||
##x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18);
|
||||
( 12, 15, 14, 7),
|
||||
( 13, 12, 15, 9),
|
||||
( 14, 13, 12, 13),
|
||||
( 15, 14, 13, 18),
|
||||
]
|
||||
|
||||
def main():
|
||||
target = os.path.join(os.path.dirname(__file__), "_salsa.py")
|
||||
fh = file(target, "w")
|
||||
write = fh.write
|
||||
|
||||
VNAMES = ["v%d" % i for i in range(16)]
|
||||
|
||||
PAD = " " * 4
|
||||
PAD2 = " " * 8
|
||||
PAD3 = " " * 12
|
||||
TLIST = ", ".join("b%d" % i for i in range(16))
|
||||
VLIST = ", ".join(VNAMES)
|
||||
kwds = dict(
|
||||
VLIST=VLIST,
|
||||
TLIST=TLIST,
|
||||
)
|
||||
|
||||
write('''\
|
||||
"""passlib.utils.scrypt._salsa - salsa 20/8 core, autogenerated by _gen_salsa.py"""
|
||||
#=================================================================
|
||||
# salsa function
|
||||
#=================================================================
|
||||
|
||||
def salsa20(input):
|
||||
\"""apply the salsa20/8 core to the provided input
|
||||
|
||||
:args input: input list containing 16 32-bit integers
|
||||
:returns: result list containing 16 32-bit integers
|
||||
\"""
|
||||
|
||||
%(TLIST)s = input
|
||||
%(VLIST)s = \\
|
||||
%(TLIST)s
|
||||
|
||||
i = 0
|
||||
while i < 4:
|
||||
''' % kwds)
|
||||
|
||||
for idx, (target, source1, source2, rotate) in enumerate(_SALSA_OPS):
|
||||
write('''\
|
||||
# salsa op %(idx)d: [%(it)d] ^= ([%(is1)d]+[%(is2)d])<<<%(rot1)d
|
||||
t = (%(src1)s + %(src2)s) & 0xffffffff
|
||||
%(dst)s ^= ((t & 0x%(rmask)08x) << %(rot1)d) | (t >> %(rot2)d)
|
||||
|
||||
''' % dict(
|
||||
idx=idx, is1 = source1, is2=source2, it=target,
|
||||
src1=VNAMES[source1],
|
||||
src2=VNAMES[source2],
|
||||
dst=VNAMES[target],
|
||||
rmask=(1<<(32-rotate))-1,
|
||||
rot1=rotate,
|
||||
rot2=32-rotate,
|
||||
))
|
||||
|
||||
write('''\
|
||||
i += 1
|
||||
|
||||
''')
|
||||
|
||||
for idx in range(16):
|
||||
write(PAD + "b%d = (b%d + v%d) & 0xffffffff\n" % (idx,idx,idx))
|
||||
|
||||
write('''\
|
||||
|
||||
return %(TLIST)s
|
||||
|
||||
#=================================================================
|
||||
# eof
|
||||
#=================================================================
|
||||
''' % kwds)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
#==========================================================================
|
||||
# eof
|
||||
#==========================================================================
|
||||
170
backend/venv/Lib/site-packages/passlib/crypto/scrypt/_salsa.py
Normal file
170
backend/venv/Lib/site-packages/passlib/crypto/scrypt/_salsa.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""passlib.utils.scrypt._salsa - salsa 20/8 core, autogenerated by _gen_salsa.py"""
|
||||
#=================================================================
|
||||
# salsa function
|
||||
#=================================================================
|
||||
|
||||
def salsa20(input):
|
||||
"""apply the salsa20/8 core to the provided input
|
||||
|
||||
:args input: input list containing 16 32-bit integers
|
||||
:returns: result list containing 16 32-bit integers
|
||||
"""
|
||||
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 = input
|
||||
v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 = \
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15
|
||||
|
||||
i = 0
|
||||
while i < 4:
|
||||
# salsa op 0: [4] ^= ([0]+[12])<<<7
|
||||
t = (v0 + v12) & 0xffffffff
|
||||
v4 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 1: [8] ^= ([4]+[0])<<<9
|
||||
t = (v4 + v0) & 0xffffffff
|
||||
v8 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 2: [12] ^= ([8]+[4])<<<13
|
||||
t = (v8 + v4) & 0xffffffff
|
||||
v12 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 3: [0] ^= ([12]+[8])<<<18
|
||||
t = (v12 + v8) & 0xffffffff
|
||||
v0 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 4: [9] ^= ([5]+[1])<<<7
|
||||
t = (v5 + v1) & 0xffffffff
|
||||
v9 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 5: [13] ^= ([9]+[5])<<<9
|
||||
t = (v9 + v5) & 0xffffffff
|
||||
v13 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 6: [1] ^= ([13]+[9])<<<13
|
||||
t = (v13 + v9) & 0xffffffff
|
||||
v1 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 7: [5] ^= ([1]+[13])<<<18
|
||||
t = (v1 + v13) & 0xffffffff
|
||||
v5 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 8: [14] ^= ([10]+[6])<<<7
|
||||
t = (v10 + v6) & 0xffffffff
|
||||
v14 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 9: [2] ^= ([14]+[10])<<<9
|
||||
t = (v14 + v10) & 0xffffffff
|
||||
v2 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 10: [6] ^= ([2]+[14])<<<13
|
||||
t = (v2 + v14) & 0xffffffff
|
||||
v6 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 11: [10] ^= ([6]+[2])<<<18
|
||||
t = (v6 + v2) & 0xffffffff
|
||||
v10 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 12: [3] ^= ([15]+[11])<<<7
|
||||
t = (v15 + v11) & 0xffffffff
|
||||
v3 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 13: [7] ^= ([3]+[15])<<<9
|
||||
t = (v3 + v15) & 0xffffffff
|
||||
v7 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 14: [11] ^= ([7]+[3])<<<13
|
||||
t = (v7 + v3) & 0xffffffff
|
||||
v11 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 15: [15] ^= ([11]+[7])<<<18
|
||||
t = (v11 + v7) & 0xffffffff
|
||||
v15 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 16: [1] ^= ([0]+[3])<<<7
|
||||
t = (v0 + v3) & 0xffffffff
|
||||
v1 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 17: [2] ^= ([1]+[0])<<<9
|
||||
t = (v1 + v0) & 0xffffffff
|
||||
v2 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 18: [3] ^= ([2]+[1])<<<13
|
||||
t = (v2 + v1) & 0xffffffff
|
||||
v3 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 19: [0] ^= ([3]+[2])<<<18
|
||||
t = (v3 + v2) & 0xffffffff
|
||||
v0 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 20: [6] ^= ([5]+[4])<<<7
|
||||
t = (v5 + v4) & 0xffffffff
|
||||
v6 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 21: [7] ^= ([6]+[5])<<<9
|
||||
t = (v6 + v5) & 0xffffffff
|
||||
v7 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 22: [4] ^= ([7]+[6])<<<13
|
||||
t = (v7 + v6) & 0xffffffff
|
||||
v4 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 23: [5] ^= ([4]+[7])<<<18
|
||||
t = (v4 + v7) & 0xffffffff
|
||||
v5 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 24: [11] ^= ([10]+[9])<<<7
|
||||
t = (v10 + v9) & 0xffffffff
|
||||
v11 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 25: [8] ^= ([11]+[10])<<<9
|
||||
t = (v11 + v10) & 0xffffffff
|
||||
v8 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 26: [9] ^= ([8]+[11])<<<13
|
||||
t = (v8 + v11) & 0xffffffff
|
||||
v9 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 27: [10] ^= ([9]+[8])<<<18
|
||||
t = (v9 + v8) & 0xffffffff
|
||||
v10 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
# salsa op 28: [12] ^= ([15]+[14])<<<7
|
||||
t = (v15 + v14) & 0xffffffff
|
||||
v12 ^= ((t & 0x01ffffff) << 7) | (t >> 25)
|
||||
|
||||
# salsa op 29: [13] ^= ([12]+[15])<<<9
|
||||
t = (v12 + v15) & 0xffffffff
|
||||
v13 ^= ((t & 0x007fffff) << 9) | (t >> 23)
|
||||
|
||||
# salsa op 30: [14] ^= ([13]+[12])<<<13
|
||||
t = (v13 + v12) & 0xffffffff
|
||||
v14 ^= ((t & 0x0007ffff) << 13) | (t >> 19)
|
||||
|
||||
# salsa op 31: [15] ^= ([14]+[13])<<<18
|
||||
t = (v14 + v13) & 0xffffffff
|
||||
v15 ^= ((t & 0x00003fff) << 18) | (t >> 14)
|
||||
|
||||
i += 1
|
||||
|
||||
b0 = (b0 + v0) & 0xffffffff
|
||||
b1 = (b1 + v1) & 0xffffffff
|
||||
b2 = (b2 + v2) & 0xffffffff
|
||||
b3 = (b3 + v3) & 0xffffffff
|
||||
b4 = (b4 + v4) & 0xffffffff
|
||||
b5 = (b5 + v5) & 0xffffffff
|
||||
b6 = (b6 + v6) & 0xffffffff
|
||||
b7 = (b7 + v7) & 0xffffffff
|
||||
b8 = (b8 + v8) & 0xffffffff
|
||||
b9 = (b9 + v9) & 0xffffffff
|
||||
b10 = (b10 + v10) & 0xffffffff
|
||||
b11 = (b11 + v11) & 0xffffffff
|
||||
b12 = (b12 + v12) & 0xffffffff
|
||||
b13 = (b13 + v13) & 0xffffffff
|
||||
b14 = (b14 + v14) & 0xffffffff
|
||||
b15 = (b15 + v15) & 0xffffffff
|
||||
|
||||
return b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15
|
||||
|
||||
#=================================================================
|
||||
# eof
|
||||
#=================================================================
|
||||
397
backend/venv/Lib/site-packages/passlib/exc.py
Normal file
397
backend/venv/Lib/site-packages/passlib/exc.py
Normal file
@@ -0,0 +1,397 @@
|
||||
"""passlib.exc -- exceptions & warnings raised by passlib"""
|
||||
#=============================================================================
|
||||
# exceptions
|
||||
#=============================================================================
|
||||
class UnknownBackendError(ValueError):
|
||||
"""
|
||||
Error raised if multi-backend handler doesn't recognize backend name.
|
||||
Inherits from :exc:`ValueError`.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
def __init__(self, hasher, backend):
|
||||
self.hasher = hasher
|
||||
self.backend = backend
|
||||
message = "%s: unknown backend: %r" % (hasher.name, backend)
|
||||
ValueError.__init__(self, message)
|
||||
|
||||
|
||||
# XXX: add a PasslibRuntimeError as base for Missing/Internal/Security runtime errors?
|
||||
|
||||
|
||||
class MissingBackendError(RuntimeError):
|
||||
"""Error raised if multi-backend handler has no available backends;
|
||||
or if specifically requested backend is not available.
|
||||
|
||||
:exc:`!MissingBackendError` derives
|
||||
from :exc:`RuntimeError`, since it usually indicates
|
||||
lack of an external library or OS feature.
|
||||
This is primarily raised by handlers which depend on
|
||||
external libraries (which is currently just
|
||||
:class:`~passlib.hash.bcrypt`).
|
||||
"""
|
||||
|
||||
|
||||
class InternalBackendError(RuntimeError):
|
||||
"""
|
||||
Error raised if something unrecoverable goes wrong with backend call;
|
||||
such as if ``crypt.crypt()`` returning a malformed hash.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
|
||||
|
||||
class PasswordValueError(ValueError):
|
||||
"""
|
||||
Error raised if a password can't be hashed / verified for various reasons.
|
||||
This exception derives from the builtin :exc:`!ValueError`.
|
||||
|
||||
May be thrown directly when password violates internal invariants of hasher
|
||||
(e.g. some don't support NULL characters). Hashers may also throw more specific subclasses,
|
||||
such as :exc:`!PasswordSizeError`.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PasswordSizeError(PasswordValueError):
|
||||
"""
|
||||
Error raised if a password exceeds the maximum size allowed
|
||||
by Passlib (by default, 4096 characters); or if password exceeds
|
||||
a hash-specific size limitation.
|
||||
|
||||
This exception derives from :exc:`PasswordValueError` (above).
|
||||
|
||||
Many password hash algorithms take proportionately larger amounts of time and/or
|
||||
memory depending on the size of the password provided. This could present
|
||||
a potential denial of service (DOS) situation if a maliciously large
|
||||
password is provided to an application. Because of this, Passlib enforces
|
||||
a maximum size limit, but one which should be *much* larger
|
||||
than any legitimate password. :exc:`PasswordSizeError` derives
|
||||
from :exc:`!ValueError`.
|
||||
|
||||
.. note::
|
||||
Applications wishing to use a different limit should set the
|
||||
``PASSLIB_MAX_PASSWORD_SIZE`` environmental variable before
|
||||
Passlib is loaded. The value can be any large positive integer.
|
||||
|
||||
.. attribute:: max_size
|
||||
|
||||
indicates the maximum allowed size.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
max_size = None
|
||||
|
||||
def __init__(self, max_size, msg=None):
|
||||
self.max_size = max_size
|
||||
if msg is None:
|
||||
msg = "password exceeds maximum allowed size"
|
||||
PasswordValueError.__init__(self, msg)
|
||||
|
||||
# this also prevents a glibc crypt segfault issue, detailed here ...
|
||||
# http://www.openwall.com/lists/oss-security/2011/11/15/1
|
||||
|
||||
class PasswordTruncateError(PasswordSizeError):
|
||||
"""
|
||||
Error raised if password would be truncated by hash.
|
||||
This derives from :exc:`PasswordSizeError` (above).
|
||||
|
||||
Hashers such as :class:`~passlib.hash.bcrypt` can be configured to raises
|
||||
this error by setting ``truncate_error=True``.
|
||||
|
||||
.. attribute:: max_size
|
||||
|
||||
indicates the maximum allowed size.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
def __init__(self, cls, msg=None):
|
||||
if msg is None:
|
||||
msg = ("Password too long (%s truncates to %d characters)" %
|
||||
(cls.name, cls.truncate_size))
|
||||
PasswordSizeError.__init__(self, cls.truncate_size, msg)
|
||||
|
||||
|
||||
class PasslibSecurityError(RuntimeError):
|
||||
"""
|
||||
Error raised if critical security issue is detected
|
||||
(e.g. an attempt is made to use a vulnerable version of a bcrypt backend).
|
||||
|
||||
.. versionadded:: 1.6.3
|
||||
"""
|
||||
|
||||
|
||||
class TokenError(ValueError):
|
||||
"""
|
||||
Base error raised by v:mod:`passlib.totp` when
|
||||
a token can't be parsed / isn't valid / etc.
|
||||
Derives from :exc:`!ValueError`.
|
||||
|
||||
Usually one of the more specific subclasses below will be raised:
|
||||
|
||||
* :class:`MalformedTokenError` -- invalid chars, too few digits
|
||||
* :class:`InvalidTokenError` -- no match found
|
||||
* :class:`UsedTokenError` -- match found, but token already used
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
#: default message to use if none provided -- subclasses may fill this in
|
||||
_default_message = 'Token not acceptable'
|
||||
|
||||
def __init__(self, msg=None, *args, **kwds):
|
||||
if msg is None:
|
||||
msg = self._default_message
|
||||
ValueError.__init__(self, msg, *args, **kwds)
|
||||
|
||||
|
||||
class MalformedTokenError(TokenError):
|
||||
"""
|
||||
Error raised by :mod:`passlib.totp` when a token isn't formatted correctly
|
||||
(contains invalid characters, wrong number of digits, etc)
|
||||
"""
|
||||
_default_message = "Unrecognized token"
|
||||
|
||||
|
||||
class InvalidTokenError(TokenError):
|
||||
"""
|
||||
Error raised by :mod:`passlib.totp` when a token is formatted correctly,
|
||||
but doesn't match any tokens within valid range.
|
||||
"""
|
||||
_default_message = "Token did not match"
|
||||
|
||||
|
||||
class UsedTokenError(TokenError):
|
||||
"""
|
||||
Error raised by :mod:`passlib.totp` if a token is reused.
|
||||
Derives from :exc:`TokenError`.
|
||||
|
||||
.. autoattribute:: expire_time
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
_default_message = "Token has already been used, please wait for another."
|
||||
|
||||
#: optional value indicating when current counter period will end,
|
||||
#: and a new token can be generated.
|
||||
expire_time = None
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.expire_time = kwds.pop("expire_time", None)
|
||||
TokenError.__init__(self, *args, **kwds)
|
||||
|
||||
|
||||
class UnknownHashError(ValueError):
|
||||
"""
|
||||
Error raised by :class:`~passlib.crypto.lookup_hash` if hash name is not recognized.
|
||||
This exception derives from :exc:`!ValueError`.
|
||||
|
||||
As of version 1.7.3, this may also be raised if hash algorithm is known,
|
||||
but has been disabled due to FIPS mode (message will include phrase "disabled for fips").
|
||||
|
||||
As of version 1.7.4, this may be raised if a :class:`~passlib.context.CryptContext`
|
||||
is unable to identify the algorithm used by a password hash.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
.. versionchanged: 1.7.3
|
||||
added 'message' argument.
|
||||
|
||||
.. versionchanged:: 1.7.4
|
||||
altered call signature.
|
||||
"""
|
||||
def __init__(self, message=None, value=None):
|
||||
self.value = value
|
||||
if message is None:
|
||||
message = "unknown hash algorithm: %r" % value
|
||||
self.message = message
|
||||
ValueError.__init__(self, message, value)
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# warnings
|
||||
#=============================================================================
|
||||
class PasslibWarning(UserWarning):
|
||||
"""base class for Passlib's user warnings,
|
||||
derives from the builtin :exc:`UserWarning`.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
# XXX: there's only one reference to this class, and it will go away in 2.0;
|
||||
# so can probably remove this along with this / roll this into PasslibHashWarning.
|
||||
class PasslibConfigWarning(PasslibWarning):
|
||||
"""Warning issued when non-fatal issue is found related to the configuration
|
||||
of a :class:`~passlib.context.CryptContext` instance.
|
||||
|
||||
This occurs primarily in one of two cases:
|
||||
|
||||
* The CryptContext contains rounds limits which exceed the hard limits
|
||||
imposed by the underlying algorithm.
|
||||
* An explicit rounds value was provided which exceeds the limits
|
||||
imposed by the CryptContext.
|
||||
|
||||
In both of these cases, the code will perform correctly & securely;
|
||||
but the warning is issued as a sign the configuration may need updating.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
class PasslibHashWarning(PasslibWarning):
|
||||
"""Warning issued when non-fatal issue is found with parameters
|
||||
or hash string passed to a passlib hash class.
|
||||
|
||||
This occurs primarily in one of two cases:
|
||||
|
||||
* A rounds value or other setting was explicitly provided which
|
||||
exceeded the handler's limits (and has been clamped
|
||||
by the :ref:`relaxed<relaxed-keyword>` flag).
|
||||
|
||||
* A malformed hash string was encountered which (while parsable)
|
||||
should be re-encoded.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
class PasslibRuntimeWarning(PasslibWarning):
|
||||
"""Warning issued when something unexpected happens during runtime.
|
||||
|
||||
The fact that it's a warning instead of an error means Passlib
|
||||
was able to correct for the issue, but that it's anomalous enough
|
||||
that the developers would love to hear under what conditions it occurred.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
class PasslibSecurityWarning(PasslibWarning):
|
||||
"""Special warning issued when Passlib encounters something
|
||||
that might affect security.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# error constructors
|
||||
#
|
||||
# note: these functions are used by the hashes in Passlib to raise common
|
||||
# error messages. They are currently just functions which return ValueError,
|
||||
# rather than subclasses of ValueError, since the specificity isn't needed
|
||||
# yet; and who wants to import a bunch of error classes when catching
|
||||
# ValueError will do?
|
||||
#=============================================================================
|
||||
|
||||
def _get_name(handler):
|
||||
return handler.name if handler else "<unnamed>"
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# generic helpers
|
||||
#------------------------------------------------------------------------
|
||||
def type_name(value):
|
||||
"""return pretty-printed string containing name of value's type"""
|
||||
cls = value.__class__
|
||||
if cls.__module__ and cls.__module__ not in ["__builtin__", "builtins"]:
|
||||
return "%s.%s" % (cls.__module__, cls.__name__)
|
||||
elif value is None:
|
||||
return 'None'
|
||||
else:
|
||||
return cls.__name__
|
||||
|
||||
def ExpectedTypeError(value, expected, param):
|
||||
"""error message when param was supposed to be one type, but found another"""
|
||||
# NOTE: value is never displayed, since it may sometimes be a password.
|
||||
name = type_name(value)
|
||||
return TypeError("%s must be %s, not %s" % (param, expected, name))
|
||||
|
||||
def ExpectedStringError(value, param):
|
||||
"""error message when param was supposed to be unicode or bytes"""
|
||||
return ExpectedTypeError(value, "unicode or bytes", param)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# hash/verify parameter errors
|
||||
#------------------------------------------------------------------------
|
||||
def MissingDigestError(handler=None):
|
||||
"""raised when verify() method gets passed config string instead of hash"""
|
||||
name = _get_name(handler)
|
||||
return ValueError("expected %s hash, got %s config string instead" %
|
||||
(name, name))
|
||||
|
||||
def NullPasswordError(handler=None):
|
||||
"""raised by OS crypt() supporting hashes, which forbid NULLs in password"""
|
||||
name = _get_name(handler)
|
||||
return PasswordValueError("%s does not allow NULL bytes in password" % name)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# errors when parsing hashes
|
||||
#------------------------------------------------------------------------
|
||||
def InvalidHashError(handler=None):
|
||||
"""error raised if unrecognized hash provided to handler"""
|
||||
return ValueError("not a valid %s hash" % _get_name(handler))
|
||||
|
||||
def MalformedHashError(handler=None, reason=None):
|
||||
"""error raised if recognized-but-malformed hash provided to handler"""
|
||||
text = "malformed %s hash" % _get_name(handler)
|
||||
if reason:
|
||||
text = "%s (%s)" % (text, reason)
|
||||
return ValueError(text)
|
||||
|
||||
def ZeroPaddedRoundsError(handler=None):
|
||||
"""error raised if hash was recognized but contained zero-padded rounds field"""
|
||||
return MalformedHashError(handler, "zero-padded rounds")
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# settings / hash component errors
|
||||
#------------------------------------------------------------------------
|
||||
def ChecksumSizeError(handler, raw=False):
|
||||
"""error raised if hash was recognized, but checksum was wrong size"""
|
||||
# TODO: if handler.use_defaults is set, this came from app-provided value,
|
||||
# not from parsing a hash string, might want different error msg.
|
||||
checksum_size = handler.checksum_size
|
||||
unit = "bytes" if raw else "chars"
|
||||
reason = "checksum must be exactly %d %s" % (checksum_size, unit)
|
||||
return MalformedHashError(handler, reason)
|
||||
|
||||
#=============================================================================
|
||||
# sensitive info helpers
|
||||
#=============================================================================
|
||||
|
||||
#: global flag, set temporarily by UTs to allow debug_only_repr() to display sensitive values.
|
||||
ENABLE_DEBUG_ONLY_REPR = False
|
||||
|
||||
|
||||
def debug_only_repr(value, param="hash"):
|
||||
"""
|
||||
helper used to display sensitive data (hashes etc) within error messages.
|
||||
currently returns placeholder test UNLESS unittests are running,
|
||||
in which case the real value is displayed.
|
||||
|
||||
mainly useful to prevent hashes / secrets from being exposed in production tracebacks;
|
||||
while still being visible from test failures.
|
||||
|
||||
NOTE: api subject to change, may formalize this more in the future.
|
||||
"""
|
||||
if ENABLE_DEBUG_ONLY_REPR or value is None or isinstance(value, bool):
|
||||
return repr(value)
|
||||
return "<%s %s value omitted>" % (param, type(value))
|
||||
|
||||
|
||||
def CryptBackendError(handler, config, hash, # *
|
||||
source="crypt.crypt()"):
|
||||
"""
|
||||
helper to generate standard message when ``crypt.crypt()`` returns invalid result.
|
||||
takes care of automatically masking contents of config & hash outside of UTs.
|
||||
"""
|
||||
name = _get_name(handler)
|
||||
msg = "%s returned invalid %s hash: config=%s hash=%s" % \
|
||||
(source, name, debug_only_repr(config), debug_only_repr(hash))
|
||||
raise InternalBackendError(msg)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1
backend/venv/Lib/site-packages/passlib/ext/__init__.py
Normal file
1
backend/venv/Lib/site-packages/passlib/ext/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
"""passlib.ext.django.models -- monkeypatch django hashing framework
|
||||
|
||||
this plugin monkeypatches django's hashing framework
|
||||
so that it uses a passlib context object, allowing handling of arbitrary
|
||||
hashes in Django databases.
|
||||
"""
|
||||
36
backend/venv/Lib/site-packages/passlib/ext/django/models.py
Normal file
36
backend/venv/Lib/site-packages/passlib/ext/django/models.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""passlib.ext.django.models -- monkeypatch django hashing framework"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
# site
|
||||
# pkg
|
||||
from passlib.context import CryptContext
|
||||
from passlib.ext.django.utils import DjangoContextAdapter
|
||||
# local
|
||||
__all__ = ["password_context"]
|
||||
|
||||
#=============================================================================
|
||||
# global attrs
|
||||
#=============================================================================
|
||||
|
||||
#: adapter instance used to drive most of this
|
||||
adapter = DjangoContextAdapter()
|
||||
|
||||
# the context object which this patches contrib.auth to use for password hashing.
|
||||
# configuration controlled by ``settings.PASSLIB_CONFIG``.
|
||||
password_context = adapter.context
|
||||
|
||||
#: hook callers should use if context is changed
|
||||
context_changed = adapter.reset_hashers
|
||||
|
||||
#=============================================================================
|
||||
# main code
|
||||
#=============================================================================
|
||||
|
||||
# load config & install monkeypatch
|
||||
adapter.load_model()
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1276
backend/venv/Lib/site-packages/passlib/ext/django/utils.py
Normal file
1276
backend/venv/Lib/site-packages/passlib/ext/django/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
"""passlib.handlers -- holds implementations of all passlib's builtin hash formats"""
|
||||
1009
backend/venv/Lib/site-packages/passlib/handlers/argon2.py
Normal file
1009
backend/venv/Lib/site-packages/passlib/handlers/argon2.py
Normal file
File diff suppressed because it is too large
Load Diff
1243
backend/venv/Lib/site-packages/passlib/handlers/bcrypt.py
Normal file
1243
backend/venv/Lib/site-packages/passlib/handlers/bcrypt.py
Normal file
File diff suppressed because it is too large
Load Diff
440
backend/venv/Lib/site-packages/passlib/handlers/cisco.py
Normal file
440
backend/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
backend/venv/Lib/site-packages/passlib/handlers/des_crypt.py
Normal file
607
backend/venv/Lib/site-packages/passlib/handlers/des_crypt.py
Normal file
@@ -0,0 +1,607 @@
|
||||
"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt, to_unicode
|
||||
from passlib.utils.binary import h64, h64big
|
||||
from passlib.utils.compat import byte_elem_value, u, uascii_to_str, unicode, suppress_cause
|
||||
from passlib.crypto.des import des_encrypt_int_block
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"des_crypt",
|
||||
"bsdi_crypt",
|
||||
"bigcrypt",
|
||||
"crypt16",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# pure-python backend for des_crypt family
|
||||
#=============================================================================
|
||||
_BNULL = b'\x00'
|
||||
|
||||
def _crypt_secret_to_key(secret):
|
||||
"""convert secret to 64-bit DES key.
|
||||
|
||||
this only uses the first 8 bytes of the secret,
|
||||
and discards the high 8th bit of each byte at that.
|
||||
a null parity bit is inserted after every 7th bit of the output.
|
||||
"""
|
||||
# NOTE: this would set the parity bits correctly,
|
||||
# but des_encrypt_int_block() would just ignore them...
|
||||
##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
|
||||
## for i, c in enumerate(secret[:8]))
|
||||
return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
|
||||
for i, c in enumerate(secret[:8]))
|
||||
|
||||
def _raw_des_crypt(secret, salt):
|
||||
"""pure-python backed for des_crypt"""
|
||||
assert len(salt) == 2
|
||||
|
||||
# NOTE: some OSes will accept non-HASH64 characters in the salt,
|
||||
# but what value they assign these characters varies wildy,
|
||||
# so just rejecting them outright.
|
||||
# the same goes for single-character salts...
|
||||
# some OSes duplicate the char, some insert a '.' char,
|
||||
# and openbsd does (something) which creates an invalid hash.
|
||||
salt_value = h64.decode_int12(salt)
|
||||
|
||||
# gotta do something - no official policy since this predates unicode
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
assert isinstance(secret, bytes)
|
||||
|
||||
# forbidding NULL char because underlying crypt() rejects them too.
|
||||
if _BNULL in secret:
|
||||
raise uh.exc.NullPasswordError(des_crypt)
|
||||
|
||||
# convert first 8 bytes of secret string into an integer
|
||||
key_value = _crypt_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result = des_encrypt_int_block(key_value, 0, salt_value, 25)
|
||||
|
||||
# run h64 encode on result
|
||||
return h64big.encode_int64(result)
|
||||
|
||||
def _bsdi_secret_to_key(secret):
|
||||
"""convert secret to DES key used by bsdi_crypt"""
|
||||
key_value = _crypt_secret_to_key(secret)
|
||||
idx = 8
|
||||
end = len(secret)
|
||||
while idx < end:
|
||||
next = idx + 8
|
||||
tmp_value = _crypt_secret_to_key(secret[idx:next])
|
||||
key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
|
||||
idx = next
|
||||
return key_value
|
||||
|
||||
def _raw_bsdi_crypt(secret, rounds, salt):
|
||||
"""pure-python backend for bsdi_crypt"""
|
||||
|
||||
# decode salt
|
||||
salt_value = h64.decode_int24(salt)
|
||||
|
||||
# gotta do something - no official policy since this predates unicode
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
assert isinstance(secret, bytes)
|
||||
|
||||
# forbidding NULL char because underlying crypt() rejects them too.
|
||||
if _BNULL in secret:
|
||||
raise uh.exc.NullPasswordError(bsdi_crypt)
|
||||
|
||||
# convert secret string into an integer
|
||||
key_value = _bsdi_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
|
||||
|
||||
# run h64 encode on result
|
||||
return h64big.encode_int64(result)
|
||||
|
||||
#=============================================================================
|
||||
# handlers
|
||||
#=============================================================================
|
||||
class des_crypt(uh.TruncateMixin, uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, des_crypt will silently truncate passwords larger than 8 bytes.
|
||||
Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "des_crypt"
|
||||
setting_kwds = ("salt", "truncate_error")
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
checksum_size = 11
|
||||
|
||||
#--------------------
|
||||
# HasSalt
|
||||
#--------------------
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 8
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
# FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
|
||||
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>[./a-z0-9]{11})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
salt, chk = hash[:2], hash[2:]
|
||||
return cls(salt=salt, checksum=chk or None)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# digest calculation
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
|
||||
return self._calc_checksum_backend(secret)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", 'abgOeLfPimXQo'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
# NOTE: we let safe_crypt() encode unicode secret -> utf8;
|
||||
# no official policy since des-crypt predates unicode
|
||||
hash = safe_crypt(secret, self.salt)
|
||||
if hash is None:
|
||||
# py3's crypt.crypt() can't handle non-utf8 bytes.
|
||||
# fallback to builtin alg, which is always available.
|
||||
return self._calc_checksum_builtin(secret)
|
||||
if not hash.startswith(self.salt) or len(hash) != 13:
|
||||
raise uh.exc.CryptBackendError(self, self.salt, hash)
|
||||
return hash[2:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# builtin backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_builtin(cls):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
|
||||
return True
|
||||
|
||||
def _calc_checksum_builtin(self, secret):
|
||||
return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 5001, must be between 1 and 16777215, inclusive.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
:meth:`hash` will now issue a warning if an even number of rounds is used
|
||||
(see :ref:`bsdi-crypt-security-issues` regarding weak DES keys).
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "bsdi_crypt"
|
||||
setting_kwds = ("salt", "rounds")
|
||||
checksum_size = 11
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 4
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 5001
|
||||
min_rounds = 1
|
||||
max_rounds = 16777215 # (1<<24)-1
|
||||
rounds_cost = "linear"
|
||||
|
||||
# NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
|
||||
# but that seems to be an OS policy, not a algorithm limitation.
|
||||
|
||||
#===================================================================
|
||||
# parsing
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
_
|
||||
(?P<rounds>[./a-z0-9]{4})
|
||||
(?P<salt>[./a-z0-9]{4})
|
||||
(?P<chk>[./a-z0-9]{11})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
rounds, salt, chk = m.group("rounds", "salt", "chk")
|
||||
return cls(
|
||||
rounds=h64.decode_int24(rounds.encode("ascii")),
|
||||
salt=salt,
|
||||
checksum=chk,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"),
|
||||
self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# validation
|
||||
#===================================================================
|
||||
|
||||
# NOTE: keeping this flag for admin/choose_rounds.py script.
|
||||
# want to eventually expose rounds logic to that script in better way.
|
||||
_avoid_even_rounds = True
|
||||
|
||||
@classmethod
|
||||
def using(cls, **kwds):
|
||||
subcls = super(bsdi_crypt, cls).using(**kwds)
|
||||
if not subcls.default_rounds & 1:
|
||||
# issue warning if caller set an even 'rounds' value.
|
||||
warn("bsdi_crypt rounds should be odd, as even rounds may reveal weak DES keys",
|
||||
uh.exc.PasslibSecurityWarning)
|
||||
return subcls
|
||||
|
||||
@classmethod
|
||||
def _generate_rounds(cls):
|
||||
rounds = super(bsdi_crypt, cls)._generate_rounds()
|
||||
# ensure autogenerated rounds are always odd
|
||||
# NOTE: doing this even for default_rounds so needs_update() doesn't get
|
||||
# caught in a loop.
|
||||
# FIXME: this technically might generate a rounds value 1 larger
|
||||
# than the requested upper bound - but better to err on side of safety.
|
||||
return rounds|1
|
||||
|
||||
#===================================================================
|
||||
# migration
|
||||
#===================================================================
|
||||
|
||||
def _calc_needs_update(self, **kwds):
|
||||
# mark bsdi_crypt hashes as deprecated if they have even rounds.
|
||||
if not self.rounds & 1:
|
||||
return True
|
||||
# hand off to base implementation
|
||||
return super(bsdi_crypt, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# backends
|
||||
#===================================================================
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", '_/...lLDAxARksGCHin.'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.to_string()
|
||||
hash = safe_crypt(secret, config)
|
||||
if hash is None:
|
||||
# py3's crypt.crypt() can't handle non-utf8 bytes.
|
||||
# fallback to builtin alg, which is always available.
|
||||
return self._calc_checksum_builtin(secret)
|
||||
if not hash.startswith(config[:9]) or len(hash) != 20:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-11:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# builtin backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_builtin(cls):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
|
||||
return True
|
||||
|
||||
def _calc_checksum_builtin(self, secret):
|
||||
return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class bigcrypt(uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "bigcrypt"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
# NOTE: checksum chars must be multiple of 11
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#===================================================================
|
||||
# internal helpers
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>([./a-z0-9]{11})+)?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _norm_checksum(self, checksum, relaxed=False):
|
||||
checksum = super(bigcrypt, self)._norm_checksum(checksum, relaxed=relaxed)
|
||||
if len(checksum) % 11:
|
||||
raise uh.exc.InvalidHashError(self)
|
||||
return checksum
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
|
||||
idx = 8
|
||||
end = len(secret)
|
||||
while idx < end:
|
||||
next = idx + 8
|
||||
chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
|
||||
idx = next
|
||||
return chk.decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, crypt16 will silently truncate passwords larger than 16 bytes.
|
||||
Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--------------------
|
||||
# PasswordHash
|
||||
#--------------------
|
||||
name = "crypt16"
|
||||
setting_kwds = ("salt", "truncate_error")
|
||||
|
||||
#--------------------
|
||||
# GenericHandler
|
||||
#--------------------
|
||||
checksum_size = 22
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# HasSalt
|
||||
#--------------------
|
||||
min_salt_size = max_salt_size = 2
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--------------------
|
||||
# TruncateMixin
|
||||
#--------------------
|
||||
truncate_size = 16
|
||||
|
||||
#===================================================================
|
||||
# internal helpers
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
(?P<salt>[./a-z0-9]{2})
|
||||
(?P<chk>[./a-z0-9]{22})?
|
||||
$"""), re.X|re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
|
||||
# parse salt value
|
||||
try:
|
||||
salt_value = h64.decode_int12(self.salt.encode("ascii"))
|
||||
except ValueError: # pragma: no cover - caught by class
|
||||
raise suppress_cause(ValueError("invalid chars in salt"))
|
||||
|
||||
# convert first 8 byts of secret string into an integer,
|
||||
key1 = _crypt_secret_to_key(secret)
|
||||
|
||||
# run data through des using input of 0
|
||||
result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
|
||||
|
||||
# convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
|
||||
key2 = _crypt_secret_to_key(secret[8:16])
|
||||
|
||||
# run data through des using input of 0
|
||||
result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
|
||||
|
||||
# done
|
||||
chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
|
||||
return chk.decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
168
backend/venv/Lib/site-packages/passlib/handlers/digests.py
Normal file
168
backend/venv/Lib/site-packages/passlib/handlers/digests.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""passlib.handlers.digests - plain hash digests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import hashlib
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str, to_bytes, render_bytes, consteq
|
||||
from passlib.utils.compat import unicode, str_to_uascii
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
# local
|
||||
__all__ = [
|
||||
"create_hex_hash",
|
||||
"hex_md4",
|
||||
"hex_md5",
|
||||
"hex_sha1",
|
||||
"hex_sha256",
|
||||
"hex_sha512",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# helpers for hexadecimal hashes
|
||||
#=============================================================================
|
||||
class HexDigestHash(uh.StaticHandler):
|
||||
"""this provides a template for supporting passwords stored as plain hexadecimal hashes"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
_hash_func = None # hash function to use - filled in by create_hex_hash()
|
||||
checksum_size = None # filled in by create_hex_hash()
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
|
||||
#: special for detecting if _hash_func is just a stub method.
|
||||
supported = True
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(self._hash_func(secret).hexdigest())
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
def create_hex_hash(digest, module=__name__, django_name=None, required=True):
|
||||
"""
|
||||
create hex-encoded unsalted hasher for specified digest algorithm.
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
If called with unknown/supported digest, won't throw error immediately,
|
||||
but instead return a dummy hasher that will throw error when called.
|
||||
|
||||
set ``required=True`` to restore old behavior.
|
||||
"""
|
||||
info = lookup_hash(digest, required=required)
|
||||
name = "hex_" + info.name
|
||||
if not info.supported:
|
||||
info.digest_size = 0
|
||||
hasher = type(name, (HexDigestHash,), dict(
|
||||
name=name,
|
||||
__module__=module, # so ABCMeta won't clobber it
|
||||
_hash_func=staticmethod(info.const), # sometimes it's a function, sometimes not. so wrap it.
|
||||
checksum_size=info.digest_size*2,
|
||||
__doc__="""This class implements a plain hexadecimal %s hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports no optional or contextual keywords.
|
||||
""" % (info.name,)
|
||||
))
|
||||
if not info.supported:
|
||||
hasher.supported = False
|
||||
if django_name:
|
||||
hasher.django_name = django_name
|
||||
return hasher
|
||||
|
||||
#=============================================================================
|
||||
# predefined handlers
|
||||
#=============================================================================
|
||||
|
||||
# NOTE: some digests below are marked as "required=False", because these may not be present on
|
||||
# FIPS systems (see issue 116). if missing, will return stub hasher that throws error
|
||||
# if an attempt is made to actually use hash/verify with them.
|
||||
|
||||
hex_md4 = create_hex_hash("md4", required=False)
|
||||
hex_md5 = create_hex_hash("md5", django_name="unsalted_md5", required=False)
|
||||
hex_sha1 = create_hex_hash("sha1", required=False)
|
||||
hex_sha256 = create_hex_hash("sha256")
|
||||
hex_sha512 = create_hex_hash("sha512")
|
||||
|
||||
#=============================================================================
|
||||
# htdigest
|
||||
#=============================================================================
|
||||
class htdigest(uh.MinimalHandler):
|
||||
"""htdigest hash function.
|
||||
|
||||
.. todo::
|
||||
document this hash
|
||||
"""
|
||||
name = "htdigest"
|
||||
setting_kwds = ()
|
||||
context_kwds = ("user", "realm", "encoding")
|
||||
default_encoding = "utf-8"
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, user, realm, encoding=None):
|
||||
# NOTE: this was deliberately written so that raw bytes are passed through
|
||||
# unchanged, the encoding kwd is only used to handle unicode values.
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
uh.validate_secret(secret)
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode(encoding)
|
||||
user = to_bytes(user, encoding, "user")
|
||||
realm = to_bytes(realm, encoding, "realm")
|
||||
data = render_bytes("%s:%s:%s", user, realm, secret)
|
||||
return hashlib.md5(data).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
"""normalize hash to native string, and validate it"""
|
||||
hash = to_native_str(hash, param="hash")
|
||||
if len(hash) != 32:
|
||||
raise uh.exc.MalformedHashError(cls, "wrong size")
|
||||
for char in hash:
|
||||
if char not in uh.LC_HEX_CHARS:
|
||||
raise uh.exc.MalformedHashError(cls, "invalid chars in hash")
|
||||
return hash
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, user, realm, encoding="utf-8"):
|
||||
hash = cls._norm_hash(hash)
|
||||
other = cls.hash(secret, user, realm, encoding)
|
||||
return consteq(hash, other)
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
try:
|
||||
cls._norm_hash(hash)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls):
|
||||
return cls.hash("", "", "")
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, user, realm, encoding=None):
|
||||
# NOTE: 'config' is ignored, as this hash has no salting / other configuration.
|
||||
# just have to make sure it's valid.
|
||||
cls._norm_hash(config)
|
||||
return cls.hash(secret, user, realm, encoding)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
512
backend/venv/Lib/site-packages/passlib/handlers/django.py
Normal file
512
backend/venv/Lib/site-packages/passlib/handlers/django.py
Normal file
@@ -0,0 +1,512 @@
|
||||
"""passlib.handlers.django- Django password hash support"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from base64 import b64encode
|
||||
from binascii import hexlify
|
||||
from hashlib import md5, sha1, sha256
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.handlers.bcrypt import _wrapped_bcrypt
|
||||
from passlib.hash import argon2, bcrypt, pbkdf2_sha1, pbkdf2_sha256
|
||||
from passlib.utils import to_unicode, rng, getrandstr
|
||||
from passlib.utils.binary import BASE64_CHARS
|
||||
from passlib.utils.compat import str_to_uascii, uascii_to_str, unicode, u
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"django_salted_sha1",
|
||||
"django_salted_md5",
|
||||
"django_bcrypt",
|
||||
"django_pbkdf2_sha1",
|
||||
"django_pbkdf2_sha256",
|
||||
"django_argon2",
|
||||
"django_des_crypt",
|
||||
"django_disabled",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# lazy imports & constants
|
||||
#=============================================================================
|
||||
|
||||
# imported by django_des_crypt._calc_checksum()
|
||||
des_crypt = None
|
||||
|
||||
def _import_des_crypt():
|
||||
global des_crypt
|
||||
if des_crypt is None:
|
||||
from passlib.hash import des_crypt
|
||||
return des_crypt
|
||||
|
||||
# django 1.4's salt charset
|
||||
SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
|
||||
#=============================================================================
|
||||
# salted hashes
|
||||
#=============================================================================
|
||||
class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler):
|
||||
"""base class providing common code for django hashes"""
|
||||
# name, ident, checksum_size must be set by subclass.
|
||||
# ident must include "$" suffix.
|
||||
setting_kwds = ("salt", "salt_size")
|
||||
|
||||
# NOTE: django 1.0-1.3 would accept empty salt strings.
|
||||
# django 1.4 won't, but this appears to be regression
|
||||
# (https://code.djangoproject.com/ticket/18144)
|
||||
# so presumably it will be fixed in a later release.
|
||||
default_salt_size = 12
|
||||
max_salt_size = None
|
||||
salt_chars = SALT_CHARS
|
||||
|
||||
checksum_chars = uh.LOWER_HEX_CHARS
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
return uh.render_mc2(self.ident, self.salt, self.checksum)
|
||||
|
||||
# NOTE: only used by PBKDF2
|
||||
class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
|
||||
"""base class providing common code for django hashes w/ variable rounds"""
|
||||
setting_kwds = DjangoSaltedHash.setting_kwds + ("rounds",)
|
||||
|
||||
min_rounds = 1
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum)
|
||||
|
||||
class django_salted_sha1(DjangoSaltedHash):
|
||||
"""This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and uses a single round of SHA1.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, a 12 character one will be autogenerated (this is recommended).
|
||||
If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of characters to use when autogenerating new salts.
|
||||
Defaults to 12, but can be any positive value.
|
||||
|
||||
This should be compatible with Django 1.4's :class:`!SHA1PasswordHasher` class.
|
||||
|
||||
.. versionchanged: 1.6
|
||||
This class now generates 12-character salts instead of 5,
|
||||
and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
|
||||
the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
|
||||
generates these hashes; but hashes generated in this manner will still be
|
||||
correctly interpreted by earlier versions of Django.
|
||||
"""
|
||||
name = "django_salted_sha1"
|
||||
django_name = "sha1"
|
||||
ident = u("sha1$")
|
||||
checksum_size = 40
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest())
|
||||
|
||||
class django_salted_md5(DjangoSaltedHash):
|
||||
"""This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and uses a single round of MD5.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, a 12 character one will be autogenerated (this is recommended).
|
||||
If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of characters to use when autogenerating new salts.
|
||||
Defaults to 12, but can be any positive value.
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.4's :class:`!MD5PasswordHasher` class.
|
||||
|
||||
.. versionchanged: 1.6
|
||||
This class now generates 12-character salts instead of 5,
|
||||
and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
|
||||
the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
|
||||
generates these hashes; but hashes generated in this manner will still be
|
||||
correctly interpreted by earlier versions of Django.
|
||||
"""
|
||||
name = "django_salted_md5"
|
||||
django_name = "md5"
|
||||
ident = u("md5$")
|
||||
checksum_size = 32
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
|
||||
|
||||
#=============================================================================
|
||||
# BCrypt
|
||||
#=============================================================================
|
||||
|
||||
django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt,
|
||||
prefix=u('bcrypt$'), ident=u("bcrypt$"),
|
||||
# NOTE: this docstring is duplicated in the docs, since sphinx
|
||||
# seems to be having trouble reading it via autodata::
|
||||
doc="""This class implements Django 1.4's BCrypt wrapper, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This is identical to :class:`!bcrypt` itself, but with
|
||||
the Django-specific prefix ``"bcrypt$"`` prepended.
|
||||
|
||||
See :doc:`/lib/passlib.hash.bcrypt` for more details,
|
||||
the usage and behavior is identical.
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.4's :class:`!BCryptPasswordHasher` class.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
""")
|
||||
django_bcrypt.django_name = "bcrypt"
|
||||
django_bcrypt._using_clone_attrs += ("django_name",)
|
||||
|
||||
#=============================================================================
|
||||
# BCRYPT + SHA256
|
||||
#=============================================================================
|
||||
|
||||
class django_bcrypt_sha256(_wrapped_bcrypt):
|
||||
"""This class implements Django 1.6's Bcrypt+SHA256 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
While the algorithm and format is somewhat different,
|
||||
the api and options for this hash are identical to :class:`!bcrypt` itself,
|
||||
see :doc:`bcrypt </lib/passlib.hash.bcrypt>` for more details.
|
||||
|
||||
.. versionadded:: 1.6.2
|
||||
"""
|
||||
name = "django_bcrypt_sha256"
|
||||
django_name = "bcrypt_sha256"
|
||||
_digest = sha256
|
||||
|
||||
# sample hash:
|
||||
# bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
|
||||
|
||||
# XXX: we can't use .ident attr due to bcrypt code using it.
|
||||
# working around that via django_prefix
|
||||
django_prefix = u('bcrypt_sha256$')
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
hash = uh.to_unicode_for_identify(hash)
|
||||
if not hash:
|
||||
return False
|
||||
return hash.startswith(cls.django_prefix)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
if not hash.startswith(cls.django_prefix):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
bhash = hash[len(cls.django_prefix):]
|
||||
if not bhash.startswith("$2"):
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
return super(django_bcrypt_sha256, cls).from_string(bhash)
|
||||
|
||||
def to_string(self):
|
||||
bhash = super(django_bcrypt_sha256, self).to_string()
|
||||
return uascii_to_str(self.django_prefix) + bhash
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
secret = hexlify(self._digest(secret).digest())
|
||||
return super(django_bcrypt_sha256, self)._calc_checksum(secret)
|
||||
|
||||
#=============================================================================
|
||||
# PBKDF2 variants
|
||||
#=============================================================================
|
||||
|
||||
class django_pbkdf2_sha256(DjangoVariableHash):
|
||||
"""This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, a 12 character one will be autogenerated (this is recommended).
|
||||
If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of characters to use when autogenerating new salts.
|
||||
Defaults to 12, but can be any positive value.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 29000, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.4's :class:`!PBKDF2PasswordHasher` class.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
name = "django_pbkdf2_sha256"
|
||||
django_name = "pbkdf2_sha256"
|
||||
ident = u('pbkdf2_sha256$')
|
||||
min_salt_size = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
checksum_size = 44 # 32 bytes -> base64
|
||||
default_rounds = pbkdf2_sha256.default_rounds # NOTE: django 1.6 uses 12000
|
||||
_digest = "sha256"
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: secret & salt will be encoded using UTF-8 by pbkdf2_hmac()
|
||||
hash = pbkdf2_hmac(self._digest, secret, self.salt, self.rounds)
|
||||
return b64encode(hash).rstrip().decode("ascii")
|
||||
|
||||
class django_pbkdf2_sha1(django_pbkdf2_sha256):
|
||||
"""This class implements Django's PBKDF2-HMAC-SHA1 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, a 12 character one will be autogenerated (this is recommended).
|
||||
If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of characters to use when autogenerating new salts.
|
||||
Defaults to 12, but can be any positive value.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 131000, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.4's :class:`!PBKDF2SHA1PasswordHasher` class.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
name = "django_pbkdf2_sha1"
|
||||
django_name = "pbkdf2_sha1"
|
||||
ident = u('pbkdf2_sha1$')
|
||||
checksum_size = 28 # 20 bytes -> base64
|
||||
default_rounds = pbkdf2_sha1.default_rounds # NOTE: django 1.6 uses 12000
|
||||
_digest = "sha1"
|
||||
|
||||
#=============================================================================
|
||||
# Argon2
|
||||
#=============================================================================
|
||||
|
||||
# NOTE: as of 2019-11-11, Django's Argon2PasswordHasher only supports Type I;
|
||||
# so limiting this to ensure that as well.
|
||||
|
||||
django_argon2 = uh.PrefixWrapper(
|
||||
name="django_argon2",
|
||||
wrapped=argon2.using(type="I"),
|
||||
prefix=u('argon2'),
|
||||
ident=u('argon2$argon2i$'),
|
||||
# NOTE: this docstring is duplicated in the docs, since sphinx
|
||||
# seems to be having trouble reading it via autodata::
|
||||
doc="""This class implements Django 1.10's Argon2 wrapper, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This is identical to :class:`!argon2` itself, but with
|
||||
the Django-specific prefix ``"argon2$"`` prepended.
|
||||
|
||||
See :doc:`argon2 </lib/passlib.hash.argon2>` for more details,
|
||||
the usage and behavior is identical.
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.10's :class:`!Argon2PasswordHasher` class.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
""")
|
||||
django_argon2.django_name = "argon2"
|
||||
django_argon2._using_clone_attrs += ("django_name",)
|
||||
|
||||
#=============================================================================
|
||||
# DES
|
||||
#=============================================================================
|
||||
class django_des_crypt(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements Django's :class:`des_crypt` wrapper, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:param bool truncate_error:
|
||||
By default, django_des_crypt will silently truncate passwords larger than 8 bytes.
|
||||
Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
This should be compatible with the hashes generated by
|
||||
Django 1.4's :class:`!CryptPasswordHasher` class.
|
||||
Note that Django only supports this hash on Unix systems
|
||||
(though :class:`!django_des_crypt` is available cross-platform
|
||||
under Passlib).
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This class will now accept hashes with empty salt strings,
|
||||
since Django 1.4 generates them this way.
|
||||
"""
|
||||
name = "django_des_crypt"
|
||||
django_name = "crypt"
|
||||
setting_kwds = ("salt", "salt_size", "truncate_error")
|
||||
ident = u("crypt$")
|
||||
checksum_chars = salt_chars = uh.HASH64_CHARS
|
||||
checksum_size = 11
|
||||
min_salt_size = default_salt_size = 2
|
||||
truncate_size = 8
|
||||
|
||||
# NOTE: regarding duplicate salt field:
|
||||
#
|
||||
# django 1.0 had a "crypt$<salt1>$<salt2><digest>" hash format,
|
||||
# used [a-z0-9] to generate a 5 char salt, stored it in salt1,
|
||||
# duplicated the first two chars of salt1 as salt2.
|
||||
# it would throw an error if salt1 was empty.
|
||||
#
|
||||
# django 1.4 started generating 2 char salt using the full alphabet,
|
||||
# left salt1 empty, and only paid attention to salt2.
|
||||
#
|
||||
# in order to be compatible with django 1.0, the hashes generated
|
||||
# by this function will always include salt1, unless the following
|
||||
# class-level field is disabled (mainly used for testing)
|
||||
use_duplicate_salt = True
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
|
||||
if chk:
|
||||
# chk should be full des_crypt hash
|
||||
if not salt:
|
||||
# django 1.4 always uses empty salt field,
|
||||
# so extract salt from des_crypt hash <chk>
|
||||
salt = chk[:2]
|
||||
elif salt[:2] != chk[:2]:
|
||||
# django 1.0 stored 5 chars in salt field, and duplicated
|
||||
# the first two chars in <chk>. we keep the full salt,
|
||||
# but make sure the first two chars match as sanity check.
|
||||
raise uh.exc.MalformedHashError(cls,
|
||||
"first two digits of salt and checksum must match")
|
||||
# in all cases, strip salt chars from <chk>
|
||||
chk = chk[2:]
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = self.salt
|
||||
chk = salt[:2] + self.checksum
|
||||
if self.use_duplicate_salt:
|
||||
# filling in salt field, so that we're compatible with django 1.0
|
||||
return uh.render_mc2(self.ident, salt, chk)
|
||||
else:
|
||||
# django 1.4+ style hash
|
||||
return uh.render_mc2(self.ident, "", chk)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: we lazily import des_crypt,
|
||||
# since most django deploys won't use django_des_crypt
|
||||
global des_crypt
|
||||
if des_crypt is None:
|
||||
_import_des_crypt()
|
||||
# check for truncation (during .hash() calls only)
|
||||
if self.use_defaults:
|
||||
self._check_truncate_policy(secret)
|
||||
return des_crypt(salt=self.salt[:2])._calc_checksum(secret)
|
||||
|
||||
class django_disabled(uh.ifc.DisabledHash, uh.StaticHandler):
|
||||
"""This class provides disabled password behavior for Django, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class does not implement a hash, but instead
|
||||
claims the special hash string ``"!"`` which Django uses
|
||||
to indicate an account's password has been disabled.
|
||||
|
||||
* newly encrypted passwords will hash to ``"!"``.
|
||||
* it rejects all passwords.
|
||||
|
||||
.. note::
|
||||
|
||||
Django 1.6 prepends a randomly generated 40-char alphanumeric string
|
||||
to each unusuable password. This class recognizes such strings,
|
||||
but for backwards compatibility, still returns ``"!"``.
|
||||
|
||||
See `<https://code.djangoproject.com/ticket/20079>`_ for why
|
||||
Django appends an alphanumeric string.
|
||||
|
||||
.. versionchanged:: 1.6.2 added Django 1.6 support
|
||||
|
||||
.. versionchanged:: 1.7 started appending an alphanumeric string.
|
||||
"""
|
||||
name = "django_disabled"
|
||||
_hash_prefix = u("!")
|
||||
suffix_length = 40
|
||||
|
||||
# XXX: move this to StaticHandler, or wherever _hash_prefix is being used?
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
hash = uh.to_unicode_for_identify(hash)
|
||||
return hash.startswith(cls._hash_prefix)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# generate random suffix to match django's behavior
|
||||
return getrandstr(rng, BASE64_CHARS[:-2], self.suffix_length)
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
uh.validate_secret(secret)
|
||||
if not cls.identify(hash):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return False
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
214
backend/venv/Lib/site-packages/passlib/handlers/fshp.py
Normal file
214
backend/venv/Lib/site-packages/passlib/handlers/fshp.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""passlib.handlers.fshp
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from base64 import b64encode, b64decode
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.utils.compat import bascii_to_str, iteritems, u,\
|
||||
unicode
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
# local
|
||||
__all__ = [
|
||||
'fshp',
|
||||
]
|
||||
#=============================================================================
|
||||
# sha1-crypt
|
||||
#=============================================================================
|
||||
class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the FSHP password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:param salt:
|
||||
Optional raw salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any non-negative value.
|
||||
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 480000, must be between 1 and 4294967295, inclusive.
|
||||
|
||||
:param variant:
|
||||
Optionally specifies variant of FSHP to use.
|
||||
|
||||
* ``0`` - uses SHA-1 digest (deprecated).
|
||||
* ``1`` - uses SHA-2/256 digest (default).
|
||||
* ``2`` - uses SHA-2/384 digest.
|
||||
* ``3`` - uses SHA-2/512 digest.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "fshp"
|
||||
setting_kwds = ("salt", "salt_size", "rounds", "variant")
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
ident = u("{FSHP")
|
||||
# checksum_size is property() that depends on variant
|
||||
|
||||
#--HasRawSalt--
|
||||
default_salt_size = 16 # current passlib default, FSHP uses 8
|
||||
max_salt_size = None
|
||||
|
||||
#--HasRounds--
|
||||
# FIXME: should probably use different default rounds
|
||||
# based on the variant. setting for default variant (sha256) for now.
|
||||
default_rounds = 480000 # current passlib default, FSHP uses 4096
|
||||
min_rounds = 1 # set by FSHP
|
||||
max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--variants--
|
||||
default_variant = 1
|
||||
_variant_info = {
|
||||
# variant: (hash name, digest size)
|
||||
0: ("sha1", 20),
|
||||
1: ("sha256", 32),
|
||||
2: ("sha384", 48),
|
||||
3: ("sha512", 64),
|
||||
}
|
||||
_variant_aliases = dict(
|
||||
[(unicode(k),k) for k in _variant_info] +
|
||||
[(v[0],k) for k,v in iteritems(_variant_info)]
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# configuration
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def using(cls, variant=None, **kwds):
|
||||
subcls = super(fshp, cls).using(**kwds)
|
||||
if variant is not None:
|
||||
subcls.default_variant = cls._norm_variant(variant)
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
variant = None
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, variant=None, **kwds):
|
||||
# NOTE: variant must be set first, since it controls checksum size, etc.
|
||||
self.use_defaults = kwds.get("use_defaults") # load this early
|
||||
if variant is not None:
|
||||
variant = self._norm_variant(variant)
|
||||
elif self.use_defaults:
|
||||
variant = self.default_variant
|
||||
assert self._norm_variant(variant) == variant, "invalid default variant: %r" % (variant,)
|
||||
else:
|
||||
raise TypeError("no variant specified")
|
||||
self.variant = variant
|
||||
super(fshp, self).__init__(**kwds)
|
||||
|
||||
@classmethod
|
||||
def _norm_variant(cls, variant):
|
||||
if isinstance(variant, bytes):
|
||||
variant = variant.decode("ascii")
|
||||
if isinstance(variant, unicode):
|
||||
try:
|
||||
variant = cls._variant_aliases[variant]
|
||||
except KeyError:
|
||||
raise ValueError("invalid fshp variant")
|
||||
if not isinstance(variant, int):
|
||||
raise TypeError("fshp variant must be int or known alias")
|
||||
if variant not in cls._variant_info:
|
||||
raise ValueError("invalid fshp variant")
|
||||
return variant
|
||||
|
||||
@property
|
||||
def checksum_alg(self):
|
||||
return self._variant_info[self.variant][0]
|
||||
|
||||
@property
|
||||
def checksum_size(self):
|
||||
return self._variant_info[self.variant][1]
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
_hash_regex = re.compile(u(r"""
|
||||
^
|
||||
\{FSHP
|
||||
(\d+)\| # variant
|
||||
(\d+)\| # salt size
|
||||
(\d+)\} # rounds
|
||||
([a-zA-Z0-9+/]+={0,3}) # digest
|
||||
$"""), re.X)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
variant, salt_size, rounds, data = m.group(1,2,3,4)
|
||||
variant = int(variant)
|
||||
salt_size = int(salt_size)
|
||||
rounds = int(rounds)
|
||||
try:
|
||||
data = b64decode(data.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
salt = data[:salt_size]
|
||||
chk = data[salt_size:]
|
||||
return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)
|
||||
|
||||
def to_string(self):
|
||||
chk = self.checksum
|
||||
salt = self.salt
|
||||
data = bascii_to_str(b64encode(salt+chk))
|
||||
return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
# NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed.
|
||||
# this has only a minimal impact on security,
|
||||
# but it is worth noting this deviation.
|
||||
return pbkdf1(
|
||||
digest=self.checksum_alg,
|
||||
secret=self.salt,
|
||||
salt=secret,
|
||||
rounds=self.rounds,
|
||||
keylen=self.checksum_size,
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
359
backend/venv/Lib/site-packages/passlib/handlers/ldap_digests.py
Normal file
359
backend/venv/Lib/site-packages/passlib/handlers/ldap_digests.py
Normal file
@@ -0,0 +1,359 @@
|
||||
"""passlib.handlers.digests - plain hash digests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from base64 import b64encode, b64decode
|
||||
from hashlib import md5, sha1, sha256, sha512
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
# site
|
||||
# pkg
|
||||
from passlib.handlers.misc import plaintext
|
||||
from passlib.utils import unix_crypt_schemes, to_unicode
|
||||
from passlib.utils.compat import uascii_to_str, unicode, u
|
||||
from passlib.utils.decor import classproperty
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"ldap_plaintext",
|
||||
"ldap_md5",
|
||||
"ldap_sha1",
|
||||
"ldap_salted_md5",
|
||||
"ldap_salted_sha1",
|
||||
"ldap_salted_sha256",
|
||||
"ldap_salted_sha512",
|
||||
|
||||
##"get_active_ldap_crypt_schemes",
|
||||
"ldap_des_crypt",
|
||||
"ldap_bsdi_crypt",
|
||||
"ldap_md5_crypt",
|
||||
"ldap_sha1_crypt",
|
||||
"ldap_bcrypt",
|
||||
"ldap_sha256_crypt",
|
||||
"ldap_sha512_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# ldap helpers
|
||||
#=============================================================================
|
||||
class _Base64DigestHelper(uh.StaticHandler):
|
||||
"""helper for ldap_md5 / ldap_sha1"""
|
||||
# XXX: could combine this with hex digests in digests.py
|
||||
|
||||
ident = None # required - prefix identifier
|
||||
_hash_func = None # required - hash function
|
||||
_hash_regex = None # required - regexp to recognize hash
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
|
||||
@classproperty
|
||||
def _hash_prefix(cls):
|
||||
"""tell StaticHandler to strip ident from checksum"""
|
||||
return cls.ident
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = self._hash_func(secret).digest()
|
||||
return b64encode(chk).decode("ascii")
|
||||
|
||||
class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""helper for ldap_salted_md5 / ldap_salted_sha1"""
|
||||
setting_kwds = ("salt", "salt_size")
|
||||
checksum_chars = uh.PADDED_BASE64_CHARS
|
||||
|
||||
ident = None # required - prefix identifier
|
||||
_hash_func = None # required - hash function
|
||||
_hash_regex = None # required - regexp to recognize hash
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
# NOTE: openldap implementation uses 4 byte salt,
|
||||
# but it's been reported (issue 30) that some servers use larger salts.
|
||||
# the semi-related rfc3112 recommends support for up to 16 byte salts.
|
||||
min_salt_size = 4
|
||||
default_salt_size = 4
|
||||
max_salt_size = 16
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
try:
|
||||
data = b64decode(m.group("tmp").encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
cs = cls.checksum_size
|
||||
assert cs
|
||||
return cls(checksum=data[:cs], salt=data[cs:])
|
||||
|
||||
def to_string(self):
|
||||
data = self.checksum + self.salt
|
||||
hash = self.ident + b64encode(data).decode("ascii")
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return self._hash_func(secret + self.salt).digest()
|
||||
|
||||
#=============================================================================
|
||||
# implementations
|
||||
#=============================================================================
|
||||
class ldap_md5(_Base64DigestHelper):
|
||||
"""This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
|
||||
"""
|
||||
name = "ldap_md5"
|
||||
ident = u("{MD5}")
|
||||
_hash_func = md5
|
||||
_hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$"))
|
||||
|
||||
class ldap_sha1(_Base64DigestHelper):
|
||||
"""This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
|
||||
"""
|
||||
name = "ldap_sha1"
|
||||
ident = u("{SHA}")
|
||||
_hash_func = sha1
|
||||
_hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$"))
|
||||
|
||||
class ldap_salted_md5(_SaltedBase64DigestHelper):
|
||||
"""This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 4 bytes for compatibility with the LDAP spec,
|
||||
but some systems use larger salts, and Passlib supports
|
||||
any value between 4-16.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This format now supports variable length salts, instead of a fix 4 bytes.
|
||||
"""
|
||||
name = "ldap_salted_md5"
|
||||
ident = u("{SMD5}")
|
||||
checksum_size = 16
|
||||
_hash_func = md5
|
||||
_hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
|
||||
|
||||
class ldap_salted_sha1(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA1" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 4 bytes for compatibility with the LDAP spec,
|
||||
but some systems use larger salts, and Passlib supports
|
||||
any value between 4-16.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This format now supports variable length salts, instead of a fix 4 bytes.
|
||||
"""
|
||||
name = "ldap_salted_sha1"
|
||||
ident = u("{SSHA}")
|
||||
checksum_size = 20
|
||||
_hash_func = sha1
|
||||
# NOTE: 32 = ceil((20 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
|
||||
|
||||
|
||||
|
||||
class ldap_salted_sha256(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA2-256" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 8 bytes for compatibility with the LDAP spec,
|
||||
but Passlib supports any value between 4-16.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
name = "ldap_salted_sha256"
|
||||
ident = u("{SSHA256}")
|
||||
checksum_size = 32
|
||||
default_salt_size = 8
|
||||
_hash_func = sha256
|
||||
# NOTE: 48 = ceil((32 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$"))
|
||||
|
||||
|
||||
class ldap_salted_sha512(_SaltedBase64DigestHelper):
|
||||
"""
|
||||
This class stores passwords using LDAP's "Salted SHA2-512" format,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a 4-16 byte salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it may be any 4-16 byte string.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 8 bytes for compatibility with the LDAP spec,
|
||||
but Passlib supports any value between 4-16.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.7.3
|
||||
"""
|
||||
name = "ldap_salted_sha512"
|
||||
ident = u("{SSHA512}")
|
||||
checksum_size = 64
|
||||
default_salt_size = 8
|
||||
_hash_func = sha512
|
||||
# NOTE: 91 = ceil((64 + 4) * 4/3)
|
||||
_hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$"))
|
||||
|
||||
|
||||
class ldap_plaintext(plaintext):
|
||||
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class acts much like the generic :class:`!passlib.hash.plaintext` handler,
|
||||
except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix
|
||||
used by RFC2307 passwords.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keyword:
|
||||
|
||||
:type encoding: str
|
||||
:param encoding:
|
||||
This controls the character encoding to use (defaults to ``utf-8``).
|
||||
|
||||
This encoding will be used to encode :class:`!unicode` passwords
|
||||
under Python 2, and decode :class:`!bytes` hashes under Python 3.
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
The ``encoding`` keyword was added.
|
||||
"""
|
||||
# NOTE: this subclasses plaintext, since all it does differently
|
||||
# is override identify()
|
||||
|
||||
name = "ldap_plaintext"
|
||||
_2307_pat = re.compile(u(r"^\{\w+\}.*$"))
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls):
|
||||
# Overridding plaintext.genconfig() since it returns "",
|
||||
# but have to return non-empty value due to identify() below
|
||||
return "!"
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
# NOTE: identifies all strings EXCEPT those with {XXX} prefix
|
||||
hash = uh.to_unicode_for_identify(hash)
|
||||
return bool(hash) and cls._2307_pat.match(hash) is None
|
||||
|
||||
#=============================================================================
|
||||
# {CRYPT} wrappers
|
||||
# the following are wrappers around the base crypt algorithms,
|
||||
# which add the ldap required {CRYPT} prefix
|
||||
#=============================================================================
|
||||
ldap_crypt_schemes = [ 'ldap_' + name for name in unix_crypt_schemes ]
|
||||
|
||||
def _init_ldap_crypt_handlers():
|
||||
# NOTE: I don't like to implicitly modify globals() like this,
|
||||
# but don't want to write out all these handlers out either :)
|
||||
g = globals()
|
||||
for wname in unix_crypt_schemes:
|
||||
name = 'ldap_' + wname
|
||||
g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True)
|
||||
del g
|
||||
_init_ldap_crypt_handlers()
|
||||
|
||||
##_lcn_host = None
|
||||
##def get_host_ldap_crypt_schemes():
|
||||
## global _lcn_host
|
||||
## if _lcn_host is None:
|
||||
## from passlib.hosts import host_context
|
||||
## schemes = host_context.schemes()
|
||||
## _lcn_host = [
|
||||
## "ldap_" + name
|
||||
## for name in unix_crypt_names
|
||||
## if name in schemes
|
||||
## ]
|
||||
## return _lcn_host
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
346
backend/venv/Lib/site-packages/passlib/handlers/md5_crypt.py
Normal file
346
backend/venv/Lib/site-packages/passlib/handlers/md5_crypt.py
Normal file
@@ -0,0 +1,346 @@
|
||||
"""passlib.handlers.md5_crypt - md5-crypt algorithm"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import safe_crypt, test_crypt, repeat_string
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"md5_crypt",
|
||||
"apr_md5_crypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# pure-python backend
|
||||
#=============================================================================
|
||||
_BNULL = b"\x00"
|
||||
_MD5_MAGIC = b"$1$"
|
||||
_APR_MAGIC = b"$apr1$"
|
||||
|
||||
# pre-calculated offsets used to speed up C digest stage (see notes below).
|
||||
# sequence generated using the following:
|
||||
##perms_order = "p,pp,ps,psp,sp,spp".split(",")
|
||||
##def offset(i):
|
||||
## key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
|
||||
## ("p" if i % 7 else "") + ("" if i % 2 else "p"))
|
||||
## return perms_order.index(key)
|
||||
##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
|
||||
_c_digest_offsets = (
|
||||
(0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
|
||||
(4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
|
||||
(4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
|
||||
)
|
||||
|
||||
# map used to transpose bytes when encoding final digest
|
||||
_transpose_map = (12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11)
|
||||
|
||||
def _raw_md5_crypt(pwd, salt, use_apr=False):
|
||||
"""perform raw md5-crypt calculation
|
||||
|
||||
this function provides a pure-python implementation of the internals
|
||||
for the MD5-Crypt algorithms; it doesn't handle any of the
|
||||
parsing/validation of the hash strings themselves.
|
||||
|
||||
:arg pwd: password chars/bytes to hash
|
||||
:arg salt: salt chars to use
|
||||
:arg use_apr: use apache variant
|
||||
|
||||
:returns:
|
||||
encoded checksum chars
|
||||
"""
|
||||
# NOTE: regarding 'apr' format:
|
||||
# really, apache? you had to invent a whole new "$apr1$" format,
|
||||
# when all you did was change the ident incorporated into the hash?
|
||||
# would love to find webpage explaining why just using a portable
|
||||
# implementation of $1$ wasn't sufficient. *nothing else* was changed.
|
||||
|
||||
#===================================================================
|
||||
# init & validate inputs
|
||||
#===================================================================
|
||||
|
||||
# validate secret
|
||||
# XXX: not sure what official unicode policy is, using this as default
|
||||
if isinstance(pwd, unicode):
|
||||
pwd = pwd.encode("utf-8")
|
||||
assert isinstance(pwd, bytes), "pwd not unicode or bytes"
|
||||
if _BNULL in pwd:
|
||||
raise uh.exc.NullPasswordError(md5_crypt)
|
||||
pwd_len = len(pwd)
|
||||
|
||||
# validate salt - should have been taken care of by caller
|
||||
assert isinstance(salt, unicode), "salt not unicode"
|
||||
salt = salt.encode("ascii")
|
||||
assert len(salt) < 9, "salt too large"
|
||||
# NOTE: spec says salts larger than 8 bytes should be truncated,
|
||||
# instead of causing an error. this function assumes that's been
|
||||
# taken care of by the handler class.
|
||||
|
||||
# load APR specific constants
|
||||
if use_apr:
|
||||
magic = _APR_MAGIC
|
||||
else:
|
||||
magic = _MD5_MAGIC
|
||||
|
||||
#===================================================================
|
||||
# digest B - used as subinput to digest A
|
||||
#===================================================================
|
||||
db = md5(pwd + salt + pwd).digest()
|
||||
|
||||
#===================================================================
|
||||
# digest A - used to initialize first round of digest C
|
||||
#===================================================================
|
||||
# start out with pwd + magic + salt
|
||||
a_ctx = md5(pwd + magic + salt)
|
||||
a_ctx_update = a_ctx.update
|
||||
|
||||
# add pwd_len bytes of b, repeating b as many times as needed.
|
||||
a_ctx_update(repeat_string(db, pwd_len))
|
||||
|
||||
# add null chars & first char of password
|
||||
# NOTE: this may have historically been a bug,
|
||||
# where they meant to use db[0] instead of B_NULL,
|
||||
# but the original code memclear'ed db,
|
||||
# and now all implementations have to use this.
|
||||
i = pwd_len
|
||||
evenchar = pwd[:1]
|
||||
while i:
|
||||
a_ctx_update(_BNULL if i & 1 else evenchar)
|
||||
i >>= 1
|
||||
|
||||
# finish A
|
||||
da = a_ctx.digest()
|
||||
|
||||
#===================================================================
|
||||
# digest C - for a 1000 rounds, combine A, S, and P
|
||||
# digests in various ways; in order to burn CPU time.
|
||||
#===================================================================
|
||||
|
||||
# NOTE: the original MD5-Crypt implementation performs the C digest
|
||||
# calculation using the following loop:
|
||||
#
|
||||
##dc = da
|
||||
##i = 0
|
||||
##while i < rounds:
|
||||
## tmp_ctx = md5(pwd if i & 1 else dc)
|
||||
## if i % 3:
|
||||
## tmp_ctx.update(salt)
|
||||
## if i % 7:
|
||||
## tmp_ctx.update(pwd)
|
||||
## tmp_ctx.update(dc if i & 1 else pwd)
|
||||
## dc = tmp_ctx.digest()
|
||||
## i += 1
|
||||
#
|
||||
# The code Passlib uses (below) implements an equivalent algorithm,
|
||||
# it's just been heavily optimized to pre-calculate a large number
|
||||
# of things beforehand. It works off of a couple of observations
|
||||
# about the original algorithm:
|
||||
#
|
||||
# 1. each round is a combination of 'dc', 'salt', and 'pwd'; and the exact
|
||||
# combination is determined by whether 'i' a multiple of 2,3, and/or 7.
|
||||
# 2. since lcm(2,3,7)==42, the series of combinations will repeat
|
||||
# every 42 rounds.
|
||||
# 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
|
||||
# while odd rounds 1-41 consist of hash(round-specific-constant + dc)
|
||||
#
|
||||
# Using these observations, the following code...
|
||||
# * calculates the round-specific combination of salt & pwd for each round 0-41
|
||||
# * runs through as many 42-round blocks as possible (23)
|
||||
# * runs through as many pairs of rounds as needed for remaining rounds (17)
|
||||
# * this results in the required 42*23+2*17=1000 rounds required by md5_crypt.
|
||||
#
|
||||
# this cuts out a lot of the control overhead incurred when running the
|
||||
# original loop 1000 times in python, resulting in ~20% increase in
|
||||
# speed under CPython (though still 2x slower than glibc crypt)
|
||||
|
||||
# prepare the 6 combinations of pwd & salt which are needed
|
||||
# (order of 'perms' must match how _c_digest_offsets was generated)
|
||||
pwd_pwd = pwd+pwd
|
||||
pwd_salt = pwd+salt
|
||||
perms = [pwd, pwd_pwd, pwd_salt, pwd_salt+pwd, salt+pwd, salt+pwd_pwd]
|
||||
|
||||
# build up list of even-round & odd-round constants,
|
||||
# and store in 21-element list as (even,odd) pairs.
|
||||
data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
|
||||
|
||||
# perform 23 blocks of 42 rounds each (for a total of 966 rounds)
|
||||
dc = da
|
||||
blocks = 23
|
||||
while blocks:
|
||||
for even, odd in data:
|
||||
dc = md5(odd + md5(dc + even).digest()).digest()
|
||||
blocks -= 1
|
||||
|
||||
# perform 17 more pairs of rounds (34 more rounds, for a total of 1000)
|
||||
for even, odd in data[:17]:
|
||||
dc = md5(odd + md5(dc + even).digest()).digest()
|
||||
|
||||
#===================================================================
|
||||
# encode digest using appropriate transpose map
|
||||
#===================================================================
|
||||
return h64.encode_transposed_bytes(dc, _transpose_map).decode("ascii")
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class _MD5_Common(uh.HasSalt, uh.GenericHandler):
|
||||
"""common code for md5_crypt and apr_md5_crypt"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
# name - set in subclass
|
||||
setting_kwds = ("salt", "salt_size")
|
||||
# ident - set in subclass
|
||||
checksum_size = 22
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
max_salt_size = 8
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
return uh.render_mc2(self.ident, self.salt, self.checksum)
|
||||
|
||||
# _calc_checksum() - provided by subclass
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class md5_crypt(uh.HasManyBackends, _MD5_Common):
|
||||
"""This class implements the MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of characters to use when autogenerating new salts.
|
||||
Defaults to 8, but can be any value between 0 and 8.
|
||||
(This is mainly needed when generating Cisco-compatible hashes,
|
||||
which require ``salt_size=4``).
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "md5_crypt"
|
||||
ident = u("$1$")
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
# FIXME: can't find definitive policy on how md5-crypt handles non-ascii.
|
||||
# all backends currently coerce -> utf-8
|
||||
|
||||
backends = ("os_crypt", "builtin")
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# os_crypt backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_os_crypt(cls):
|
||||
if test_crypt("test", '$1$test$pi/xDtU5WFVRqYS6BMU8X/'):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_os_crypt(self, secret):
|
||||
config = self.ident + self.salt
|
||||
hash = safe_crypt(secret, config)
|
||||
if hash is None:
|
||||
# py3's crypt.crypt() can't handle non-utf8 bytes.
|
||||
# fallback to builtin alg, which is always available.
|
||||
return self._calc_checksum_builtin(secret)
|
||||
if not hash.startswith(config) or len(hash) != len(config) + 23:
|
||||
raise uh.exc.CryptBackendError(self, config, hash)
|
||||
return hash[-22:]
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# builtin backend
|
||||
#---------------------------------------------------------------
|
||||
@classmethod
|
||||
def _load_backend_builtin(cls):
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
|
||||
return True
|
||||
|
||||
def _calc_checksum_builtin(self, secret):
|
||||
return _raw_md5_crypt(secret, self.salt)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class apr_md5_crypt(_MD5_Common):
|
||||
"""This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "apr_md5_crypt"
|
||||
ident = u("$apr1$")
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
return _raw_md5_crypt(secret, self.salt, use_apr=True)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
269
backend/venv/Lib/site-packages/passlib/handlers/misc.py
Normal file
269
backend/venv/Lib/site-packages/passlib/handlers/misc.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""passlib.handlers.misc - misc generic handlers
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import sys
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str, str_consteq
|
||||
from passlib.utils.compat import unicode, u, unicode_or_bytes_types
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"unix_disabled",
|
||||
"unix_fallback",
|
||||
"plaintext",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class unix_fallback(uh.ifc.DisabledHash, uh.StaticHandler):
|
||||
"""This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class does not implement a hash, but instead provides fallback
|
||||
behavior as found in /etc/shadow on most unix variants.
|
||||
If used, should be the last scheme in the context.
|
||||
|
||||
* this class will positively identify all hash strings.
|
||||
* for security, passwords will always hash to ``!``.
|
||||
* it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used).
|
||||
* by default it rejects all passwords if the hash is an empty string,
|
||||
but if ``enable_wildcard=True`` is passed to verify(),
|
||||
all passwords will be allowed through if the hash is an empty string.
|
||||
|
||||
.. deprecated:: 1.6
|
||||
This has been deprecated due to its "wildcard" feature,
|
||||
and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
|
||||
"""
|
||||
name = "unix_fallback"
|
||||
context_kwds = ("enable_wildcard",)
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
if isinstance(hash, unicode_or_bytes_types):
|
||||
return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
|
||||
def __init__(self, enable_wildcard=False, **kwds):
|
||||
warn("'unix_fallback' is deprecated, "
|
||||
"and will be removed in Passlib 1.8; "
|
||||
"please use 'unix_disabled' instead.",
|
||||
DeprecationWarning)
|
||||
super(unix_fallback, self).__init__(**kwds)
|
||||
self.enable_wildcard = enable_wildcard
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if self.checksum:
|
||||
# NOTE: hash will generally be "!", but we want to preserve
|
||||
# it in case it's something else, like "*".
|
||||
return self.checksum
|
||||
else:
|
||||
return u("!")
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, enable_wildcard=False):
|
||||
uh.validate_secret(secret)
|
||||
if not isinstance(hash, unicode_or_bytes_types):
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
elif hash:
|
||||
return False
|
||||
else:
|
||||
return enable_wildcard
|
||||
|
||||
_MARKER_CHARS = u("*!")
|
||||
_MARKER_BYTES = b"*!"
|
||||
|
||||
class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler):
|
||||
"""This class provides disabled password behavior for unix shadow files,
|
||||
and follows the :ref:`password-hash-api`.
|
||||
|
||||
This class does not implement a hash, but instead matches the "disabled account"
|
||||
strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
|
||||
will simply return the disabled account marker. It will reject all passwords,
|
||||
no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.hash`
|
||||
method supports one optional keyword:
|
||||
|
||||
:type marker: str
|
||||
:param marker:
|
||||
Optional marker string which overrides the platform default
|
||||
used to indicate a disabled account.
|
||||
|
||||
If not specified, this will default to ``"*"`` on BSD systems,
|
||||
and use the Linux default ``"!"`` for all other platforms.
|
||||
(:attr:`!unix_disabled.default_marker` will contain the default value)
|
||||
|
||||
.. versionadded:: 1.6
|
||||
This class was added as a replacement for the now-deprecated
|
||||
:class:`unix_fallback` class, which had some undesirable features.
|
||||
"""
|
||||
name = "unix_disabled"
|
||||
setting_kwds = ("marker",)
|
||||
context_kwds = ()
|
||||
|
||||
_disable_prefixes = tuple(str(_MARKER_CHARS))
|
||||
|
||||
# TODO: rename attr to 'marker'...
|
||||
if 'bsd' in sys.platform: # pragma: no cover -- runtime detection
|
||||
default_marker = u("*")
|
||||
else:
|
||||
# use the linux default for other systems
|
||||
# (glibc also supports adding old hash after the marker
|
||||
# so it can be restored later).
|
||||
default_marker = u("!")
|
||||
|
||||
@classmethod
|
||||
def using(cls, marker=None, **kwds):
|
||||
subcls = super(unix_disabled, cls).using(**kwds)
|
||||
if marker is not None:
|
||||
if not cls.identify(marker):
|
||||
raise ValueError("invalid marker: %r" % marker)
|
||||
subcls.default_marker = marker
|
||||
return subcls
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
# NOTE: technically, anything in the /etc/shadow password field
|
||||
# which isn't valid crypt() output counts as "disabled".
|
||||
# but that's rather ambiguous, and it's hard to predict what
|
||||
# valid output is for unknown crypt() implementations.
|
||||
# so to be on the safe side, we only match things *known*
|
||||
# to be disabled field indicators, and will add others
|
||||
# as they are found. things beginning w/ "$" should *never* match.
|
||||
#
|
||||
# things currently matched:
|
||||
# * linux uses "!"
|
||||
# * bsd uses "*"
|
||||
# * linux may use "!" + hash to disable but preserve original hash
|
||||
# * linux counts empty string as "any password";
|
||||
# this code recognizes it, but treats it the same as "!"
|
||||
if isinstance(hash, unicode):
|
||||
start = _MARKER_CHARS
|
||||
elif isinstance(hash, bytes):
|
||||
start = _MARKER_BYTES
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
return not hash or hash[0] in start
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
uh.validate_secret(secret)
|
||||
if not cls.identify(hash): # handles typecheck
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, **kwds):
|
||||
if kwds:
|
||||
uh.warn_hash_settings_deprecation(cls, kwds)
|
||||
return cls.using(**kwds).hash(secret)
|
||||
uh.validate_secret(secret)
|
||||
marker = cls.default_marker
|
||||
assert marker and cls.identify(marker)
|
||||
return to_native_str(marker, param="marker")
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, marker=None):
|
||||
if not cls.identify(config):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
elif config:
|
||||
# preserve the existing str,since it might contain a disabled password hash ("!" + hash)
|
||||
uh.validate_secret(secret)
|
||||
return to_native_str(config, param="config")
|
||||
else:
|
||||
if marker is not None:
|
||||
cls = cls.using(marker=marker)
|
||||
return cls.hash(secret)
|
||||
|
||||
@classmethod
|
||||
def disable(cls, hash=None):
|
||||
out = cls.hash("")
|
||||
if hash is not None:
|
||||
hash = to_native_str(hash, param="hash")
|
||||
if cls.identify(hash):
|
||||
# extract original hash, so that we normalize marker
|
||||
hash = cls.enable(hash)
|
||||
if hash:
|
||||
out += hash
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def enable(cls, hash):
|
||||
hash = to_native_str(hash, param="hash")
|
||||
for prefix in cls._disable_prefixes:
|
||||
if hash.startswith(prefix):
|
||||
orig = hash[len(prefix):]
|
||||
if orig:
|
||||
return orig
|
||||
else:
|
||||
raise ValueError("cannot restore original hash")
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
class plaintext(uh.MinimalHandler):
|
||||
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keyword:
|
||||
|
||||
:type encoding: str
|
||||
:param encoding:
|
||||
This controls the character encoding to use (defaults to ``utf-8``).
|
||||
|
||||
This encoding will be used to encode :class:`!unicode` passwords
|
||||
under Python 2, and decode :class:`!bytes` hashes under Python 3.
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
The ``encoding`` keyword was added.
|
||||
"""
|
||||
# NOTE: this is subclassed by ldap_plaintext
|
||||
|
||||
name = "plaintext"
|
||||
setting_kwds = ()
|
||||
context_kwds = ("encoding",)
|
||||
default_encoding = "utf-8"
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
if isinstance(hash, unicode_or_bytes_types):
|
||||
return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
|
||||
@classmethod
|
||||
def hash(cls, secret, encoding=None):
|
||||
uh.validate_secret(secret)
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
return to_native_str(secret, encoding, "secret")
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, encoding=None):
|
||||
if not encoding:
|
||||
encoding = cls.default_encoding
|
||||
hash = to_native_str(hash, encoding, "hash")
|
||||
if not cls.identify(hash):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return str_consteq(cls.hash(secret, encoding), hash)
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls):
|
||||
return cls.hash("")
|
||||
|
||||
@uh.deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, encoding=None):
|
||||
# NOTE: 'config' is ignored, as this hash has no salting / etc
|
||||
if not cls.identify(config):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
return cls.hash(secret, encoding=encoding)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
244
backend/venv/Lib/site-packages/passlib/handlers/mssql.py
Normal file
244
backend/venv/Lib/site-packages/passlib/handlers/mssql.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""passlib.handlers.mssql - MS-SQL Password Hash
|
||||
|
||||
Notes
|
||||
=====
|
||||
MS-SQL has used a number of hash algs over the years,
|
||||
most of which were exposed through the undocumented
|
||||
'pwdencrypt' and 'pwdcompare' sql functions.
|
||||
|
||||
Known formats
|
||||
-------------
|
||||
6.5
|
||||
snefru hash, ascii encoded password
|
||||
no examples found
|
||||
|
||||
7.0
|
||||
snefru hash, unicode (what encoding?)
|
||||
saw ref that these blobs were 16 bytes in size
|
||||
no examples found
|
||||
|
||||
2000
|
||||
byte string using displayed as 0x hex, using 0x0100 prefix.
|
||||
contains hashes of password and upper-case password.
|
||||
|
||||
2007
|
||||
same as 2000, but without the upper-case hash.
|
||||
|
||||
refs
|
||||
----------
|
||||
https://blogs.msdn.com/b/lcris/archive/2007/04/30/sql-server-2005-about-login-password-hashes.aspx?Redirected=true
|
||||
http://us.generation-nt.com/securing-passwords-hash-help-35429432.html
|
||||
http://forum.md5decrypter.co.uk/topic230-mysql-and-mssql-get-password-hashes.aspx
|
||||
http://www.theregister.co.uk/2002/07/08/cracking_ms_sql_server_passwords/
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import consteq
|
||||
from passlib.utils.compat import bascii_to_str, unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"mssql2000",
|
||||
"mssql2005",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# mssql 2000
|
||||
#=============================================================================
|
||||
def _raw_mssql(secret, salt):
|
||||
assert isinstance(secret, unicode)
|
||||
assert isinstance(salt, bytes)
|
||||
return sha1(secret.encode("utf-16-le") + salt).digest()
|
||||
|
||||
BIDENT = b"0x0100"
|
||||
##BIDENT2 = b("\x01\x00")
|
||||
UIDENT = u("0x0100")
|
||||
|
||||
def _ident_mssql(hash, csize, bsize):
|
||||
"""common identify for mssql 2000/2005"""
|
||||
if isinstance(hash, unicode):
|
||||
if len(hash) == csize and hash.startswith(UIDENT):
|
||||
return True
|
||||
elif isinstance(hash, bytes):
|
||||
if len(hash) == csize and hash.startswith(BIDENT):
|
||||
return True
|
||||
##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
|
||||
## return True
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
return False
|
||||
|
||||
def _parse_mssql(hash, csize, bsize, handler):
|
||||
"""common parser for mssql 2000/2005; returns 4 byte salt + checksum"""
|
||||
if isinstance(hash, unicode):
|
||||
if len(hash) == csize and hash.startswith(UIDENT):
|
||||
try:
|
||||
return unhexlify(hash[6:].encode("utf-8"))
|
||||
except TypeError: # throw when bad char found
|
||||
pass
|
||||
elif isinstance(hash, bytes):
|
||||
# assumes ascii-compat encoding
|
||||
assert isinstance(hash, bytes)
|
||||
if len(hash) == csize and hash.startswith(BIDENT):
|
||||
try:
|
||||
return unhexlify(hash[6:])
|
||||
except TypeError: # throw when bad char found
|
||||
pass
|
||||
##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
|
||||
## return hash[2:]
|
||||
else:
|
||||
raise uh.exc.ExpectedStringError(hash, "hash")
|
||||
raise uh.exc.InvalidHashError(handler)
|
||||
|
||||
class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 4 bytes in length.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "mssql2000"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_size = 40
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# 0100 - 2 byte identifier
|
||||
# 4 byte salt
|
||||
# 20 byte checksum
|
||||
# 20 byte checksum
|
||||
# = 46 bytes
|
||||
# encoded '0x' + 92 chars = 94
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
return _ident_mssql(hash, 94, 46)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
data = _parse_mssql(hash, 94, 46, cls)
|
||||
return cls(salt=data[:4], checksum=data[4:])
|
||||
|
||||
def to_string(self):
|
||||
raw = self.salt + self.checksum
|
||||
# raw bytes format - BIDENT2 + raw
|
||||
return "0x0100" + bascii_to_str(hexlify(raw).upper())
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
salt = self.salt
|
||||
return _raw_mssql(secret, salt) + _raw_mssql(secret.upper(), salt)
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
# NOTE: we only compare against the upper-case hash
|
||||
# XXX: add 'full' just to verify both checksums?
|
||||
uh.validate_secret(secret)
|
||||
self = cls.from_string(hash)
|
||||
chk = self.checksum
|
||||
if chk is None:
|
||||
raise uh.exc.MissingDigestError(cls)
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
result = _raw_mssql(secret.upper(), self.salt)
|
||||
return consteq(result, chk[20:])
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 4 bytes in length.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "mssql2005"
|
||||
setting_kwds = ("salt",)
|
||||
|
||||
checksum_size = 20
|
||||
min_salt_size = max_salt_size = 4
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# 0x0100 - 2 byte identifier
|
||||
# 4 byte salt
|
||||
# 20 byte checksum
|
||||
# = 26 bytes
|
||||
# encoded '0x' + 52 chars = 54
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
return _ident_mssql(hash, 54, 26)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
data = _parse_mssql(hash, 54, 26, cls)
|
||||
return cls(salt=data[:4], checksum=data[4:])
|
||||
|
||||
def to_string(self):
|
||||
raw = self.salt + self.checksum
|
||||
# raw bytes format - BIDENT2 + raw
|
||||
return "0x0100" + bascii_to_str(hexlify(raw)).upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
return _raw_mssql(secret, self.salt)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
128
backend/venv/Lib/site-packages/passlib/handlers/mysql.py
Normal file
128
backend/venv/Lib/site-packages/passlib/handlers/mysql.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""passlib.handlers.mysql
|
||||
|
||||
MySQL 3.2.3 / OLD_PASSWORD()
|
||||
|
||||
This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1.
|
||||
|
||||
See :mod:`passlib.handlers.mysql_41` for the new algorithm was put in place in version 4.1
|
||||
|
||||
This algorithm is known to be very insecure, and should only be used to verify existing password hashes.
|
||||
|
||||
http://djangosnippets.org/snippets/1508/
|
||||
|
||||
MySQL 4.1.1 / NEW PASSWORD
|
||||
This implements Mysql new PASSWORD algorithm, introduced in version 4.1.
|
||||
|
||||
This function is unsalted, and therefore not very secure against rainbow attacks.
|
||||
It should only be used when dealing with mysql passwords,
|
||||
for all other purposes, you should use a salted hash function.
|
||||
|
||||
Description taken from http://dev.mysql.com/doc/refman/6.0/en/password-hashing.html
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_native_str
|
||||
from passlib.utils.compat import bascii_to_str, unicode, u, \
|
||||
byte_elem_value, str_to_uascii
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
'mysql323',
|
||||
'mysq41',
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# backend
|
||||
#=============================================================================
|
||||
class mysql323(uh.StaticHandler):
|
||||
"""This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "mysql323"
|
||||
checksum_size = 16
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.lower()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: no idea if mysql has a policy about handling unicode passwords
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
|
||||
MASK_32 = 0xffffffff
|
||||
MASK_31 = 0x7fffffff
|
||||
WHITE = b' \t'
|
||||
|
||||
nr1 = 0x50305735
|
||||
nr2 = 0x12345671
|
||||
add = 7
|
||||
for c in secret:
|
||||
if c in WHITE:
|
||||
continue
|
||||
tmp = byte_elem_value(c)
|
||||
nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32
|
||||
nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32
|
||||
add = (add+tmp) & MASK_32
|
||||
return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class mysql41(uh.StaticHandler):
|
||||
"""This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It has no salt and a single fixed round.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
name = "mysql41"
|
||||
_hash_prefix = u("*")
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 40
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: no idea if mysql has a policy about handling unicode passwords
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
172
backend/venv/Lib/site-packages/passlib/handlers/oracle.py
Normal file
172
backend/venv/Lib/site-packages/passlib/handlers/oracle.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""passlib.handlers.oracle - Oracle DB Password Hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import sha1
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode, xor_bytes
|
||||
from passlib.utils.compat import irange, u, \
|
||||
uascii_to_str, unicode, str_to_uascii
|
||||
from passlib.crypto.des import des_encrypt_block
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"oracle10g",
|
||||
"oracle11g"
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# oracle10
|
||||
#=============================================================================
|
||||
def des_cbc_encrypt(key, value, iv=b'\x00' * 8, pad=b'\x00'):
|
||||
"""performs des-cbc encryption, returns only last block.
|
||||
|
||||
this performs a specific DES-CBC encryption implementation
|
||||
as needed by the Oracle10 hash. it probably won't be useful for
|
||||
other purposes as-is.
|
||||
|
||||
input value is null-padded to multiple of 8 bytes.
|
||||
|
||||
:arg key: des key as bytes
|
||||
:arg value: value to encrypt, as bytes.
|
||||
:param iv: optional IV
|
||||
:param pad: optional pad byte
|
||||
|
||||
:returns: last block of DES-CBC encryption of all ``value``'s byte blocks.
|
||||
"""
|
||||
value += pad * (-len(value) % 8) # null pad to multiple of 8
|
||||
hash = iv # start things off
|
||||
for offset in irange(0,len(value),8):
|
||||
chunk = xor_bytes(hash, value[offset:offset+8])
|
||||
hash = des_encrypt_block(key, chunk)
|
||||
return hash
|
||||
|
||||
# magic string used as initial des key by oracle10
|
||||
ORACLE10_MAGIC = b"\x01\x23\x45\x67\x89\xAB\xCD\xEF"
|
||||
|
||||
class oracle10(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements the password hash used by Oracle up to version 10g, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It does a single round of hashing, and relies on the username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keywords:
|
||||
|
||||
:type user: str
|
||||
:param user: name of oracle user account this password is associated with.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "oracle10"
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 16
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def _norm_hash(cls, hash):
|
||||
return hash.upper()
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: not sure how oracle handles unicode.
|
||||
# online docs about 10g hash indicate it puts ascii chars
|
||||
# in a 2-byte encoding w/ the high byte set to null.
|
||||
# they don't say how it handles other chars, or what encoding.
|
||||
#
|
||||
# so for now, encoding secret & user to utf-16-be,
|
||||
# since that fits, and if secret/user is bytes,
|
||||
# we assume utf-8, and decode first.
|
||||
#
|
||||
# this whole mess really needs someone w/ an oracle system,
|
||||
# and some answers :)
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
user = to_unicode(self.user, "utf-8", param="user")
|
||||
input = (user+secret).upper().encode("utf-16-be")
|
||||
hash = des_cbc_encrypt(ORACLE10_MAGIC, input)
|
||||
hash = des_cbc_encrypt(hash, input)
|
||||
return hexlify(hash).decode("ascii").upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# oracle11
|
||||
#=============================================================================
|
||||
class oracle11(uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the Oracle11g password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 20 hexadecimal characters.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "oracle11"
|
||||
setting_kwds = ("salt",)
|
||||
checksum_size = 40
|
||||
checksum_chars = uh.UPPER_HEX_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 20
|
||||
salt_chars = uh.UPPER_HEX_CHARS
|
||||
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
_hash_regex = re.compile(u("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$"), re.I)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
m = cls._hash_regex.match(hash)
|
||||
if not m:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
salt, chk = m.group("salt", "chk")
|
||||
return cls(salt=salt, checksum=chk.upper())
|
||||
|
||||
def to_string(self):
|
||||
chk = self.checksum
|
||||
hash = u("S:%s%s") % (chk.upper(), self.salt.upper())
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest()
|
||||
return str_to_uascii(chk).upper()
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
475
backend/venv/Lib/site-packages/passlib/handlers/pbkdf2.py
Normal file
475
backend/venv/Lib/site-packages/passlib/handlers/pbkdf2.py
Normal file
@@ -0,0 +1,475 @@
|
||||
"""passlib.handlers.pbkdf - PBKDF2 based hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify, unhexlify
|
||||
from base64 import b64encode, b64decode
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_unicode
|
||||
from passlib.utils.binary import ab64_decode, ab64_encode
|
||||
from passlib.utils.compat import str_to_bascii, u, uascii_to_str, unicode
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"pbkdf2_sha1",
|
||||
"pbkdf2_sha256",
|
||||
"pbkdf2_sha512",
|
||||
"cta_pbkdf2_sha1",
|
||||
"dlitz_pbkdf2_sha1",
|
||||
"grub_pbkdf2_sha512",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""base class for various pbkdf2_{digest} algorithms"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#--GenericHandler--
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = None # set by subclass
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--this class--
|
||||
_digest = None # name of subclass-specified hash
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check.
|
||||
# the underlying pbkdf2 specifies no bounds for either.
|
||||
|
||||
# NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends...
|
||||
# >8 bytes of entropy in salt, >1000 rounds
|
||||
# increased due to time since rfc established
|
||||
|
||||
#===================================================================
|
||||
# methods
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
|
||||
salt = ab64_decode(salt.encode("ascii"))
|
||||
if chk:
|
||||
chk = ab64_decode(chk.encode("ascii"))
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = ab64_encode(self.salt).decode("ascii")
|
||||
chk = ab64_encode(self.checksum).decode("ascii")
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using UTF8
|
||||
return pbkdf2_hmac(self._digest, secret, self.salt, self.rounds, self.checksum_size)
|
||||
|
||||
def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
|
||||
"""create new Pbkdf2DigestHandler subclass for a specific hash"""
|
||||
name = 'pbkdf2_' + hash_name
|
||||
if ident is None:
|
||||
ident = u("$pbkdf2-%s$") % (hash_name,)
|
||||
base = Pbkdf2DigestHandler
|
||||
return type(name, (base,), dict(
|
||||
__module__=module, # so ABCMeta won't clobber it.
|
||||
name=name,
|
||||
ident=ident,
|
||||
_digest = hash_name,
|
||||
default_rounds=rounds,
|
||||
checksum_size=digest_size,
|
||||
encoded_checksum_size=(digest_size*4+2)//3,
|
||||
__doc__="""This class implements a generic ``PBKDF2-HMAC-%(digest)s``-based password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to %(dsc)d bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to %(dr)d, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
""" % dict(digest=hash_name.upper(), dsc=base.default_salt_size, dr=rounds)
|
||||
))
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# derived handlers
|
||||
#------------------------------------------------------------------------
|
||||
pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$"))
|
||||
pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000)
|
||||
pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000)
|
||||
|
||||
ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
|
||||
ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
|
||||
ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True)
|
||||
|
||||
#=============================================================================
|
||||
# cryptacular's pbkdf2 hash
|
||||
#=============================================================================
|
||||
|
||||
# bytes used by cta hash for base64 values 63 & 64
|
||||
CTA_ALTCHARS = b"-_"
|
||||
|
||||
class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements Cryptacular's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, it may be any length.
|
||||
If not specified, a one will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 60000, must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "cta_pbkdf2_sha1"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
ident = u("$p5k2$")
|
||||
checksum_size = 20
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. underlying algorithm (and reference implementation)
|
||||
# allows effectively unbounded values for both of these parameters.
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = pbkdf2_sha1.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# hash $p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0=
|
||||
# ident $p5k2$
|
||||
# rounds 1000
|
||||
# salt ZxK4ZBJCfQg=
|
||||
# chk jJZVscWtO--p1-xIZl6jhO2LKR0=
|
||||
# NOTE: rounds in hex
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
# NOTE: passlib deviation - forbidding zero-padded rounds
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16, handler=cls)
|
||||
salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS)
|
||||
if chk:
|
||||
chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS)
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = b64encode(self.salt, CTA_ALTCHARS).decode("ascii")
|
||||
chk = b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk, rounds_base=16)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha1", secret, self.salt, self.rounds, 20)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# dlitz's pbkdf2 hash
|
||||
#=============================================================================
|
||||
class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If specified, it may be any length, but must use the characters in the regexp range ``[./0-9A-Za-z]``.
|
||||
If not specified, a 16 character salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 60000, must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "dlitz_pbkdf2_sha1"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
ident = u("$p5k2$")
|
||||
_stub_checksum = u("0" * 48 + "=")
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. underlying algorithm (and reference implementation)
|
||||
# allows effectively unbounded values for both of these parameters.
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
# NOTE: for security, the default here is set to match pbkdf2_sha1,
|
||||
# even though this hash's extra block makes it twice as slow.
|
||||
default_rounds = pbkdf2_sha1.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
# hash $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
|
||||
# ident $p5k2$
|
||||
# rounds c
|
||||
# salt u9HvcT4d
|
||||
# chk Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
|
||||
# rounds in lowercase hex, no zero padding
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16,
|
||||
default_rounds=400, handler=cls)
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
rounds = self.rounds
|
||||
if rounds == 400:
|
||||
rounds = None # omit rounds measurement if == 400
|
||||
return uh.render_mc3(self.ident, rounds, self.salt, self.checksum, rounds_base=16)
|
||||
|
||||
def _get_config(self):
|
||||
rounds = self.rounds
|
||||
if rounds == 400:
|
||||
rounds = None # omit rounds measurement if == 400
|
||||
return uh.render_mc3(self.ident, rounds, self.salt, None, rounds_base=16)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
salt = self._get_config()
|
||||
result = pbkdf2_hmac("sha1", secret, salt, self.rounds, 24)
|
||||
return ab64_encode(result).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# crowd
|
||||
#=============================================================================
|
||||
class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements the PBKDF2 hash used by Atlassian.
|
||||
|
||||
It supports a fixed-length salt, and a fixed number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be exactly 16 bytes.
|
||||
If not specified, a salt will be autogenerated (this is recommended).
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include
|
||||
``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
#--GenericHandler--
|
||||
name = "atlassian_pbkdf2_sha1"
|
||||
setting_kwds =("salt",)
|
||||
ident = u("{PKCS5S2}")
|
||||
checksum_size = 32
|
||||
|
||||
#--HasRawSalt--
|
||||
min_salt_size = max_salt_size = 16
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_unicode(hash, "ascii", "hash")
|
||||
ident = cls.ident
|
||||
if not hash.startswith(ident):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
data = b64decode(hash[len(ident):].encode("ascii"))
|
||||
salt, chk = data[:16], data[16:]
|
||||
return cls(salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
data = self.salt + self.checksum
|
||||
hash = self.ident + b64encode(data).decode("ascii")
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# TODO: find out what crowd's policy is re: unicode
|
||||
# crowd seems to use a fixed number of rounds.
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha1", secret, self.salt, 10000, 32)
|
||||
|
||||
#=============================================================================
|
||||
# grub
|
||||
#=============================================================================
|
||||
class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class implements Grub's pbkdf2-hmac-sha512 hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a 64 byte salt will be autogenerated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 64 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 19000, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
name = "grub_pbkdf2_sha512"
|
||||
setting_kwds = ("salt", "salt_size", "rounds")
|
||||
|
||||
ident = u("grub.pbkdf2.sha512.")
|
||||
checksum_size = 64
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
|
||||
# sanity check. the underlying pbkdf2 specifies no bounds for either,
|
||||
# and it's not clear what grub specifies.
|
||||
|
||||
default_salt_size = 64
|
||||
max_salt_size = 1024
|
||||
|
||||
default_rounds = pbkdf2_sha512.default_rounds
|
||||
min_rounds = 1
|
||||
max_rounds = 0xffffffff # setting at 32-bit limit for now
|
||||
rounds_cost = "linear"
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."),
|
||||
handler=cls)
|
||||
salt = unhexlify(salt.encode("ascii"))
|
||||
if chk:
|
||||
chk = unhexlify(chk.encode("ascii"))
|
||||
return cls(rounds=rounds, salt=salt, checksum=chk)
|
||||
|
||||
def to_string(self):
|
||||
salt = hexlify(self.salt).decode("ascii").upper()
|
||||
chk = hexlify(self.checksum).decode("ascii").upper()
|
||||
return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u("."))
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
# TODO: find out what grub's policy is re: unicode
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
|
||||
return pbkdf2_hmac("sha512", secret, self.salt, self.rounds, 64)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
135
backend/venv/Lib/site-packages/passlib/handlers/phpass.py
Normal file
135
backend/venv/Lib/site-packages/passlib/handlers/phpass.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""passlib.handlers.phpass - PHPass Portable Crypt
|
||||
|
||||
phppass located - http://www.openwall.com/phpass/
|
||||
algorithm described - http://www.openwall.com/articles/PHP-Users-Passwords
|
||||
|
||||
phpass context - blowfish, bsdi_crypt, phpass
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.binary import h64
|
||||
from passlib.utils.compat import u, uascii_to_str, unicode
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"phpass",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# phpass
|
||||
#=============================================================================
|
||||
class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
|
||||
"""This class implements the PHPass Portable Hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a fixed-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If not specified, one will be autogenerated (this is recommended).
|
||||
If specified, it must be 8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 19, must be between 7 and 30, inclusive.
|
||||
This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
|
||||
|
||||
:type ident: str
|
||||
:param ident:
|
||||
phpBB3 uses ``H`` instead of ``P`` for its identifier,
|
||||
this may be set to ``H`` in order to generate phpBB3 compatible hashes.
|
||||
it defaults to ``P``.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
#--GenericHandler--
|
||||
name = "phpass"
|
||||
setting_kwds = ("salt", "rounds", "ident")
|
||||
checksum_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasSalt--
|
||||
min_salt_size = max_salt_size = 8
|
||||
salt_chars = uh.HASH64_CHARS
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 19
|
||||
min_rounds = 7
|
||||
max_rounds = 30
|
||||
rounds_cost = "log2"
|
||||
|
||||
#--HasManyIdents--
|
||||
default_ident = u("$P$")
|
||||
ident_values = (u("$P$"), u("$H$"))
|
||||
ident_aliases = {u("P"):u("$P$"), u("H"):u("$H$")}
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
|
||||
#$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0
|
||||
# $P$
|
||||
# 9
|
||||
# IQRaTwmf
|
||||
# eRo7ud9Fh4E2PdI0S3r.L0
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
ident, data = cls._parse_ident(hash)
|
||||
rounds, salt, chk = data[0], data[1:9], data[9:]
|
||||
return cls(
|
||||
ident=ident,
|
||||
rounds=h64.decode_int6(rounds.encode("ascii")),
|
||||
salt=salt,
|
||||
checksum=chk or None,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
hash = u("%s%s%s%s") % (self.ident,
|
||||
h64.encode_int6(self.rounds).decode("ascii"),
|
||||
self.salt,
|
||||
self.checksum or u(''))
|
||||
return uascii_to_str(hash)
|
||||
|
||||
#===================================================================
|
||||
# backend
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
# FIXME: can't find definitive policy on how phpass handles non-ascii.
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
real_rounds = 1<<self.rounds
|
||||
result = md5(self.salt.encode("ascii") + secret).digest()
|
||||
r = 0
|
||||
while r < real_rounds:
|
||||
result = md5(result + secret).digest()
|
||||
r += 1
|
||||
return h64.encode_bytes(result).decode("ascii")
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
55
backend/venv/Lib/site-packages/passlib/handlers/postgres.py
Normal file
55
backend/venv/Lib/site-packages/passlib/handlers/postgres.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""passlib.handlers.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from hashlib import md5
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import to_bytes
|
||||
from passlib.utils.compat import str_to_uascii, unicode, u
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"postgres_md5",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class postgres_md5(uh.HasUserContext, uh.StaticHandler):
|
||||
"""This class implements the Postgres MD5 Password hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It does a single round of hashing, and relies on the username as the salt.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
|
||||
following additional contextual keywords:
|
||||
|
||||
:type user: str
|
||||
:param user: name of postgres user account this password is associated with.
|
||||
"""
|
||||
#===================================================================
|
||||
# algorithm information
|
||||
#===================================================================
|
||||
name = "postgres_md5"
|
||||
_hash_prefix = u("md5")
|
||||
checksum_chars = uh.HEX_CHARS
|
||||
checksum_size = 32
|
||||
|
||||
#===================================================================
|
||||
# primary interface
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
user = to_bytes(self.user, "utf-8", param="user")
|
||||
return str_to_uascii(md5(secret + user).hexdigest())
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
29
backend/venv/Lib/site-packages/passlib/handlers/roundup.py
Normal file
29
backend/venv/Lib/site-packages/passlib/handlers/roundup.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""passlib.handlers.roundup - Roundup issue tracker hashes"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.utils.compat import u
|
||||
# local
|
||||
__all__ = [
|
||||
"roundup_plaintext",
|
||||
"ldap_hex_md5",
|
||||
"ldap_hex_sha1",
|
||||
]
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
roundup_plaintext = uh.PrefixWrapper("roundup_plaintext", "plaintext",
|
||||
prefix=u("{plaintext}"), lazy=True)
|
||||
|
||||
# NOTE: these are here because they're currently only known to be used by roundup
|
||||
ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u("{MD5}"), lazy=True)
|
||||
ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u("{SHA}"), lazy=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
582
backend/venv/Lib/site-packages/passlib/handlers/scram.py
Normal file
582
backend/venv/Lib/site-packages/passlib/handlers/scram.py
Normal file
@@ -0,0 +1,582 @@
|
||||
"""passlib.handlers.scram - hash for SCRAM credential storage"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils import consteq, saslprep, to_native_str, splitcomma
|
||||
from passlib.utils.binary import ab64_decode, ab64_encode
|
||||
from passlib.utils.compat import bascii_to_str, iteritems, u, native_string_types
|
||||
from passlib.crypto.digest import pbkdf2_hmac, norm_hash_name
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"scram",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scram credentials hash
|
||||
#=============================================================================
|
||||
class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
|
||||
"""This class provides a format for storing SCRAM passwords, and follows
|
||||
the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, and a variable number of rounds.
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: bytes
|
||||
:param salt:
|
||||
Optional salt bytes.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, a 12 byte salt will be autogenerated
|
||||
(this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 12 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 100000, but must be within ``range(1,1<<32)``.
|
||||
|
||||
:type algs: list of strings
|
||||
:param algs:
|
||||
Specify list of digest algorithms to use.
|
||||
|
||||
By default each scram hash will contain digests for SHA-1,
|
||||
SHA-256, and SHA-512. This can be overridden by specify either be a
|
||||
list such as ``["sha-1", "sha-256"]``, or a comma-separated string
|
||||
such as ``"sha-1, sha-256"``. Names are case insensitive, and may
|
||||
use :mod:`!hashlib` or `IANA <http://www.iana.org/assignments/hash-function-text-names>`_
|
||||
hash names.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
In addition to the standard :ref:`password-hash-api` methods,
|
||||
this class also provides the following methods for manipulating Passlib
|
||||
scram hashes in ways useful for pluging into a SCRAM protocol stack:
|
||||
|
||||
.. automethod:: extract_digest_info
|
||||
.. automethod:: extract_digest_algs
|
||||
.. automethod:: derive_digest
|
||||
"""
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
# NOTE: unlike most GenericHandler classes, the 'checksum' attr of
|
||||
# ScramHandler is actually a map from digest_name -> digest, so
|
||||
# many of the standard methods have been overridden.
|
||||
|
||||
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide
|
||||
# a sanity check; the underlying pbkdf2 specifies no bounds for either.
|
||||
|
||||
#--GenericHandler--
|
||||
name = "scram"
|
||||
setting_kwds = ("salt", "salt_size", "rounds", "algs")
|
||||
ident = u("$scram$")
|
||||
|
||||
#--HasSalt--
|
||||
default_salt_size = 12
|
||||
max_salt_size = 1024
|
||||
|
||||
#--HasRounds--
|
||||
default_rounds = 100000
|
||||
min_rounds = 1
|
||||
max_rounds = 2**32-1
|
||||
rounds_cost = "linear"
|
||||
|
||||
#--custom--
|
||||
|
||||
# default algorithms when creating new hashes.
|
||||
default_algs = ["sha-1", "sha-256", "sha-512"]
|
||||
|
||||
# list of algs verify prefers to use, in order.
|
||||
_verify_algs = ["sha-256", "sha-512", "sha-224", "sha-384", "sha-1"]
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
|
||||
# 'checksum' is different from most GenericHandler subclasses,
|
||||
# in that it contains a dict mapping from alg -> digest,
|
||||
# or None if no checksum present.
|
||||
|
||||
# list of algorithms to create/compare digests for.
|
||||
algs = None
|
||||
|
||||
#===================================================================
|
||||
# scram frontend helpers
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def extract_digest_info(cls, hash, alg):
|
||||
"""return (salt, rounds, digest) for specific hash algorithm.
|
||||
|
||||
:type hash: str
|
||||
:arg hash:
|
||||
:class:`!scram` hash stored for desired user
|
||||
|
||||
:type alg: str
|
||||
:arg alg:
|
||||
Name of digest algorithm (e.g. ``"sha-1"``) requested by client.
|
||||
|
||||
This value is run through :func:`~passlib.crypto.digest.norm_hash_name`,
|
||||
so it is case-insensitive, and can be the raw SCRAM
|
||||
mechanism name (e.g. ``"SCRAM-SHA-1"``), the IANA name,
|
||||
or the hashlib name.
|
||||
|
||||
:raises KeyError:
|
||||
If the hash does not contain an entry for the requested digest
|
||||
algorithm.
|
||||
|
||||
:returns:
|
||||
A tuple containing ``(salt, rounds, digest)``,
|
||||
where *digest* matches the raw bytes returned by
|
||||
SCRAM's :func:`Hi` function for the stored password,
|
||||
the provided *salt*, and the iteration count (*rounds*).
|
||||
*salt* and *digest* are both raw (unencoded) bytes.
|
||||
"""
|
||||
# XXX: this could be sped up by writing custom parsing routine
|
||||
# that just picks out relevant digest, and doesn't bother
|
||||
# with full structure validation each time it's called.
|
||||
alg = norm_hash_name(alg, 'iana')
|
||||
self = cls.from_string(hash)
|
||||
chkmap = self.checksum
|
||||
if not chkmap:
|
||||
raise ValueError("scram hash contains no digests")
|
||||
return self.salt, self.rounds, chkmap[alg]
|
||||
|
||||
@classmethod
|
||||
def extract_digest_algs(cls, hash, format="iana"):
|
||||
"""Return names of all algorithms stored in a given hash.
|
||||
|
||||
:type hash: str
|
||||
:arg hash:
|
||||
The :class:`!scram` hash to parse
|
||||
|
||||
:type format: str
|
||||
:param format:
|
||||
This changes the naming convention used by the
|
||||
returned algorithm names. By default the names
|
||||
are IANA-compatible; possible values are ``"iana"`` or ``"hashlib"``.
|
||||
|
||||
:returns:
|
||||
Returns a list of digest algorithms; e.g. ``["sha-1"]``
|
||||
"""
|
||||
# XXX: this could be sped up by writing custom parsing routine
|
||||
# that just picks out relevant names, and doesn't bother
|
||||
# with full structure validation each time it's called.
|
||||
algs = cls.from_string(hash).algs
|
||||
if format == "iana":
|
||||
return algs
|
||||
else:
|
||||
return [norm_hash_name(alg, format) for alg in algs]
|
||||
|
||||
@classmethod
|
||||
def derive_digest(cls, password, salt, rounds, alg):
|
||||
"""helper to create SaltedPassword digest for SCRAM.
|
||||
|
||||
This performs the step in the SCRAM protocol described as::
|
||||
|
||||
SaltedPassword := Hi(Normalize(password), salt, i)
|
||||
|
||||
:type password: unicode or utf-8 bytes
|
||||
:arg password: password to run through digest
|
||||
|
||||
:type salt: bytes
|
||||
:arg salt: raw salt data
|
||||
|
||||
:type rounds: int
|
||||
:arg rounds: number of iterations.
|
||||
|
||||
:type alg: str
|
||||
:arg alg: name of digest to use (e.g. ``"sha-1"``).
|
||||
|
||||
:returns:
|
||||
raw bytes of ``SaltedPassword``
|
||||
"""
|
||||
if isinstance(password, bytes):
|
||||
password = password.decode("utf-8")
|
||||
# NOTE: pbkdf2_hmac() will encode secret & salt using utf-8,
|
||||
# and handle normalizing alg name.
|
||||
return pbkdf2_hmac(alg, saslprep(password), salt, rounds)
|
||||
|
||||
#===================================================================
|
||||
# serialization
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
hash = to_native_str(hash, "ascii", "hash")
|
||||
if not hash.startswith("$scram$"):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
parts = hash[7:].split("$")
|
||||
if len(parts) != 3:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
rounds_str, salt_str, chk_str = parts
|
||||
|
||||
# decode rounds
|
||||
rounds = int(rounds_str)
|
||||
if rounds_str != str(rounds): # forbid zero padding, etc.
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
|
||||
# decode salt
|
||||
try:
|
||||
salt = ab64_decode(salt_str.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
|
||||
# decode algs/digest list
|
||||
if not chk_str:
|
||||
# scram hashes MUST have something here.
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
elif "=" in chk_str:
|
||||
# comma-separated list of 'alg=digest' pairs
|
||||
algs = None
|
||||
chkmap = {}
|
||||
for pair in chk_str.split(","):
|
||||
alg, digest = pair.split("=")
|
||||
try:
|
||||
chkmap[alg] = ab64_decode(digest.encode("ascii"))
|
||||
except TypeError:
|
||||
raise uh.exc.MalformedHashError(cls)
|
||||
else:
|
||||
# comma-separated list of alg names, no digests
|
||||
algs = chk_str
|
||||
chkmap = None
|
||||
|
||||
# return new object
|
||||
return cls(
|
||||
rounds=rounds,
|
||||
salt=salt,
|
||||
checksum=chkmap,
|
||||
algs=algs,
|
||||
)
|
||||
|
||||
def to_string(self):
|
||||
salt = bascii_to_str(ab64_encode(self.salt))
|
||||
chkmap = self.checksum
|
||||
chk_str = ",".join(
|
||||
"%s=%s" % (alg, bascii_to_str(ab64_encode(chkmap[alg])))
|
||||
for alg in self.algs
|
||||
)
|
||||
return '$scram$%d$%s$%s' % (self.rounds, salt, chk_str)
|
||||
|
||||
#===================================================================
|
||||
# variant constructor
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def using(cls, default_algs=None, algs=None, **kwds):
|
||||
# parse aliases
|
||||
if algs is not None:
|
||||
assert default_algs is None
|
||||
default_algs = algs
|
||||
|
||||
# create subclass
|
||||
subcls = super(scram, cls).using(**kwds)
|
||||
|
||||
# fill in algs
|
||||
if default_algs is not None:
|
||||
subcls.default_algs = cls._norm_algs(default_algs)
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, algs=None, **kwds):
|
||||
super(scram, self).__init__(**kwds)
|
||||
|
||||
# init algs
|
||||
digest_map = self.checksum
|
||||
if algs is not None:
|
||||
if digest_map is not None:
|
||||
raise RuntimeError("checksum & algs kwds are mutually exclusive")
|
||||
algs = self._norm_algs(algs)
|
||||
elif digest_map is not None:
|
||||
# derive algs list from digest map (if present).
|
||||
algs = self._norm_algs(digest_map.keys())
|
||||
elif self.use_defaults:
|
||||
algs = list(self.default_algs)
|
||||
assert self._norm_algs(algs) == algs, "invalid default algs: %r" % (algs,)
|
||||
else:
|
||||
raise TypeError("no algs list specified")
|
||||
self.algs = algs
|
||||
|
||||
def _norm_checksum(self, checksum, relaxed=False):
|
||||
if not isinstance(checksum, dict):
|
||||
raise uh.exc.ExpectedTypeError(checksum, "dict", "checksum")
|
||||
for alg, digest in iteritems(checksum):
|
||||
if alg != norm_hash_name(alg, 'iana'):
|
||||
raise ValueError("malformed algorithm name in scram hash: %r" %
|
||||
(alg,))
|
||||
if len(alg) > 9:
|
||||
raise ValueError("SCRAM limits algorithm names to "
|
||||
"9 characters: %r" % (alg,))
|
||||
if not isinstance(digest, bytes):
|
||||
raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests")
|
||||
# TODO: verify digest size (if digest is known)
|
||||
if 'sha-1' not in checksum:
|
||||
# NOTE: required because of SCRAM spec.
|
||||
raise ValueError("sha-1 must be in algorithm list of scram hash")
|
||||
return checksum
|
||||
|
||||
@classmethod
|
||||
def _norm_algs(cls, algs):
|
||||
"""normalize algs parameter"""
|
||||
if isinstance(algs, native_string_types):
|
||||
algs = splitcomma(algs)
|
||||
algs = sorted(norm_hash_name(alg, 'iana') for alg in algs)
|
||||
if any(len(alg)>9 for alg in algs):
|
||||
raise ValueError("SCRAM limits alg names to max of 9 characters")
|
||||
if 'sha-1' not in algs:
|
||||
# NOTE: required because of SCRAM spec (rfc 5802)
|
||||
raise ValueError("sha-1 must be in algorithm list of scram hash")
|
||||
return algs
|
||||
|
||||
#===================================================================
|
||||
# migration
|
||||
#===================================================================
|
||||
def _calc_needs_update(self, **kwds):
|
||||
# marks hashes as deprecated if they don't include at least all default_algs.
|
||||
# XXX: should we deprecate if they aren't exactly the same,
|
||||
# to permit removing legacy hashes?
|
||||
if not set(self.algs).issuperset(self.default_algs):
|
||||
return True
|
||||
|
||||
# hand off to base implementation
|
||||
return super(scram, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# digest methods
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret, alg=None):
|
||||
rounds = self.rounds
|
||||
salt = self.salt
|
||||
hash = self.derive_digest
|
||||
if alg:
|
||||
# if requested, generate digest for specific alg
|
||||
return hash(secret, salt, rounds, alg)
|
||||
else:
|
||||
# by default, return dict containing digests for all algs
|
||||
return dict(
|
||||
(alg, hash(secret, salt, rounds, alg))
|
||||
for alg in self.algs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash, full=False):
|
||||
uh.validate_secret(secret)
|
||||
self = cls.from_string(hash)
|
||||
chkmap = self.checksum
|
||||
if not chkmap:
|
||||
raise ValueError("expected %s hash, got %s config string instead" %
|
||||
(cls.name, cls.name))
|
||||
|
||||
# NOTE: to make the verify method efficient, we just calculate hash
|
||||
# of shortest digest by default. apps can pass in "full=True" to
|
||||
# check entire hash for consistency.
|
||||
if full:
|
||||
correct = failed = False
|
||||
for alg, digest in iteritems(chkmap):
|
||||
other = self._calc_checksum(secret, alg)
|
||||
# NOTE: could do this length check in norm_algs(),
|
||||
# but don't need to be that strict, and want to be able
|
||||
# to parse hashes containing algs not supported by platform.
|
||||
# it's fine if we fail here though.
|
||||
if len(digest) != len(other):
|
||||
raise ValueError("mis-sized %s digest in scram hash: %r != %r"
|
||||
% (alg, len(digest), len(other)))
|
||||
if consteq(other, digest):
|
||||
correct = True
|
||||
else:
|
||||
failed = True
|
||||
if correct and failed:
|
||||
raise ValueError("scram hash verified inconsistently, "
|
||||
"may be corrupted")
|
||||
else:
|
||||
return correct
|
||||
else:
|
||||
# XXX: should this just always use sha1 hash? would be faster.
|
||||
# otherwise only verify against one hash, pick one w/ best security.
|
||||
for alg in self._verify_algs:
|
||||
if alg in chkmap:
|
||||
other = self._calc_checksum(secret, alg)
|
||||
return consteq(other, chkmap[alg])
|
||||
# there should always be sha-1 at the very least,
|
||||
# or something went wrong inside _norm_algs()
|
||||
raise AssertionError("sha-1 digest not found!")
|
||||
|
||||
#===================================================================
|
||||
#
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# code used for testing scram against protocol examples during development.
|
||||
#=============================================================================
|
||||
##def _test_reference_scram():
|
||||
## "quick hack testing scram reference vectors"
|
||||
## # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801
|
||||
## from passlib.utils.compat import print_
|
||||
##
|
||||
## engine = _scram_engine(
|
||||
## alg="sha-1",
|
||||
## salt='QSXCR+Q6sek8bf92'.decode("base64"),
|
||||
## rounds=4096,
|
||||
## password=u("pencil"),
|
||||
## )
|
||||
## print_(engine.digest.encode("base64").rstrip())
|
||||
##
|
||||
## msg = engine.format_auth_msg(
|
||||
## username="user",
|
||||
## client_nonce = "fyko+d2lbbFgONRv9qkxdawL",
|
||||
## server_nonce = "3rfcNHYJY1ZVvWVs7j",
|
||||
## header='c=biws',
|
||||
## )
|
||||
##
|
||||
## cp = engine.get_encoded_client_proof(msg)
|
||||
## assert cp == "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", cp
|
||||
##
|
||||
## ss = engine.get_encoded_server_sig(msg)
|
||||
## assert ss == "rmF9pqV8S7suAoZWja4dJRkFsKQ=", ss
|
||||
##
|
||||
##class _scram_engine(object):
|
||||
## """helper class for verifying scram hash behavior
|
||||
## against SCRAM protocol examples. not officially part of Passlib.
|
||||
##
|
||||
## takes in alg, salt, rounds, and a digest or password.
|
||||
##
|
||||
## can calculate the various keys & messages of the scram protocol.
|
||||
##
|
||||
## """
|
||||
## #=========================================================
|
||||
## # init
|
||||
## #=========================================================
|
||||
##
|
||||
## @classmethod
|
||||
## def from_string(cls, hash, alg):
|
||||
## "create record from scram hash, for given alg"
|
||||
## return cls(alg, *scram.extract_digest_info(hash, alg))
|
||||
##
|
||||
## def __init__(self, alg, salt, rounds, digest=None, password=None):
|
||||
## self.alg = norm_hash_name(alg)
|
||||
## self.salt = salt
|
||||
## self.rounds = rounds
|
||||
## self.password = password
|
||||
## if password:
|
||||
## data = scram.derive_digest(password, salt, rounds, alg)
|
||||
## if digest and data != digest:
|
||||
## raise ValueError("password doesn't match digest")
|
||||
## else:
|
||||
## digest = data
|
||||
## elif not digest:
|
||||
## raise TypeError("must provide password or digest")
|
||||
## self.digest = digest
|
||||
##
|
||||
## #=========================================================
|
||||
## # frontend methods
|
||||
## #=========================================================
|
||||
## def get_hash(self, data):
|
||||
## "return hash of raw data"
|
||||
## return hashlib.new(iana_to_hashlib(self.alg), data).digest()
|
||||
##
|
||||
## def get_client_proof(self, msg):
|
||||
## "return client proof of specified auth msg text"
|
||||
## return xor_bytes(self.client_key, self.get_client_sig(msg))
|
||||
##
|
||||
## def get_encoded_client_proof(self, msg):
|
||||
## return self.get_client_proof(msg).encode("base64").rstrip()
|
||||
##
|
||||
## def get_client_sig(self, msg):
|
||||
## "return client signature of specified auth msg text"
|
||||
## return self.get_hmac(self.stored_key, msg)
|
||||
##
|
||||
## def get_server_sig(self, msg):
|
||||
## "return server signature of specified auth msg text"
|
||||
## return self.get_hmac(self.server_key, msg)
|
||||
##
|
||||
## def get_encoded_server_sig(self, msg):
|
||||
## return self.get_server_sig(msg).encode("base64").rstrip()
|
||||
##
|
||||
## def format_server_response(self, client_nonce, server_nonce):
|
||||
## return 'r={client_nonce}{server_nonce},s={salt},i={rounds}'.format(
|
||||
## client_nonce=client_nonce,
|
||||
## server_nonce=server_nonce,
|
||||
## rounds=self.rounds,
|
||||
## salt=self.encoded_salt,
|
||||
## )
|
||||
##
|
||||
## def format_auth_msg(self, username, client_nonce, server_nonce,
|
||||
## header='c=biws'):
|
||||
## return (
|
||||
## 'n={username},r={client_nonce}'
|
||||
## ','
|
||||
## 'r={client_nonce}{server_nonce},s={salt},i={rounds}'
|
||||
## ','
|
||||
## '{header},r={client_nonce}{server_nonce}'
|
||||
## ).format(
|
||||
## username=username,
|
||||
## client_nonce=client_nonce,
|
||||
## server_nonce=server_nonce,
|
||||
## salt=self.encoded_salt,
|
||||
## rounds=self.rounds,
|
||||
## header=header,
|
||||
## )
|
||||
##
|
||||
## #=========================================================
|
||||
## # helpers to calculate & cache constant data
|
||||
## #=========================================================
|
||||
## def _calc_get_hmac(self):
|
||||
## return get_prf("hmac-" + iana_to_hashlib(self.alg))[0]
|
||||
##
|
||||
## def _calc_client_key(self):
|
||||
## return self.get_hmac(self.digest, b("Client Key"))
|
||||
##
|
||||
## def _calc_stored_key(self):
|
||||
## return self.get_hash(self.client_key)
|
||||
##
|
||||
## def _calc_server_key(self):
|
||||
## return self.get_hmac(self.digest, b("Server Key"))
|
||||
##
|
||||
## def _calc_encoded_salt(self):
|
||||
## return self.salt.encode("base64").rstrip()
|
||||
##
|
||||
## #=========================================================
|
||||
## # hacks for calculated attributes
|
||||
## #=========================================================
|
||||
##
|
||||
## def __getattr__(self, attr):
|
||||
## if not attr.startswith("_"):
|
||||
## f = getattr(self, "_calc_" + attr, None)
|
||||
## if f:
|
||||
## value = f()
|
||||
## setattr(self, attr, value)
|
||||
## return value
|
||||
## raise AttributeError("attribute not found")
|
||||
##
|
||||
## def __dir__(self):
|
||||
## cdir = dir(self.__class__)
|
||||
## attrs = set(cdir)
|
||||
## attrs.update(self.__dict__)
|
||||
## attrs.update(attr[6:] for attr in cdir
|
||||
## if attr.startswith("_calc_"))
|
||||
## return sorted(attrs)
|
||||
## #=========================================================
|
||||
## # eoc
|
||||
## #=========================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
383
backend/venv/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
383
backend/venv/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
@@ -0,0 +1,383 @@
|
||||
"""passlib.handlers.scrypt -- scrypt password hash"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, absolute_import
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.crypto import scrypt as _scrypt
|
||||
from passlib.utils import h64, to_bytes
|
||||
from passlib.utils.binary import h64, b64s_decode, b64s_encode
|
||||
from passlib.utils.compat import u, bascii_to_str, suppress_cause
|
||||
from passlib.utils.decor import classproperty
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"scrypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scrypt format identifiers
|
||||
#=============================================================================
|
||||
|
||||
IDENT_SCRYPT = u("$scrypt$") # identifier used by passlib
|
||||
IDENT_7 = u("$7$") # used by official scrypt spec
|
||||
|
||||
_UDOLLAR = u("$")
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.HasManyIdents,
|
||||
uh.GenericHandler):
|
||||
"""This class implements an SCrypt-based password [#scrypt-home]_ hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, a variable number of rounds,
|
||||
as well as some custom tuning parameters unique to scrypt (see below).
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, one will be auto-generated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 16, but must be within ``range(1,32)``.
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike many hash algorithms, increasing the rounds value
|
||||
will increase both the time *and memory* required to hash a password.
|
||||
|
||||
:type block_size: int
|
||||
:param block_size:
|
||||
Optional block size to pass to scrypt hash function (the ``r`` parameter).
|
||||
Useful for tuning scrypt to optimal performance for your CPU architecture.
|
||||
Defaults to 8.
|
||||
|
||||
:type parallelism: int
|
||||
:param parallelism:
|
||||
Optional parallelism to pass to scrypt hash function (the ``p`` parameter).
|
||||
Defaults to 1.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. note::
|
||||
|
||||
The underlying scrypt hash function has a number of limitations
|
||||
on it's parameter values, which forbids certain combinations of settings.
|
||||
The requirements are:
|
||||
|
||||
* ``linear_rounds = 2**<some positive integer>``
|
||||
* ``linear_rounds < 2**(16 * block_size)``
|
||||
* ``block_size * parallelism <= 2**30-1``
|
||||
|
||||
.. todo::
|
||||
|
||||
This class currently does not support configuring default values
|
||||
for ``block_size`` or ``parallelism`` via a :class:`~passlib.context.CryptContext`
|
||||
configuration.
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#------------------------
|
||||
# PasswordHash
|
||||
#------------------------
|
||||
name = "scrypt"
|
||||
setting_kwds = ("ident", "salt", "salt_size", "rounds", "block_size", "parallelism")
|
||||
|
||||
#------------------------
|
||||
# GenericHandler
|
||||
#------------------------
|
||||
# NOTE: scrypt supports arbitrary output sizes. since it's output runs through
|
||||
# pbkdf2-hmac-sha256 before returning, and this could be raised eventually...
|
||||
# but a 256-bit digest is more than sufficient for password hashing.
|
||||
# XXX: make checksum size configurable? could merge w/ argon2 code that does this.
|
||||
checksum_size = 32
|
||||
|
||||
#------------------------
|
||||
# HasManyIdents
|
||||
#------------------------
|
||||
default_ident = IDENT_SCRYPT
|
||||
ident_values = (IDENT_SCRYPT, IDENT_7)
|
||||
|
||||
#------------------------
|
||||
# HasRawSalt
|
||||
#------------------------
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#------------------------
|
||||
# HasRounds
|
||||
#------------------------
|
||||
# TODO: would like to dynamically pick this based on system
|
||||
default_rounds = 16
|
||||
min_rounds = 1
|
||||
max_rounds = 31 # limited by scrypt alg
|
||||
rounds_cost = "log2"
|
||||
|
||||
# TODO: make default block size configurable via using(), and deprecatable via .needs_update()
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
|
||||
#: default parallelism setting (min=1 currently hardcoded in mixin)
|
||||
parallelism = 1
|
||||
|
||||
#: default block size setting
|
||||
block_size = 8
|
||||
|
||||
#===================================================================
|
||||
# variant constructor
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def using(cls, block_size=None, **kwds):
|
||||
subcls = super(scrypt, cls).using(**kwds)
|
||||
if block_size is not None:
|
||||
if isinstance(block_size, uh.native_string_types):
|
||||
block_size = int(block_size)
|
||||
subcls.block_size = subcls._norm_block_size(block_size, relaxed=kwds.get("relaxed"))
|
||||
|
||||
# make sure param combination is valid for scrypt()
|
||||
try:
|
||||
_scrypt.validate(1 << cls.default_rounds, cls.block_size, cls.parallelism)
|
||||
except ValueError as err:
|
||||
raise suppress_cause(ValueError("scrypt: invalid settings combination: " + str(err)))
|
||||
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# parsing
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
return cls(**cls.parse(hash))
|
||||
|
||||
@classmethod
|
||||
def parse(cls, hash):
|
||||
ident, suffix = cls._parse_ident(hash)
|
||||
func = getattr(cls, "_parse_%s_string" % ident.strip(_UDOLLAR), None)
|
||||
if func:
|
||||
return func(suffix)
|
||||
else:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
#
|
||||
# passlib's format:
|
||||
# $scrypt$ln=<logN>,r=<r>,p=<p>$<salt>[$<digest>]
|
||||
# where:
|
||||
# logN, r, p -- decimal-encoded positive integer, no zero-padding
|
||||
# logN -- log cost setting
|
||||
# r -- block size setting (usually 8)
|
||||
# p -- parallelism setting (usually 1)
|
||||
# salt, digest -- b64-nopad encoded bytes
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_scrypt_string(cls, suffix):
|
||||
# break params, salt, and digest sections
|
||||
parts = suffix.split("$")
|
||||
if len(parts) == 3:
|
||||
params, salt, digest = parts
|
||||
elif len(parts) == 2:
|
||||
params, salt = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed hash")
|
||||
|
||||
# break params apart
|
||||
parts = params.split(",")
|
||||
if len(parts) == 3:
|
||||
nstr, bstr, pstr = parts
|
||||
assert nstr.startswith("ln=")
|
||||
assert bstr.startswith("r=")
|
||||
assert pstr.startswith("p=")
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed settings field")
|
||||
|
||||
return dict(
|
||||
ident=IDENT_SCRYPT,
|
||||
rounds=int(nstr[3:]),
|
||||
block_size=int(bstr[2:]),
|
||||
parallelism=int(pstr[2:]),
|
||||
salt=b64s_decode(salt.encode("ascii")),
|
||||
checksum=b64s_decode(digest.encode("ascii")) if digest else None,
|
||||
)
|
||||
|
||||
#
|
||||
# official format specification defined at
|
||||
# https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt
|
||||
# format:
|
||||
# $7$<N><rrrrr><ppppp><salt...>[$<digest>]
|
||||
# 0 12345 67890 1
|
||||
# where:
|
||||
# All bytes use h64-little-endian encoding
|
||||
# N: 6-bit log cost setting
|
||||
# r: 30-bit block size setting
|
||||
# p: 30-bit parallelism setting
|
||||
# salt: variable length salt bytes
|
||||
# digest: fixed 32-byte digest
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_7_string(cls, suffix):
|
||||
# XXX: annoyingly, official spec embeds salt *raw*, yet doesn't specify a hash encoding.
|
||||
# so assuming only h64 chars are valid for salt, and are ASCII encoded.
|
||||
|
||||
# split into params & digest
|
||||
parts = suffix.encode("ascii").split(b"$")
|
||||
if len(parts) == 2:
|
||||
params, digest = parts
|
||||
elif len(parts) == 1:
|
||||
params, = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError()
|
||||
|
||||
# parse params & return
|
||||
if len(params) < 11:
|
||||
raise uh.exc.MalformedHashError(cls, "params field too short")
|
||||
return dict(
|
||||
ident=IDENT_7,
|
||||
rounds=h64.decode_int6(params[:1]),
|
||||
block_size=h64.decode_int30(params[1:6]),
|
||||
parallelism=h64.decode_int30(params[6:11]),
|
||||
salt=params[11:],
|
||||
checksum=h64.decode_bytes(digest) if digest else None,
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
def to_string(self):
|
||||
ident = self.ident
|
||||
if ident == IDENT_SCRYPT:
|
||||
return "$scrypt$ln=%d,r=%d,p=%d$%s$%s" % (
|
||||
self.rounds,
|
||||
self.block_size,
|
||||
self.parallelism,
|
||||
bascii_to_str(b64s_encode(self.salt)),
|
||||
bascii_to_str(b64s_encode(self.checksum)),
|
||||
)
|
||||
else:
|
||||
assert ident == IDENT_7
|
||||
salt = self.salt
|
||||
try:
|
||||
salt.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
raise suppress_cause(NotImplementedError("scrypt $7$ hashes dont support non-ascii salts"))
|
||||
return bascii_to_str(b"".join([
|
||||
b"$7$",
|
||||
h64.encode_int6(self.rounds),
|
||||
h64.encode_int30(self.block_size),
|
||||
h64.encode_int30(self.parallelism),
|
||||
self.salt,
|
||||
b"$",
|
||||
h64.encode_bytes(self.checksum)
|
||||
]))
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, block_size=None, **kwds):
|
||||
super(scrypt, self).__init__(**kwds)
|
||||
|
||||
# init block size
|
||||
if block_size is None:
|
||||
assert uh.validate_default_value(self, self.block_size, self._norm_block_size,
|
||||
param="block_size")
|
||||
else:
|
||||
self.block_size = self._norm_block_size(block_size)
|
||||
|
||||
# NOTE: if hash contains invalid complex constraint, relying on error
|
||||
# being raised by scrypt call in _calc_checksum()
|
||||
|
||||
@classmethod
|
||||
def _norm_block_size(cls, block_size, relaxed=False):
|
||||
return uh.norm_integer(cls, block_size, min=1, param="block_size", relaxed=relaxed)
|
||||
|
||||
def _generate_salt(self):
|
||||
salt = super(scrypt, self)._generate_salt()
|
||||
if self.ident == IDENT_7:
|
||||
# this format doesn't support non-ascii salts.
|
||||
# as workaround, we take raw bytes, encoded to base64
|
||||
salt = b64s_encode(salt)
|
||||
return salt
|
||||
|
||||
#===================================================================
|
||||
# backend configuration
|
||||
# NOTE: this following HasManyBackends' API, but provides it's own implementation,
|
||||
# which actually switches the backend that 'passlib.crypto.scrypt.scrypt()' uses.
|
||||
#===================================================================
|
||||
|
||||
@classproperty
|
||||
def backends(cls):
|
||||
return _scrypt.backend_values
|
||||
|
||||
@classmethod
|
||||
def get_backend(cls):
|
||||
return _scrypt.backend
|
||||
|
||||
@classmethod
|
||||
def has_backend(cls, name="any"):
|
||||
try:
|
||||
cls.set_backend(name, dryrun=True)
|
||||
return True
|
||||
except uh.exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def set_backend(cls, name="any", dryrun=False):
|
||||
_scrypt._set_backend(name, dryrun=dryrun)
|
||||
|
||||
#===================================================================
|
||||
# digest calculation
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
secret = to_bytes(secret, param="secret")
|
||||
return _scrypt.scrypt(secret, self.salt, n=(1 << self.rounds), r=self.block_size,
|
||||
p=self.parallelism, keylen=self.checksum_size)
|
||||
|
||||
#===================================================================
|
||||
# hash migration
|
||||
#===================================================================
|
||||
|
||||
def _calc_needs_update(self, **kwds):
|
||||
"""
|
||||
mark hash as needing update if rounds is outside desired bounds.
|
||||
"""
|
||||
# XXX: for now, marking all hashes which don't have matching block_size setting
|
||||
if self.block_size != type(self).block_size:
|
||||
return True
|
||||
return super(scrypt, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
158
backend/venv/Lib/site-packages/passlib/handlers/sha1_crypt.py
Normal file
158
backend/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
backend/venv/Lib/site-packages/passlib/handlers/sha2_crypt.py
Normal file
534
backend/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
backend/venv/Lib/site-packages/passlib/handlers/sun_md5_crypt.py
Normal file
363
backend/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
backend/venv/Lib/site-packages/passlib/handlers/windows.py
Normal file
334
backend/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
|
||||
#=============================================================================
|
||||
68
backend/venv/Lib/site-packages/passlib/hash.py
Normal file
68
backend/venv/Lib/site-packages/passlib/hash.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
passlib.hash - proxy object mapping hash scheme names -> handlers
|
||||
|
||||
==================
|
||||
***** NOTICE *****
|
||||
==================
|
||||
|
||||
This module does not actually contain any hashes. This file
|
||||
is a stub that replaces itself with a proxy object.
|
||||
|
||||
This proxy object (passlib.registry._PasslibRegistryProxy)
|
||||
handles lazy-loading hashes as they are requested.
|
||||
|
||||
The actual implementation of the various hashes is store elsewhere,
|
||||
mainly in the submodules of the ``passlib.handlers`` subpackage.
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# import proxy object and replace this module
|
||||
#=============================================================================
|
||||
|
||||
# XXX: if any platform has problem w/ lazy modules, could support 'non-lazy'
|
||||
# version which just imports all schemes known to list_crypt_handlers()
|
||||
|
||||
from passlib.registry import _proxy
|
||||
import sys
|
||||
sys.modules[__name__] = _proxy
|
||||
|
||||
#=============================================================================
|
||||
# HACK: the following bit of code is unreachable, but it's presence seems to
|
||||
# help make autocomplete work for certain IDEs such as PyCharm.
|
||||
# this list is automatically regenerated using $SOURCE/admin/regen.py
|
||||
#=============================================================================
|
||||
|
||||
#----------------------------------------------------
|
||||
# begin autocomplete hack (autogenerated 2016-11-10)
|
||||
#----------------------------------------------------
|
||||
if False:
|
||||
from passlib.handlers.argon2 import argon2
|
||||
from passlib.handlers.bcrypt import bcrypt, bcrypt_sha256
|
||||
from passlib.handlers.cisco import cisco_asa, cisco_pix, cisco_type7
|
||||
from passlib.handlers.des_crypt import bigcrypt, bsdi_crypt, crypt16, des_crypt
|
||||
from passlib.handlers.digests import hex_md4, hex_md5, hex_sha1, hex_sha256, hex_sha512, htdigest
|
||||
from passlib.handlers.django import django_bcrypt, django_bcrypt_sha256, django_des_crypt, django_disabled, django_pbkdf2_sha1, django_pbkdf2_sha256, django_salted_md5, django_salted_sha1
|
||||
from passlib.handlers.fshp import fshp
|
||||
from passlib.handlers.ldap_digests import ldap_bcrypt, ldap_bsdi_crypt, ldap_des_crypt, ldap_md5, ldap_md5_crypt, ldap_plaintext, ldap_salted_md5, ldap_salted_sha1, ldap_salted_sha256, ldap_salted_sha512, ldap_sha1, ldap_sha1_crypt, ldap_sha256_crypt, ldap_sha512_crypt
|
||||
from passlib.handlers.md5_crypt import apr_md5_crypt, md5_crypt
|
||||
from passlib.handlers.misc import plaintext, unix_disabled, unix_fallback
|
||||
from passlib.handlers.mssql import mssql2000, mssql2005
|
||||
from passlib.handlers.mysql import mysql323, mysql41
|
||||
from passlib.handlers.oracle import oracle10, oracle11
|
||||
from passlib.handlers.pbkdf2 import atlassian_pbkdf2_sha1, cta_pbkdf2_sha1, dlitz_pbkdf2_sha1, grub_pbkdf2_sha512, ldap_pbkdf2_sha1, ldap_pbkdf2_sha256, ldap_pbkdf2_sha512, pbkdf2_sha1, pbkdf2_sha256, pbkdf2_sha512
|
||||
from passlib.handlers.phpass import phpass
|
||||
from passlib.handlers.postgres import postgres_md5
|
||||
from passlib.handlers.roundup import ldap_hex_md5, ldap_hex_sha1, roundup_plaintext
|
||||
from passlib.handlers.scram import scram
|
||||
from passlib.handlers.scrypt import scrypt
|
||||
from passlib.handlers.sha1_crypt import sha1_crypt
|
||||
from passlib.handlers.sha2_crypt import sha256_crypt, sha512_crypt
|
||||
from passlib.handlers.sun_md5_crypt import sun_md5_crypt
|
||||
from passlib.handlers.windows import bsd_nthash, lmhash, msdcc, msdcc2, nthash
|
||||
#----------------------------------------------------
|
||||
# end autocomplete hack
|
||||
#----------------------------------------------------
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
106
backend/venv/Lib/site-packages/passlib/hosts.py
Normal file
106
backend/venv/Lib/site-packages/passlib/hosts.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""passlib.hosts"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from warnings import warn
|
||||
# pkg
|
||||
from passlib.context import LazyCryptContext
|
||||
from passlib.exc import PasslibRuntimeWarning
|
||||
from passlib import registry
|
||||
from passlib.utils import has_crypt, unix_crypt_schemes
|
||||
# local
|
||||
__all__ = [
|
||||
"linux_context", "linux2_context",
|
||||
"openbsd_context",
|
||||
"netbsd_context",
|
||||
"freebsd_context",
|
||||
"host_context",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# linux support
|
||||
#=============================================================================
|
||||
|
||||
# known platform names - linux2
|
||||
|
||||
linux_context = linux2_context = LazyCryptContext(
|
||||
schemes = [ "sha512_crypt", "sha256_crypt", "md5_crypt",
|
||||
"des_crypt", "unix_disabled" ],
|
||||
deprecated = [ "des_crypt" ],
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# bsd support
|
||||
#=============================================================================
|
||||
|
||||
# known platform names -
|
||||
# freebsd2
|
||||
# freebsd3
|
||||
# freebsd4
|
||||
# freebsd5
|
||||
# freebsd6
|
||||
# freebsd7
|
||||
#
|
||||
# netbsd1
|
||||
|
||||
# referencing source via -http://fxr.googlebit.com
|
||||
# freebsd 6,7,8 - des, md5, bcrypt, bsd_nthash
|
||||
# netbsd - des, ext, md5, bcrypt, sha1
|
||||
# openbsd - des, ext, md5, bcrypt
|
||||
|
||||
freebsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsd_nthash",
|
||||
"des_crypt", "unix_disabled"])
|
||||
|
||||
openbsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsdi_crypt",
|
||||
"des_crypt", "unix_disabled"])
|
||||
|
||||
netbsd_context = LazyCryptContext(["bcrypt", "sha1_crypt", "md5_crypt",
|
||||
"bsdi_crypt", "des_crypt", "unix_disabled"])
|
||||
|
||||
# XXX: include darwin in this list? it's got a BSD crypt variant,
|
||||
# but that's not what it uses for user passwords.
|
||||
|
||||
#=============================================================================
|
||||
# current host
|
||||
#=============================================================================
|
||||
if registry.os_crypt_present:
|
||||
# NOTE: this is basically mimicing the output of os crypt(),
|
||||
# except that it uses passlib's (usually stronger) defaults settings,
|
||||
# and can be inspected and used much more flexibly.
|
||||
|
||||
def _iter_os_crypt_schemes():
|
||||
"""helper which iterates over supported os_crypt schemes"""
|
||||
out = registry.get_supported_os_crypt_schemes()
|
||||
if out:
|
||||
# only offer disabled handler if there's another scheme in front,
|
||||
# as this can't actually hash any passwords
|
||||
out += ("unix_disabled",)
|
||||
return out
|
||||
|
||||
host_context = LazyCryptContext(_iter_os_crypt_schemes())
|
||||
|
||||
#=============================================================================
|
||||
# other platforms
|
||||
#=============================================================================
|
||||
|
||||
# known platform strings -
|
||||
# aix3
|
||||
# aix4
|
||||
# atheos
|
||||
# beos5
|
||||
# darwin
|
||||
# generic
|
||||
# hp-ux11
|
||||
# irix5
|
||||
# irix6
|
||||
# mac
|
||||
# next3
|
||||
# os2emx
|
||||
# riscos
|
||||
# sunos5
|
||||
# unixware7
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
353
backend/venv/Lib/site-packages/passlib/ifc.py
Normal file
353
backend/venv/Lib/site-packages/passlib/ifc.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""passlib.ifc - abstract interfaces used by Passlib"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import sys
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.decor import deprecated_method
|
||||
# local
|
||||
__all__ = [
|
||||
"PasswordHash",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# 2/3 compatibility helpers
|
||||
#=============================================================================
|
||||
def recreate_with_metaclass(meta):
|
||||
"""class decorator that re-creates class using metaclass"""
|
||||
def builder(cls):
|
||||
if meta is type(cls):
|
||||
return cls
|
||||
return meta(cls.__name__, cls.__bases__, cls.__dict__.copy())
|
||||
return builder
|
||||
|
||||
#=============================================================================
|
||||
# PasswordHash interface
|
||||
#=============================================================================
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
|
||||
# TODO: make this actually use abstractproperty(),
|
||||
# now that we dropped py25, 'abc' is always available.
|
||||
|
||||
# XXX: rename to PasswordHasher?
|
||||
|
||||
@recreate_with_metaclass(ABCMeta)
|
||||
class PasswordHash(object):
|
||||
"""This class describes an abstract interface which all password hashes
|
||||
in Passlib adhere to. Under Python 2.6 and up, this is an actual
|
||||
Abstract Base Class built using the :mod:`!abc` module.
|
||||
|
||||
See the Passlib docs for full documentation.
|
||||
"""
|
||||
#===================================================================
|
||||
# class attributes
|
||||
#===================================================================
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# general information
|
||||
#---------------------------------------------------------------
|
||||
##name
|
||||
##setting_kwds
|
||||
##context_kwds
|
||||
|
||||
#: flag which indicates this hasher matches a "disabled" hash
|
||||
#: (e.g. unix_disabled, or django_disabled); and doesn't actually
|
||||
#: depend on the provided password.
|
||||
is_disabled = False
|
||||
|
||||
#: Should be None, or a positive integer indicating hash
|
||||
#: doesn't support secrets larger than this value.
|
||||
#: Whether hash throws error or silently truncates secret
|
||||
#: depends on .truncate_error and .truncate_verify_reject flags below.
|
||||
#: NOTE: calls may treat as boolean, since value will never be 0.
|
||||
#: .. versionadded:: 1.7
|
||||
#: .. TODO: passlib 1.8: deprecate/rename this attr to "max_secret_size"?
|
||||
truncate_size = None
|
||||
|
||||
# NOTE: these next two default to the optimistic "ideal",
|
||||
# most hashes in passlib have to default to False
|
||||
# for backward compat and/or expected behavior with existing hashes.
|
||||
|
||||
#: If True, .hash() should throw a :exc:`~passlib.exc.PasswordSizeError` for
|
||||
#: any secrets larger than .truncate_size. Many hashers default to False
|
||||
#: for historical / compatibility purposes, indicating they will silently
|
||||
#: truncate instead. All such hashers SHOULD support changing
|
||||
#: the policy via ``.using(truncate_error=True)``.
|
||||
#: .. versionadded:: 1.7
|
||||
#: .. TODO: passlib 1.8: deprecate/rename this attr to "truncate_hash_error"?
|
||||
truncate_error = True
|
||||
|
||||
#: If True, .verify() should reject secrets larger than max_password_size.
|
||||
#: Many hashers default to False for historical / compatibility purposes,
|
||||
#: indicating they will match on the truncated portion instead.
|
||||
#: .. versionadded:: 1.7.1
|
||||
truncate_verify_reject = True
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# salt information -- if 'salt' in setting_kwds
|
||||
#---------------------------------------------------------------
|
||||
##min_salt_size
|
||||
##max_salt_size
|
||||
##default_salt_size
|
||||
##salt_chars
|
||||
##default_salt_chars
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# rounds information -- if 'rounds' in setting_kwds
|
||||
#---------------------------------------------------------------
|
||||
##min_rounds
|
||||
##max_rounds
|
||||
##default_rounds
|
||||
##rounds_cost
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# encoding info -- if 'encoding' in context_kwds
|
||||
#---------------------------------------------------------------
|
||||
##default_encoding
|
||||
|
||||
#===================================================================
|
||||
# primary methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def hash(cls, secret, # *
|
||||
**setting_and_context_kwds): # pragma: no cover -- abstract method
|
||||
r"""
|
||||
Hash secret, returning result.
|
||||
Should handle generating salt, etc, and should return string
|
||||
containing identifier, salt & other configuration, as well as digest.
|
||||
|
||||
:param \\*\\*settings_kwds:
|
||||
|
||||
Pass in settings to customize configuration of resulting hash.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Starting with Passlib 1.7, callers should no longer pass settings keywords
|
||||
(e.g. ``rounds`` or ``salt`` directly to :meth:`!hash`); should use
|
||||
``.using(**settings).hash(secret)`` construction instead.
|
||||
|
||||
Support will be removed in Passlib 2.0.
|
||||
|
||||
:param \\*\\*context_kwds:
|
||||
|
||||
Specific algorithms may require context-specific information (such as the user login).
|
||||
"""
|
||||
# FIXME: need stub for classes that define .encrypt() instead ...
|
||||
# this should call .encrypt(), and check for recursion back to here.
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
|
||||
@deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash()")
|
||||
@classmethod
|
||||
def encrypt(cls, *args, **kwds):
|
||||
"""
|
||||
Legacy alias for :meth:`hash`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
This method was renamed to :meth:`!hash` in version 1.7.
|
||||
This alias will be removed in version 2.0, and should only
|
||||
be used for compatibility with Passlib 1.3 - 1.6.
|
||||
"""
|
||||
return cls.hash(*args, **kwds)
|
||||
|
||||
# XXX: could provide default implementation which hands value to
|
||||
# hash(), and then does constant-time comparision on the result
|
||||
# (after making both are same string type)
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def verify(cls, secret, hash, **context_kwds): # pragma: no cover -- abstract method
|
||||
"""verify secret against hash, returns True/False"""
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
|
||||
#===================================================================
|
||||
# configuration
|
||||
#===================================================================
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def using(cls, relaxed=False, **kwds):
|
||||
"""
|
||||
Return another hasher object (typically a subclass of the current one),
|
||||
which integrates the configuration options specified by ``kwds``.
|
||||
This should *always* return a new object, even if no configuration options are changed.
|
||||
|
||||
.. todo::
|
||||
|
||||
document which options are accepted.
|
||||
|
||||
:returns:
|
||||
typically returns a subclass for most hasher implementations.
|
||||
|
||||
.. todo::
|
||||
|
||||
add this method to main documentation.
|
||||
"""
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
|
||||
#===================================================================
|
||||
# migration
|
||||
#===================================================================
|
||||
@classmethod
|
||||
def needs_update(cls, hash, secret=None):
|
||||
"""
|
||||
check if hash's configuration is outside desired bounds,
|
||||
or contains some other internal option which requires
|
||||
updating the password hash.
|
||||
|
||||
:param hash:
|
||||
hash string to examine
|
||||
|
||||
:param secret:
|
||||
optional secret known to have verified against the provided hash.
|
||||
(this is used by some hashes to detect legacy algorithm mistakes).
|
||||
|
||||
:return:
|
||||
whether secret needs re-hashing.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
# by default, always report that we don't need update
|
||||
return False
|
||||
|
||||
#===================================================================
|
||||
# additional methods
|
||||
#===================================================================
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def identify(cls, hash): # pragma: no cover -- abstract method
|
||||
"""check if hash belongs to this scheme, returns True/False"""
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
|
||||
@deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genconfig(cls, **setting_kwds): # pragma: no cover -- abstract method
|
||||
"""
|
||||
compile settings into a configuration string for genhash()
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
|
||||
|
||||
For all known real-world uses, hashing a constant string
|
||||
should provide equivalent functionality.
|
||||
|
||||
This deprecation may be reversed if a use-case presents itself in the mean time.
|
||||
"""
|
||||
# NOTE: this fallback runs full hash alg, w/ whatever cost param is passed along.
|
||||
# implementations (esp ones w/ variable cost) will want to subclass this
|
||||
# with a constant-time implementation that just renders a config string.
|
||||
if cls.context_kwds:
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
return cls.using(**setting_kwds).hash("")
|
||||
|
||||
@deprecated_method(deprecated="1.7", removed="2.0")
|
||||
@classmethod
|
||||
def genhash(cls, secret, config, **context):
|
||||
"""
|
||||
generated hash for secret, using settings from config/hash string
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
|
||||
|
||||
This deprecation may be reversed if a use-case presents itself in the mean time.
|
||||
"""
|
||||
# XXX: if hashes reliably offered a .parse() method, could make a fallback for this.
|
||||
raise NotImplementedError("must be implemented by subclass")
|
||||
|
||||
#===================================================================
|
||||
# undocumented methods / attributes
|
||||
#===================================================================
|
||||
# the following entry points are used internally by passlib,
|
||||
# and aren't documented as part of the exposed interface.
|
||||
# they are subject to change between releases,
|
||||
# but are documented here so there's a list of them *somewhere*.
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# extra metdata
|
||||
#---------------------------------------------------------------
|
||||
|
||||
#: this attribute shouldn't be used by hashers themselves,
|
||||
#: it's reserved for the CryptContext to track which hashers are deprecated.
|
||||
#: Note the context will only set this on objects it owns (and generated by .using()),
|
||||
#: and WONT set it on global objects.
|
||||
#: [added in 1.7]
|
||||
#: TODO: document this, or at least the use of testing for
|
||||
#: 'CryptContext().handler().deprecated'
|
||||
deprecated = False
|
||||
|
||||
#: optionally present if hasher corresponds to format built into Django.
|
||||
#: this attribute (if not None) should be the Django 'algorithm' name.
|
||||
#: also indicates to passlib.ext.django that (when installed in django),
|
||||
#: django's native hasher should be used in preference to this one.
|
||||
## django_name
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# checksum information - defined for many hashes
|
||||
#---------------------------------------------------------------
|
||||
## checksum_chars
|
||||
## checksum_size
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# experimental methods
|
||||
#---------------------------------------------------------------
|
||||
|
||||
##@classmethod
|
||||
##def normhash(cls, hash):
|
||||
## """helper to clean up non-canonic instances of hash.
|
||||
## currently only provided by bcrypt() to fix an historical passlib issue.
|
||||
## """
|
||||
|
||||
# experimental helper to parse hash into components.
|
||||
##@classmethod
|
||||
##def parsehash(cls, hash, checksum=True, sanitize=False):
|
||||
## """helper to parse hash into components, returns dict"""
|
||||
|
||||
# experiment helper to estimate bitsize of different hashes,
|
||||
# implement for GenericHandler, but may be currently be off for some hashes.
|
||||
# want to expand this into a way to programmatically compare
|
||||
# "strengths" of different hashes and hash algorithms.
|
||||
# still needs to have some factor for estimate relative cost per round,
|
||||
# ala in the style of the scrypt whitepaper.
|
||||
##@classmethod
|
||||
##def bitsize(cls, **kwds):
|
||||
## """returns dict mapping component -> bits contributed.
|
||||
## components currently include checksum, salt, rounds.
|
||||
## """
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
class DisabledHash(PasswordHash):
|
||||
"""
|
||||
extended disabled-hash methods; only need be present if .disabled = True
|
||||
"""
|
||||
|
||||
is_disabled = True
|
||||
|
||||
@classmethod
|
||||
def disable(cls, hash=None):
|
||||
"""
|
||||
return string representing a 'disabled' hash;
|
||||
optionally including previously enabled hash
|
||||
(this is up to the individual scheme).
|
||||
"""
|
||||
# default behavior: ignore original hash, return standalone marker
|
||||
return cls.hash("")
|
||||
|
||||
@classmethod
|
||||
def enable(cls, hash):
|
||||
"""
|
||||
given a disabled-hash string,
|
||||
extract previously-enabled hash if one is present,
|
||||
otherwise raises ValueError
|
||||
"""
|
||||
# default behavior: no way to restore original hash
|
||||
raise ValueError("cannot restore original hash")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
809
backend/venv/Lib/site-packages/passlib/pwd.py
Normal file
809
backend/venv/Lib/site-packages/passlib/pwd.py
Normal file
@@ -0,0 +1,809 @@
|
||||
"""passlib.pwd -- password generation helpers"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
# core
|
||||
import codecs
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from collections.abc import MutableMapping
|
||||
except ImportError:
|
||||
# py2 compat
|
||||
from collections import MutableMapping
|
||||
from math import ceil, log as logf
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import pkg_resources
|
||||
import os
|
||||
# site
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils.compat import PY2, irange, itervalues, int_types
|
||||
from passlib.utils import rng, getrandstr, to_unicode
|
||||
from passlib.utils.decor import memoized_property
|
||||
# local
|
||||
__all__ = [
|
||||
"genword", "default_charsets",
|
||||
"genphrase", "default_wordsets",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# constants
|
||||
#=============================================================================
|
||||
|
||||
# XXX: rename / publically document this map?
|
||||
entropy_aliases = dict(
|
||||
# barest protection from throttled online attack
|
||||
unsafe=12,
|
||||
|
||||
# some protection from unthrottled online attack
|
||||
weak=24,
|
||||
|
||||
# some protection from offline attacks
|
||||
fair=36,
|
||||
|
||||
# reasonable protection from offline attacks
|
||||
strong=48,
|
||||
|
||||
# very good protection from offline attacks
|
||||
secure=60,
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# internal helpers
|
||||
#=============================================================================
|
||||
|
||||
def _superclasses(obj, cls):
|
||||
"""return remaining classes in object's MRO after cls"""
|
||||
mro = type(obj).__mro__
|
||||
return mro[mro.index(cls)+1:]
|
||||
|
||||
|
||||
def _self_info_rate(source):
|
||||
"""
|
||||
returns 'rate of self-information' --
|
||||
i.e. average (per-symbol) entropy of the sequence **source**,
|
||||
where probability of a given symbol occurring is calculated based on
|
||||
the number of occurrences within the sequence itself.
|
||||
|
||||
if all elements of the source are unique, this should equal ``log(len(source), 2)``.
|
||||
|
||||
:arg source:
|
||||
iterable containing 0+ symbols
|
||||
(e.g. list of strings or ints, string of characters, etc).
|
||||
|
||||
:returns:
|
||||
float bits of entropy
|
||||
"""
|
||||
try:
|
||||
size = len(source)
|
||||
except TypeError:
|
||||
# if len() doesn't work, calculate size by summing counts later
|
||||
size = None
|
||||
counts = defaultdict(int)
|
||||
for char in source:
|
||||
counts[char] += 1
|
||||
if size is None:
|
||||
values = counts.values()
|
||||
size = sum(values)
|
||||
else:
|
||||
values = itervalues(counts)
|
||||
if not size:
|
||||
return 0
|
||||
# NOTE: the following performs ``- sum(value / size * logf(value / size, 2) for value in values)``,
|
||||
# it just does so with as much pulled out of the sum() loop as possible...
|
||||
return logf(size, 2) - sum(value * logf(value, 2) for value in values) / size
|
||||
|
||||
|
||||
# def _total_self_info(source):
|
||||
# """
|
||||
# return total self-entropy of a sequence
|
||||
# (the average entropy per symbol * size of sequence)
|
||||
# """
|
||||
# return _self_info_rate(source) * len(source)
|
||||
|
||||
|
||||
def _open_asset_path(path, encoding=None):
|
||||
"""
|
||||
:param asset_path:
|
||||
string containing absolute path to file,
|
||||
or package-relative path using format
|
||||
``"python.module:relative/file/path"``.
|
||||
|
||||
:returns:
|
||||
filehandle opened in 'rb' mode
|
||||
(unless encoding explicitly specified)
|
||||
"""
|
||||
if encoding:
|
||||
return codecs.getreader(encoding)(_open_asset_path(path))
|
||||
if os.path.isabs(path):
|
||||
return open(path, "rb")
|
||||
package, sep, subpath = path.partition(":")
|
||||
if not sep:
|
||||
raise ValueError("asset path must be absolute file path "
|
||||
"or use 'pkg.name:sub/path' format: %r" % (path,))
|
||||
return pkg_resources.resource_stream(package, subpath)
|
||||
|
||||
|
||||
#: type aliases
|
||||
_sequence_types = (list, tuple)
|
||||
_set_types = (set, frozenset)
|
||||
|
||||
#: set of elements that ensure_unique() has validated already.
|
||||
_ensure_unique_cache = set()
|
||||
|
||||
|
||||
def _ensure_unique(source, param="source"):
|
||||
"""
|
||||
helper for generators --
|
||||
Throws ValueError if source elements aren't unique.
|
||||
Error message will display (abbreviated) repr of the duplicates in a string/list
|
||||
"""
|
||||
# check cache to speed things up for frozensets / tuples / strings
|
||||
cache = _ensure_unique_cache
|
||||
hashable = True
|
||||
try:
|
||||
if source in cache:
|
||||
return True
|
||||
except TypeError:
|
||||
hashable = False
|
||||
|
||||
# check if it has dup elements
|
||||
if isinstance(source, _set_types) or len(set(source)) == len(source):
|
||||
if hashable:
|
||||
try:
|
||||
cache.add(source)
|
||||
except TypeError:
|
||||
# XXX: under pypy, "list() in set()" above doesn't throw TypeError,
|
||||
# but trying to add unhashable it to a set *does*.
|
||||
pass
|
||||
return True
|
||||
|
||||
# build list of duplicate values
|
||||
seen = set()
|
||||
dups = set()
|
||||
for elem in source:
|
||||
(dups if elem in seen else seen).add(elem)
|
||||
dups = sorted(dups)
|
||||
trunc = 8
|
||||
if len(dups) > trunc:
|
||||
trunc = 5
|
||||
dup_repr = ", ".join(repr(str(word)) for word in dups[:trunc])
|
||||
if len(dups) > trunc:
|
||||
dup_repr += ", ... plus %d others" % (len(dups) - trunc)
|
||||
|
||||
# throw error
|
||||
raise ValueError("`%s` cannot contain duplicate elements: %s" %
|
||||
(param, dup_repr))
|
||||
|
||||
#=============================================================================
|
||||
# base generator class
|
||||
#=============================================================================
|
||||
class SequenceGenerator(object):
|
||||
"""
|
||||
Base class used by word & phrase generators.
|
||||
|
||||
These objects take a series of options, corresponding
|
||||
to those of the :func:`generate` function.
|
||||
They act as callables which can be used to generate a password
|
||||
or a list of 1+ passwords. They also expose some read-only
|
||||
informational attributes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
:param entropy:
|
||||
Optionally specify the amount of entropy the resulting passwords
|
||||
should contain (as measured with respect to the generator itself).
|
||||
This will be used to auto-calculate the required password size.
|
||||
|
||||
:param length:
|
||||
Optionally specify the length of password to generate,
|
||||
measured as count of whatever symbols the subclass uses (characters or words).
|
||||
Note if ``entropy`` requires a larger minimum length,
|
||||
that will be used instead.
|
||||
|
||||
:param rng:
|
||||
Optionally provide a custom RNG source to use.
|
||||
Should be an instance of :class:`random.Random`,
|
||||
defaults to :class:`random.SystemRandom`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
.. autoattribute:: length
|
||||
.. autoattribute:: symbol_count
|
||||
.. autoattribute:: entropy_per_symbol
|
||||
.. autoattribute:: entropy
|
||||
|
||||
Subclassing
|
||||
-----------
|
||||
Subclasses must implement the ``.__next__()`` method,
|
||||
and set ``.symbol_count`` before calling base ``__init__`` method.
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: requested size of final password
|
||||
length = None
|
||||
|
||||
#: requested entropy of final password
|
||||
requested_entropy = "strong"
|
||||
|
||||
#: random number source to use
|
||||
rng = rng
|
||||
|
||||
#: number of potential symbols (must be filled in by subclass)
|
||||
symbol_count = None
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, entropy=None, length=None, rng=None, **kwds):
|
||||
|
||||
# make sure subclass set things up correctly
|
||||
assert self.symbol_count is not None, "subclass must set .symbol_count"
|
||||
|
||||
# init length & requested entropy
|
||||
if entropy is not None or length is None:
|
||||
if entropy is None:
|
||||
entropy = self.requested_entropy
|
||||
entropy = entropy_aliases.get(entropy, entropy)
|
||||
if entropy <= 0:
|
||||
raise ValueError("`entropy` must be positive number")
|
||||
min_length = int(ceil(entropy / self.entropy_per_symbol))
|
||||
if length is None or length < min_length:
|
||||
length = min_length
|
||||
|
||||
self.requested_entropy = entropy
|
||||
|
||||
if length < 1:
|
||||
raise ValueError("`length` must be positive integer")
|
||||
self.length = length
|
||||
|
||||
# init other common options
|
||||
if rng is not None:
|
||||
self.rng = rng
|
||||
|
||||
# hand off to parent
|
||||
if kwds and _superclasses(self, SequenceGenerator) == (object,):
|
||||
raise TypeError("Unexpected keyword(s): %s" % ", ".join(kwds.keys()))
|
||||
super(SequenceGenerator, self).__init__(**kwds)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def entropy_per_symbol(self):
|
||||
"""
|
||||
Average entropy per symbol (assuming all symbols have equal probability)
|
||||
"""
|
||||
return logf(self.symbol_count, 2)
|
||||
|
||||
@memoized_property
|
||||
def entropy(self):
|
||||
"""
|
||||
Effective entropy of generated passwords.
|
||||
|
||||
This value will always be a multiple of :attr:`entropy_per_symbol`.
|
||||
If entropy is specified in constructor, :attr:`length` will be chosen so
|
||||
so that this value is the smallest multiple >= :attr:`requested_entropy`.
|
||||
"""
|
||||
return self.length * self.entropy_per_symbol
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
def __next__(self):
|
||||
"""main generation function, should create one password/phrase"""
|
||||
raise NotImplementedError("implement in subclass")
|
||||
|
||||
def __call__(self, returns=None):
|
||||
"""
|
||||
frontend used by genword() / genphrase() to create passwords
|
||||
"""
|
||||
if returns is None:
|
||||
return next(self)
|
||||
elif isinstance(returns, int_types):
|
||||
return [next(self) for _ in irange(returns)]
|
||||
elif returns is iter:
|
||||
return self
|
||||
else:
|
||||
raise exc.ExpectedTypeError(returns, "<None>, int, or <iter>", "returns")
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
if PY2:
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# default charsets
|
||||
#=============================================================================
|
||||
|
||||
#: global dict of predefined characters sets
|
||||
default_charsets = dict(
|
||||
# ascii letters, digits, and some punctuation
|
||||
ascii_72='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*?/',
|
||||
|
||||
# ascii letters and digits
|
||||
ascii_62='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
|
||||
# ascii_50, without visually similar '1IiLl', '0Oo', '5S', '8B'
|
||||
ascii_50='234679abcdefghjkmnpqrstuvwxyzACDEFGHJKMNPQRTUVWXYZ',
|
||||
|
||||
# lower case hexadecimal
|
||||
hex='0123456789abcdef',
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# password generator
|
||||
#=============================================================================
|
||||
|
||||
class WordGenerator(SequenceGenerator):
|
||||
"""
|
||||
Class which generates passwords by randomly choosing from a string of unique characters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
:param chars:
|
||||
custom character string to draw from.
|
||||
|
||||
:param charset:
|
||||
predefined charset to draw from.
|
||||
|
||||
:param \\*\\*kwds:
|
||||
all other keywords passed to the :class:`SequenceGenerator` parent class.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
.. autoattribute:: chars
|
||||
.. autoattribute:: charset
|
||||
.. autoattribute:: default_charsets
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: Predefined character set in use (set to None for instances using custom 'chars')
|
||||
charset = "ascii_62"
|
||||
|
||||
#: string of chars to draw from -- usually filled in from charset
|
||||
chars = None
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, chars=None, charset=None, **kwds):
|
||||
|
||||
# init chars and charset
|
||||
if chars:
|
||||
if charset:
|
||||
raise TypeError("`chars` and `charset` are mutually exclusive")
|
||||
else:
|
||||
if not charset:
|
||||
charset = self.charset
|
||||
assert charset
|
||||
chars = default_charsets[charset]
|
||||
self.charset = charset
|
||||
chars = to_unicode(chars, param="chars")
|
||||
_ensure_unique(chars, param="chars")
|
||||
self.chars = chars
|
||||
|
||||
# hand off to parent
|
||||
super(WordGenerator, self).__init__(**kwds)
|
||||
# log.debug("WordGenerator(): entropy/char=%r", self.entropy_per_symbol)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def symbol_count(self):
|
||||
return len(self.chars)
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
|
||||
def __next__(self):
|
||||
# XXX: could do things like optionally ensure certain character groups
|
||||
# (e.g. letters & punctuation) are included
|
||||
return getrandstr(self.rng, self.chars, self.length)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
def genword(entropy=None, length=None, returns=None, **kwds):
|
||||
"""Generate one or more random passwords.
|
||||
|
||||
This function uses :mod:`random.SystemRandom` to generate
|
||||
one or more passwords using various character sets.
|
||||
The complexity of the password can be specified
|
||||
by size, or by the desired amount of entropy.
|
||||
|
||||
Usage Example::
|
||||
|
||||
>>> # generate a random alphanumeric string with 48 bits of entropy (the default)
|
||||
>>> from passlib import pwd
|
||||
>>> pwd.genword()
|
||||
'DnBHvDjMK6'
|
||||
|
||||
>>> # generate a random hexadecimal string with 52 bits of entropy
|
||||
>>> pwd.genword(entropy=52, charset="hex")
|
||||
'310f1a7ac793f'
|
||||
|
||||
:param entropy:
|
||||
Strength of resulting password, measured in 'guessing entropy' bits.
|
||||
An appropriate **length** value will be calculated
|
||||
based on the requested entropy amount, and the size of the character set.
|
||||
|
||||
This can be a positive integer, or one of the following preset
|
||||
strings: ``"weak"`` (24), ``"fair"`` (36),
|
||||
``"strong"`` (48), and ``"secure"`` (56).
|
||||
|
||||
If neither this or **length** is specified, **entropy** will default
|
||||
to ``"strong"`` (48).
|
||||
|
||||
:param length:
|
||||
Size of resulting password, measured in characters.
|
||||
If omitted, the size is auto-calculated based on the **entropy** parameter.
|
||||
|
||||
If both **entropy** and **length** are specified,
|
||||
the stronger value will be used.
|
||||
|
||||
:param returns:
|
||||
Controls what this function returns:
|
||||
|
||||
* If ``None`` (the default), this function will generate a single password.
|
||||
* If an integer, this function will return a list containing that many passwords.
|
||||
* If the ``iter`` constant, will return an iterator that yields passwords.
|
||||
|
||||
:param chars:
|
||||
|
||||
Optionally specify custom string of characters to use when randomly
|
||||
generating a password. This option cannot be combined with **charset**.
|
||||
|
||||
:param charset:
|
||||
|
||||
The predefined character set to draw from (if not specified by **chars**).
|
||||
There are currently four presets available:
|
||||
|
||||
* ``"ascii_62"`` (the default) -- all digits and ascii upper & lowercase letters.
|
||||
Provides ~5.95 entropy per character.
|
||||
|
||||
* ``"ascii_50"`` -- subset which excludes visually similar characters
|
||||
(``1IiLl0Oo5S8B``). Provides ~5.64 entropy per character.
|
||||
|
||||
* ``"ascii_72"`` -- all digits and ascii upper & lowercase letters,
|
||||
as well as some punctuation. Provides ~6.17 entropy per character.
|
||||
|
||||
* ``"hex"`` -- Lower case hexadecimal. Providers 4 bits of entropy per character.
|
||||
|
||||
:returns:
|
||||
:class:`!unicode` string containing randomly generated password;
|
||||
or list of 1+ passwords if :samp:`returns={int}` is specified.
|
||||
"""
|
||||
gen = WordGenerator(length=length, entropy=entropy, **kwds)
|
||||
return gen(returns)
|
||||
|
||||
#=============================================================================
|
||||
# default wordsets
|
||||
#=============================================================================
|
||||
|
||||
def _load_wordset(asset_path):
|
||||
"""
|
||||
load wordset from compressed datafile within package data.
|
||||
file should be utf-8 encoded
|
||||
|
||||
:param asset_path:
|
||||
string containing absolute path to wordset file,
|
||||
or "python.module:relative/file/path".
|
||||
|
||||
:returns:
|
||||
tuple of words, as loaded from specified words file.
|
||||
"""
|
||||
# open resource file, convert to tuple of words (strip blank lines & ws)
|
||||
with _open_asset_path(asset_path, "utf-8") as fh:
|
||||
gen = (word.strip() for word in fh)
|
||||
words = tuple(word for word in gen if word)
|
||||
|
||||
# NOTE: works but not used
|
||||
# # detect if file uses "<int> <word>" format, and strip numeric prefix
|
||||
# def extract(row):
|
||||
# idx, word = row.replace("\t", " ").split(" ", 1)
|
||||
# if not idx.isdigit():
|
||||
# raise ValueError("row is not dice index + word")
|
||||
# return word
|
||||
# try:
|
||||
# extract(words[-1])
|
||||
# except ValueError:
|
||||
# pass
|
||||
# else:
|
||||
# words = tuple(extract(word) for word in words)
|
||||
|
||||
log.debug("loaded %d-element wordset from %r", len(words), asset_path)
|
||||
return words
|
||||
|
||||
|
||||
class WordsetDict(MutableMapping):
|
||||
"""
|
||||
Special mapping used to store dictionary of wordsets.
|
||||
Different from a regular dict in that some wordsets
|
||||
may be lazy-loaded from an asset path.
|
||||
"""
|
||||
|
||||
#: dict of key -> asset path
|
||||
paths = None
|
||||
|
||||
#: dict of key -> value
|
||||
_loaded = None
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.paths = {}
|
||||
self._loaded = {}
|
||||
super(WordsetDict, self).__init__(*args, **kwds)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self._loaded[key]
|
||||
except KeyError:
|
||||
pass
|
||||
path = self.paths[key]
|
||||
value = self._loaded[key] = _load_wordset(path)
|
||||
return value
|
||||
|
||||
def set_path(self, key, path):
|
||||
"""
|
||||
set asset path to lazy-load wordset from.
|
||||
"""
|
||||
self.paths[key] = path
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._loaded[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key in self:
|
||||
del self._loaded[key]
|
||||
self.paths.pop(key, None)
|
||||
else:
|
||||
del self.paths[key]
|
||||
|
||||
@property
|
||||
def _keyset(self):
|
||||
keys = set(self._loaded)
|
||||
keys.update(self.paths)
|
||||
return keys
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._keyset)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._keyset)
|
||||
|
||||
# NOTE: speeds things up, and prevents contains from lazy-loading
|
||||
def __contains__(self, key):
|
||||
return key in self._loaded or key in self.paths
|
||||
|
||||
|
||||
#: dict of predefined word sets.
|
||||
#: key is name of wordset, value should be sequence of words.
|
||||
default_wordsets = WordsetDict()
|
||||
|
||||
# register the wordsets built into passlib
|
||||
for name in "eff_long eff_short eff_prefixed bip39".split():
|
||||
default_wordsets.set_path(name, "passlib:_data/wordsets/%s.txt" % name)
|
||||
|
||||
#=============================================================================
|
||||
# passphrase generator
|
||||
#=============================================================================
|
||||
class PhraseGenerator(SequenceGenerator):
|
||||
"""class which generates passphrases by randomly choosing
|
||||
from a list of unique words.
|
||||
|
||||
:param wordset:
|
||||
wordset to draw from.
|
||||
:param preset:
|
||||
name of preset wordlist to use instead of ``wordset``.
|
||||
:param spaces:
|
||||
whether to insert spaces between words in output (defaults to ``True``).
|
||||
:param \\*\\*kwds:
|
||||
all other keywords passed to the :class:`SequenceGenerator` parent class.
|
||||
|
||||
.. autoattribute:: wordset
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: predefined wordset to use
|
||||
wordset = "eff_long"
|
||||
|
||||
#: list of words to draw from
|
||||
words = None
|
||||
|
||||
#: separator to use when joining words
|
||||
sep = " "
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, wordset=None, words=None, sep=None, **kwds):
|
||||
|
||||
# load wordset
|
||||
if words is not None:
|
||||
if wordset is not None:
|
||||
raise TypeError("`words` and `wordset` are mutually exclusive")
|
||||
else:
|
||||
if wordset is None:
|
||||
wordset = self.wordset
|
||||
assert wordset
|
||||
words = default_wordsets[wordset]
|
||||
self.wordset = wordset
|
||||
|
||||
# init words
|
||||
if not isinstance(words, _sequence_types):
|
||||
words = tuple(words)
|
||||
_ensure_unique(words, param="words")
|
||||
self.words = words
|
||||
|
||||
# init separator
|
||||
if sep is None:
|
||||
sep = self.sep
|
||||
sep = to_unicode(sep, param="sep")
|
||||
self.sep = sep
|
||||
|
||||
# hand off to parent
|
||||
super(PhraseGenerator, self).__init__(**kwds)
|
||||
##log.debug("PhraseGenerator(): entropy/word=%r entropy/char=%r min_chars=%r",
|
||||
## self.entropy_per_symbol, self.entropy_per_char, self.min_chars)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def symbol_count(self):
|
||||
return len(self.words)
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
|
||||
def __next__(self):
|
||||
words = (self.rng.choice(self.words) for _ in irange(self.length))
|
||||
return self.sep.join(words)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
def genphrase(entropy=None, length=None, returns=None, **kwds):
|
||||
"""Generate one or more random password / passphrases.
|
||||
|
||||
This function uses :mod:`random.SystemRandom` to generate
|
||||
one or more passwords; it can be configured to generate
|
||||
alphanumeric passwords, or full english phrases.
|
||||
The complexity of the password can be specified
|
||||
by size, or by the desired amount of entropy.
|
||||
|
||||
Usage Example::
|
||||
|
||||
>>> # generate random phrase with 48 bits of entropy
|
||||
>>> from passlib import pwd
|
||||
>>> pwd.genphrase()
|
||||
'gangly robbing salt shove'
|
||||
|
||||
>>> # generate a random phrase with 52 bits of entropy
|
||||
>>> # using a particular wordset
|
||||
>>> pwd.genword(entropy=52, wordset="bip39")
|
||||
'wheat dilemma reward rescue diary'
|
||||
|
||||
:param entropy:
|
||||
Strength of resulting password, measured in 'guessing entropy' bits.
|
||||
An appropriate **length** value will be calculated
|
||||
based on the requested entropy amount, and the size of the word set.
|
||||
|
||||
This can be a positive integer, or one of the following preset
|
||||
strings: ``"weak"`` (24), ``"fair"`` (36),
|
||||
``"strong"`` (48), and ``"secure"`` (56).
|
||||
|
||||
If neither this or **length** is specified, **entropy** will default
|
||||
to ``"strong"`` (48).
|
||||
|
||||
:param length:
|
||||
Length of resulting password, measured in words.
|
||||
If omitted, the size is auto-calculated based on the **entropy** parameter.
|
||||
|
||||
If both **entropy** and **length** are specified,
|
||||
the stronger value will be used.
|
||||
|
||||
:param returns:
|
||||
Controls what this function returns:
|
||||
|
||||
* If ``None`` (the default), this function will generate a single password.
|
||||
* If an integer, this function will return a list containing that many passwords.
|
||||
* If the ``iter`` builtin, will return an iterator that yields passwords.
|
||||
|
||||
:param words:
|
||||
|
||||
Optionally specifies a list/set of words to use when randomly generating a passphrase.
|
||||
This option cannot be combined with **wordset**.
|
||||
|
||||
:param wordset:
|
||||
|
||||
The predefined word set to draw from (if not specified by **words**).
|
||||
There are currently four presets available:
|
||||
|
||||
``"eff_long"`` (the default)
|
||||
|
||||
Wordset containing 7776 english words of ~7 letters.
|
||||
Constructed by the EFF, it offers ~12.9 bits of entropy per word.
|
||||
|
||||
This wordset (and the other ``"eff_"`` wordsets)
|
||||
were `created by the EFF <https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases>`_
|
||||
to aid in generating passwords. See their announcement page
|
||||
for more details about the design & properties of these wordsets.
|
||||
|
||||
``"eff_short"``
|
||||
|
||||
Wordset containing 1296 english words of ~4.5 letters.
|
||||
Constructed by the EFF, it offers ~10.3 bits of entropy per word.
|
||||
|
||||
``"eff_prefixed"``
|
||||
|
||||
Wordset containing 1296 english words of ~8 letters,
|
||||
selected so that they each have a unique 3-character prefix.
|
||||
Constructed by the EFF, it offers ~10.3 bits of entropy per word.
|
||||
|
||||
``"bip39"``
|
||||
|
||||
Wordset of 2048 english words of ~5 letters,
|
||||
selected so that they each have a unique 4-character prefix.
|
||||
Published as part of Bitcoin's `BIP 39 <https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt>`_,
|
||||
this wordset has exactly 11 bits of entropy per word.
|
||||
|
||||
This list offers words that are typically shorter than ``"eff_long"``
|
||||
(at the cost of slightly less entropy); and much shorter than
|
||||
``"eff_prefixed"`` (at the cost of a longer unique prefix).
|
||||
|
||||
:param sep:
|
||||
Optional separator to use when joining words.
|
||||
Defaults to ``" "`` (a space), but can be an empty string, a hyphen, etc.
|
||||
|
||||
:returns:
|
||||
:class:`!unicode` string containing randomly generated passphrase;
|
||||
or list of 1+ passphrases if :samp:`returns={int}` is specified.
|
||||
"""
|
||||
gen = PhraseGenerator(entropy=entropy, length=length, **kwds)
|
||||
return gen(returns)
|
||||
|
||||
#=============================================================================
|
||||
# strength measurement
|
||||
#
|
||||
# NOTE:
|
||||
# for a little while, had rough draft of password strength measurement alg here.
|
||||
# but not sure if there's value in yet another measurement algorithm,
|
||||
# that's not just duplicating the effort of libraries like zxcbn.
|
||||
# may revive it later, but for now, leaving some refs to others out there:
|
||||
# * NIST 800-63 has simple alg
|
||||
# * zxcvbn (https://tech.dropbox.com/2012/04/zxcvbn-realistic-password-strength-estimation/)
|
||||
# might also be good, and has approach similar to composite approach i was already thinking about,
|
||||
# but much more well thought out.
|
||||
# * passfault (https://github.com/c-a-m/passfault) looks thorough,
|
||||
# but may have licensing issues, plus porting to python looks like very big job :(
|
||||
# * give a look at running things through zlib - might be able to cheaply
|
||||
# catch extra redundancies.
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
547
backend/venv/Lib/site-packages/passlib/registry.py
Normal file
547
backend/venv/Lib/site-packages/passlib/registry.py
Normal file
@@ -0,0 +1,547 @@
|
||||
"""passlib.registry - registry for password hash handlers"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.exc import ExpectedTypeError, PasslibWarning
|
||||
from passlib.ifc import PasswordHash
|
||||
from passlib.utils import (
|
||||
is_crypt_handler, has_crypt as os_crypt_present,
|
||||
unix_crypt_schemes as os_crypt_schemes,
|
||||
)
|
||||
from passlib.utils.compat import unicode_or_str
|
||||
from passlib.utils.decor import memoize_single_value
|
||||
# local
|
||||
__all__ = [
|
||||
"register_crypt_handler_path",
|
||||
"register_crypt_handler",
|
||||
"get_crypt_handler",
|
||||
"list_crypt_handlers",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# proxy object used in place of 'passlib.hash' module
|
||||
#=============================================================================
|
||||
class _PasslibRegistryProxy(object):
|
||||
"""proxy module passlib.hash
|
||||
|
||||
this module is in fact an object which lazy-loads
|
||||
the requested password hash algorithm from wherever it has been stored.
|
||||
it acts as a thin wrapper around :func:`passlib.registry.get_crypt_handler`.
|
||||
"""
|
||||
__name__ = "passlib.hash"
|
||||
__package__ = None
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("_"):
|
||||
raise AttributeError("missing attribute: %r" % (attr,))
|
||||
handler = get_crypt_handler(attr, None)
|
||||
if handler:
|
||||
return handler
|
||||
else:
|
||||
raise AttributeError("unknown password hash: %r" % (attr,))
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if attr.startswith("_"):
|
||||
# writing to private attributes should behave normally.
|
||||
# (required so GAE can write to the __loader__ attribute).
|
||||
object.__setattr__(self, attr, value)
|
||||
else:
|
||||
# writing to public attributes should be treated
|
||||
# as attempting to register a handler.
|
||||
register_crypt_handler(value, _attr=attr)
|
||||
|
||||
def __repr__(self):
|
||||
return "<proxy module 'passlib.hash'>"
|
||||
|
||||
def __dir__(self):
|
||||
# this adds in lazy-loaded handler names,
|
||||
# otherwise this is the standard dir() implementation.
|
||||
attrs = set(dir(self.__class__))
|
||||
attrs.update(self.__dict__)
|
||||
attrs.update(_locations)
|
||||
return sorted(attrs)
|
||||
|
||||
# create single instance - available publically as 'passlib.hash'
|
||||
_proxy = _PasslibRegistryProxy()
|
||||
|
||||
#=============================================================================
|
||||
# internal registry state
|
||||
#=============================================================================
|
||||
|
||||
# singleton uses to detect omitted keywords
|
||||
_UNSET = object()
|
||||
|
||||
# dict mapping name -> loaded handlers (just uses proxy object's internal dict)
|
||||
_handlers = _proxy.__dict__
|
||||
|
||||
# dict mapping names -> import path for lazy loading.
|
||||
# * import path should be "module.path" or "module.path:attr"
|
||||
# * if attr omitted, "name" used as default.
|
||||
_locations = dict(
|
||||
# NOTE: this is a hardcoded list of the handlers built into passlib,
|
||||
# applications should call register_crypt_handler_path()
|
||||
apr_md5_crypt = "passlib.handlers.md5_crypt",
|
||||
argon2 = "passlib.handlers.argon2",
|
||||
atlassian_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
bcrypt = "passlib.handlers.bcrypt",
|
||||
bcrypt_sha256 = "passlib.handlers.bcrypt",
|
||||
bigcrypt = "passlib.handlers.des_crypt",
|
||||
bsd_nthash = "passlib.handlers.windows",
|
||||
bsdi_crypt = "passlib.handlers.des_crypt",
|
||||
cisco_pix = "passlib.handlers.cisco",
|
||||
cisco_asa = "passlib.handlers.cisco",
|
||||
cisco_type7 = "passlib.handlers.cisco",
|
||||
cta_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
crypt16 = "passlib.handlers.des_crypt",
|
||||
des_crypt = "passlib.handlers.des_crypt",
|
||||
django_argon2 = "passlib.handlers.django",
|
||||
django_bcrypt = "passlib.handlers.django",
|
||||
django_bcrypt_sha256 = "passlib.handlers.django",
|
||||
django_pbkdf2_sha256 = "passlib.handlers.django",
|
||||
django_pbkdf2_sha1 = "passlib.handlers.django",
|
||||
django_salted_sha1 = "passlib.handlers.django",
|
||||
django_salted_md5 = "passlib.handlers.django",
|
||||
django_des_crypt = "passlib.handlers.django",
|
||||
django_disabled = "passlib.handlers.django",
|
||||
dlitz_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
fshp = "passlib.handlers.fshp",
|
||||
grub_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
hex_md4 = "passlib.handlers.digests",
|
||||
hex_md5 = "passlib.handlers.digests",
|
||||
hex_sha1 = "passlib.handlers.digests",
|
||||
hex_sha256 = "passlib.handlers.digests",
|
||||
hex_sha512 = "passlib.handlers.digests",
|
||||
htdigest = "passlib.handlers.digests",
|
||||
ldap_plaintext = "passlib.handlers.ldap_digests",
|
||||
ldap_md5 = "passlib.handlers.ldap_digests",
|
||||
ldap_sha1 = "passlib.handlers.ldap_digests",
|
||||
ldap_hex_md5 = "passlib.handlers.roundup",
|
||||
ldap_hex_sha1 = "passlib.handlers.roundup",
|
||||
ldap_salted_md5 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha1 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha256 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha512 = "passlib.handlers.ldap_digests",
|
||||
ldap_des_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_bsdi_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_md5_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_bcrypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha1_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha256_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha512_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
ldap_pbkdf2_sha256 = "passlib.handlers.pbkdf2",
|
||||
ldap_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
lmhash = "passlib.handlers.windows",
|
||||
md5_crypt = "passlib.handlers.md5_crypt",
|
||||
msdcc = "passlib.handlers.windows",
|
||||
msdcc2 = "passlib.handlers.windows",
|
||||
mssql2000 = "passlib.handlers.mssql",
|
||||
mssql2005 = "passlib.handlers.mssql",
|
||||
mysql323 = "passlib.handlers.mysql",
|
||||
mysql41 = "passlib.handlers.mysql",
|
||||
nthash = "passlib.handlers.windows",
|
||||
oracle10 = "passlib.handlers.oracle",
|
||||
oracle11 = "passlib.handlers.oracle",
|
||||
pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
pbkdf2_sha256 = "passlib.handlers.pbkdf2",
|
||||
pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
phpass = "passlib.handlers.phpass",
|
||||
plaintext = "passlib.handlers.misc",
|
||||
postgres_md5 = "passlib.handlers.postgres",
|
||||
roundup_plaintext = "passlib.handlers.roundup",
|
||||
scram = "passlib.handlers.scram",
|
||||
scrypt = "passlib.handlers.scrypt",
|
||||
sha1_crypt = "passlib.handlers.sha1_crypt",
|
||||
sha256_crypt = "passlib.handlers.sha2_crypt",
|
||||
sha512_crypt = "passlib.handlers.sha2_crypt",
|
||||
sun_md5_crypt = "passlib.handlers.sun_md5_crypt",
|
||||
unix_disabled = "passlib.handlers.misc",
|
||||
unix_fallback = "passlib.handlers.misc",
|
||||
)
|
||||
|
||||
# master regexp for detecting valid handler names
|
||||
_name_re = re.compile("^[a-z][a-z0-9_]+[a-z0-9]$")
|
||||
|
||||
# names which aren't allowed for various reasons
|
||||
# (mainly keyword conflicts in CryptContext)
|
||||
_forbidden_names = frozenset(["onload", "policy", "context", "all",
|
||||
"default", "none", "auto"])
|
||||
|
||||
#=============================================================================
|
||||
# registry frontend functions
|
||||
#=============================================================================
|
||||
def _validate_handler_name(name):
|
||||
"""helper to validate handler name
|
||||
|
||||
:raises ValueError:
|
||||
* if empty name
|
||||
* if name not lower case
|
||||
* if name contains double underscores
|
||||
* if name is reserved (e.g. ``context``, ``all``).
|
||||
"""
|
||||
if not name:
|
||||
raise ValueError("handler name cannot be empty: %r" % (name,))
|
||||
if name.lower() != name:
|
||||
raise ValueError("name must be lower-case: %r" % (name,))
|
||||
if not _name_re.match(name):
|
||||
raise ValueError("invalid name (must be 3+ characters, "
|
||||
" begin with a-z, and contain only underscore, a-z, "
|
||||
"0-9): %r" % (name,))
|
||||
if '__' in name:
|
||||
raise ValueError("name may not contain double-underscores: %r" %
|
||||
(name,))
|
||||
if name in _forbidden_names:
|
||||
raise ValueError("that name is not allowed: %r" % (name,))
|
||||
return True
|
||||
|
||||
def register_crypt_handler_path(name, path):
|
||||
"""register location to lazy-load handler when requested.
|
||||
|
||||
custom hashes may be registered via :func:`register_crypt_handler`,
|
||||
or they may be registered by this function,
|
||||
which will delay actually importing and loading the handler
|
||||
until a call to :func:`get_crypt_handler` is made for the specified name.
|
||||
|
||||
:arg name: name of handler
|
||||
:arg path: module import path
|
||||
|
||||
the specified module path should contain a password hash handler
|
||||
called :samp:`{name}`, or the path may contain a colon,
|
||||
specifying the module and module attribute to use.
|
||||
for example, the following would cause ``get_handler("myhash")`` to look
|
||||
for a class named ``myhash`` within the ``myapp.helpers`` module::
|
||||
|
||||
>>> from passlib.registry import registry_crypt_handler_path
|
||||
>>> registry_crypt_handler_path("myhash", "myapp.helpers")
|
||||
|
||||
...while this form would cause ``get_handler("myhash")`` to look
|
||||
for a class name ``MyHash`` within the ``myapp.helpers`` module::
|
||||
|
||||
>>> from passlib.registry import registry_crypt_handler_path
|
||||
>>> registry_crypt_handler_path("myhash", "myapp.helpers:MyHash")
|
||||
"""
|
||||
# validate name
|
||||
_validate_handler_name(name)
|
||||
|
||||
# validate path
|
||||
if path.startswith("."):
|
||||
raise ValueError("path cannot start with '.'")
|
||||
if ':' in path:
|
||||
if path.count(':') > 1:
|
||||
raise ValueError("path cannot have more than one ':'")
|
||||
if path.find('.', path.index(':')) > -1:
|
||||
raise ValueError("path cannot have '.' to right of ':'")
|
||||
|
||||
# store location
|
||||
_locations[name] = path
|
||||
log.debug("registered path to %r handler: %r", name, path)
|
||||
|
||||
def register_crypt_handler(handler, force=False, _attr=None):
|
||||
"""register password hash handler.
|
||||
|
||||
this method immediately registers a handler with the internal passlib registry,
|
||||
so that it will be returned by :func:`get_crypt_handler` when requested.
|
||||
|
||||
:arg handler: the password hash handler to register
|
||||
:param force: force override of existing handler (defaults to False)
|
||||
:param _attr:
|
||||
[internal kwd] if specified, ensures ``handler.name``
|
||||
matches this value, or raises :exc:`ValueError`.
|
||||
|
||||
:raises TypeError:
|
||||
if the specified object does not appear to be a valid handler.
|
||||
|
||||
:raises ValueError:
|
||||
if the specified object's name (or other required attributes)
|
||||
contain invalid values.
|
||||
|
||||
:raises KeyError:
|
||||
if a (different) handler was already registered with
|
||||
the same name, and ``force=True`` was not specified.
|
||||
"""
|
||||
# validate handler
|
||||
if not is_crypt_handler(handler):
|
||||
raise ExpectedTypeError(handler, "password hash handler", "handler")
|
||||
if not handler:
|
||||
raise AssertionError("``bool(handler)`` must be True")
|
||||
|
||||
# validate name
|
||||
name = handler.name
|
||||
_validate_handler_name(name)
|
||||
if _attr and _attr != name:
|
||||
raise ValueError("handlers must be stored only under their own name (%r != %r)" %
|
||||
(_attr, name))
|
||||
|
||||
# check for existing handler
|
||||
other = _handlers.get(name)
|
||||
if other:
|
||||
if other is handler:
|
||||
log.debug("same %r handler already registered: %r", name, handler)
|
||||
return
|
||||
elif force:
|
||||
log.warning("overriding previously registered %r handler: %r",
|
||||
name, other)
|
||||
else:
|
||||
raise KeyError("another %r handler has already been registered: %r" %
|
||||
(name, other))
|
||||
|
||||
# register handler
|
||||
_handlers[name] = handler
|
||||
log.debug("registered %r handler: %r", name, handler)
|
||||
|
||||
def get_crypt_handler(name, default=_UNSET):
|
||||
"""return handler for specified password hash scheme.
|
||||
|
||||
this method looks up a handler for the specified scheme.
|
||||
if the handler is not already loaded,
|
||||
it checks if the location is known, and loads it first.
|
||||
|
||||
:arg name: name of handler to return
|
||||
:param default: optional default value to return if no handler with specified name is found.
|
||||
|
||||
:raises KeyError: if no handler matching that name is found, and no default specified, a KeyError will be raised.
|
||||
|
||||
:returns: handler attached to name, or default value (if specified).
|
||||
"""
|
||||
# catch invalid names before we check _handlers,
|
||||
# since it's a module dict, and exposes things like __package__, etc.
|
||||
if name.startswith("_"):
|
||||
if default is _UNSET:
|
||||
raise KeyError("invalid handler name: %r" % (name,))
|
||||
else:
|
||||
return default
|
||||
|
||||
# check if handler is already loaded
|
||||
try:
|
||||
return _handlers[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# normalize name (and if changed, check dict again)
|
||||
assert isinstance(name, unicode_or_str), "name must be string instance"
|
||||
alt = name.replace("-","_").lower()
|
||||
if alt != name:
|
||||
warn("handler names should be lower-case, and use underscores instead "
|
||||
"of hyphens: %r => %r" % (name, alt), PasslibWarning,
|
||||
stacklevel=2)
|
||||
name = alt
|
||||
|
||||
# try to load using new name
|
||||
try:
|
||||
return _handlers[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# check if lazy load mapping has been specified for this driver
|
||||
path = _locations.get(name)
|
||||
if path:
|
||||
if ':' in path:
|
||||
modname, modattr = path.split(":")
|
||||
else:
|
||||
modname, modattr = path, name
|
||||
##log.debug("loading %r handler from path: '%s:%s'", name, modname, modattr)
|
||||
|
||||
# try to load the module - any import errors indicate runtime config, usually
|
||||
# either missing package, or bad path provided to register_crypt_handler_path()
|
||||
mod = __import__(modname, fromlist=[modattr], level=0)
|
||||
|
||||
# first check if importing module triggered register_crypt_handler(),
|
||||
# (this is discouraged due to its magical implicitness)
|
||||
handler = _handlers.get(name)
|
||||
if handler:
|
||||
# XXX: issue deprecation warning here?
|
||||
assert is_crypt_handler(handler), "unexpected object: name=%r object=%r" % (name, handler)
|
||||
return handler
|
||||
|
||||
# then get real handler & register it
|
||||
handler = getattr(mod, modattr)
|
||||
register_crypt_handler(handler, _attr=name)
|
||||
return handler
|
||||
|
||||
# fail!
|
||||
if default is _UNSET:
|
||||
raise KeyError("no crypt handler found for algorithm: %r" % (name,))
|
||||
else:
|
||||
return default
|
||||
|
||||
def list_crypt_handlers(loaded_only=False):
|
||||
"""return sorted list of all known crypt handler names.
|
||||
|
||||
:param loaded_only: if ``True``, only returns names of handlers which have actually been loaded.
|
||||
|
||||
:returns: list of names of all known handlers
|
||||
"""
|
||||
names = set(_handlers)
|
||||
if not loaded_only:
|
||||
names.update(_locations)
|
||||
# strip private attrs out of namespace and sort.
|
||||
# TODO: make _handlers a separate list, so we don't have module namespace mixed in.
|
||||
return sorted(name for name in names if not name.startswith("_"))
|
||||
|
||||
# NOTE: these two functions mainly exist just for the unittests...
|
||||
|
||||
def _has_crypt_handler(name, loaded_only=False):
|
||||
"""check if handler name is known.
|
||||
|
||||
this is only useful for two cases:
|
||||
|
||||
* quickly checking if handler has already been loaded
|
||||
* checking if handler exists, without actually loading it
|
||||
|
||||
:arg name: name of handler
|
||||
:param loaded_only: if ``True``, returns False if handler exists but hasn't been loaded
|
||||
"""
|
||||
return (name in _handlers) or (not loaded_only and name in _locations)
|
||||
|
||||
def _unload_handler_name(name, locations=True):
|
||||
"""unloads a handler from the registry.
|
||||
|
||||
.. warning::
|
||||
|
||||
this is an internal function,
|
||||
used only by the unittests.
|
||||
|
||||
if loaded handler is found with specified name, it's removed.
|
||||
if path to lazy load handler is found, it's removed.
|
||||
|
||||
missing names are a noop.
|
||||
|
||||
:arg name: name of handler to unload
|
||||
:param locations: if False, won't purge registered handler locations (default True)
|
||||
"""
|
||||
if name in _handlers:
|
||||
del _handlers[name]
|
||||
if locations and name in _locations:
|
||||
del _locations[name]
|
||||
|
||||
#=============================================================================
|
||||
# inspection helpers
|
||||
#=============================================================================
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# general
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# TODO: needs UTs
|
||||
def _resolve(hasher, param="value"):
|
||||
"""
|
||||
internal helper to resolve argument to hasher object
|
||||
"""
|
||||
if is_crypt_handler(hasher):
|
||||
return hasher
|
||||
elif isinstance(hasher, unicode_or_str):
|
||||
return get_crypt_handler(hasher)
|
||||
else:
|
||||
raise exc.ExpectedTypeError(hasher, unicode_or_str, param)
|
||||
|
||||
|
||||
#: backend aliases
|
||||
ANY = "any"
|
||||
BUILTIN = "builtin"
|
||||
OS_CRYPT = "os_crypt"
|
||||
|
||||
# TODO: needs UTs
|
||||
def has_backend(hasher, backend=ANY, safe=False):
|
||||
"""
|
||||
Test if specified backend is available for hasher.
|
||||
|
||||
:param hasher:
|
||||
Hasher name or object.
|
||||
|
||||
:param backend:
|
||||
Name of backend, or ``"any"`` if any backend will do.
|
||||
For hashers without multiple backends, will pretend
|
||||
they have a single backend named ``"builtin"``.
|
||||
|
||||
:param safe:
|
||||
By default, throws error if backend is unknown.
|
||||
If ``safe=True``, will just return false value.
|
||||
|
||||
:raises ValueError:
|
||||
* if hasher name is unknown.
|
||||
* if backend is unknown to hasher, and safe=False.
|
||||
|
||||
:return:
|
||||
True if backend available, False if not available,
|
||||
and None if unknown + safe=True.
|
||||
"""
|
||||
hasher = _resolve(hasher)
|
||||
|
||||
if backend == ANY:
|
||||
if not hasattr(hasher, "get_backend"):
|
||||
# single backend, assume it's loaded
|
||||
return True
|
||||
|
||||
# multiple backends, check at least one is loadable
|
||||
try:
|
||||
hasher.get_backend()
|
||||
return True
|
||||
except exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
# test for specific backend
|
||||
if hasattr(hasher, "has_backend"):
|
||||
# multiple backends
|
||||
if safe and backend not in hasher.backends:
|
||||
return None
|
||||
return hasher.has_backend(backend)
|
||||
|
||||
# single builtin backend
|
||||
if backend == BUILTIN:
|
||||
return True
|
||||
elif safe:
|
||||
return None
|
||||
else:
|
||||
raise exc.UnknownBackendError(hasher, backend)
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# os crypt
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# TODO: move unix_crypt_schemes list to here.
|
||||
# os_crypt_schemes -- alias for unix_crypt_schemes above
|
||||
|
||||
|
||||
# TODO: needs UTs
|
||||
@memoize_single_value
|
||||
def get_supported_os_crypt_schemes():
|
||||
"""
|
||||
return tuple of schemes which :func:`crypt.crypt` natively supports.
|
||||
"""
|
||||
if not os_crypt_present:
|
||||
return ()
|
||||
cache = tuple(name for name in os_crypt_schemes
|
||||
if get_crypt_handler(name).has_backend(OS_CRYPT))
|
||||
if not cache: # pragma: no cover -- sanity check
|
||||
# no idea what OS this could happen on...
|
||||
import platform
|
||||
warn("crypt.crypt() function is present, but doesn't support any "
|
||||
"formats known to passlib! (system=%r release=%r)" %
|
||||
(platform.system(), platform.release()),
|
||||
exc.PasslibRuntimeWarning)
|
||||
return cache
|
||||
|
||||
|
||||
# TODO: needs UTs
|
||||
def has_os_crypt_support(hasher):
|
||||
"""
|
||||
check if hash is supported by native :func:`crypt.crypt` function.
|
||||
if :func:`crypt.crypt` is not present, will always return False.
|
||||
|
||||
:param hasher:
|
||||
name or hasher object.
|
||||
|
||||
:returns bool:
|
||||
True if hash format is supported by OS, else False.
|
||||
"""
|
||||
return os_crypt_present and has_backend(hasher, OS_CRYPT, safe=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1
backend/venv/Lib/site-packages/passlib/tests/__init__.py
Normal file
1
backend/venv/Lib/site-packages/passlib/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""passlib tests"""
|
||||
6
backend/venv/Lib/site-packages/passlib/tests/__main__.py
Normal file
6
backend/venv/Lib/site-packages/passlib/tests/__main__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
from nose import run
|
||||
run(
|
||||
defaultTest=os.path.dirname(__file__),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
"""helper for method in test_registry.py"""
|
||||
from passlib.registry import register_crypt_handler
|
||||
import passlib.utils.handlers as uh
|
||||
|
||||
class dummy_bad(uh.StaticHandler):
|
||||
name = "dummy_bad"
|
||||
|
||||
class alt_dummy_bad(uh.StaticHandler):
|
||||
name = "dummy_bad"
|
||||
|
||||
# NOTE: if passlib.tests is being run from symlink (e.g. via gaeunit),
|
||||
# this module may be imported a second time as test._test_bad_registry.
|
||||
# we don't want it to do anything in that case.
|
||||
if __name__.startswith("passlib.tests"):
|
||||
register_crypt_handler(alt_dummy_bad)
|
||||
67
backend/venv/Lib/site-packages/passlib/tests/backports.py
Normal file
67
backend/venv/Lib/site-packages/passlib/tests/backports.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""backports of needed unittest2 features"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
import sys
|
||||
##from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import PY26
|
||||
# local
|
||||
__all__ = [
|
||||
"TestCase",
|
||||
"unittest",
|
||||
# TODO: deprecate these exports in favor of "unittest.XXX"
|
||||
"skip", "skipIf", "skipUnless",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# import latest unittest module available
|
||||
#=============================================================================
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
if PY26:
|
||||
raise ImportError("Passlib's tests require 'unittest2' under Python 2.6 (as of Passlib 1.7)")
|
||||
# python 2.7 and python 3.2 both have unittest2 features (at least, the ones we use)
|
||||
import unittest
|
||||
|
||||
#=============================================================================
|
||||
# unittest aliases
|
||||
#=============================================================================
|
||||
skip = unittest.skip
|
||||
skipIf = unittest.skipIf
|
||||
skipUnless = unittest.skipUnless
|
||||
SkipTest = unittest.SkipTest
|
||||
|
||||
#=============================================================================
|
||||
# custom test harness
|
||||
#=============================================================================
|
||||
class TestCase(unittest.TestCase):
|
||||
"""backports a number of unittest2 features in TestCase"""
|
||||
|
||||
#===================================================================
|
||||
# backport some unittest2 names
|
||||
#===================================================================
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# backport assertRegex() alias from 3.2 to 2.7
|
||||
# was present in 2.7 under an alternate name
|
||||
#---------------------------------------------------------------
|
||||
if not hasattr(unittest.TestCase, "assertRegex"):
|
||||
assertRegex = unittest.TestCase.assertRegexpMatches
|
||||
|
||||
if not hasattr(unittest.TestCase, "assertRaisesRegex"):
|
||||
assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
9
backend/venv/Lib/site-packages/passlib/tests/sample1.cfg
Normal file
9
backend/venv/Lib/site-packages/passlib/tests/sample1.cfg
Normal file
@@ -0,0 +1,9 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all__vary_rounds = 0.1
|
||||
bsdi_crypt__default_rounds = 25001
|
||||
bsdi_crypt__max_rounds = 30001
|
||||
sha512_crypt__max_rounds = 50000
|
||||
sha512_crypt__min_rounds = 40000
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all__vary_rounds = 0.1
|
||||
bsdi_crypt__default_rounds = 25001
|
||||
bsdi_crypt__max_rounds = 30001
|
||||
sha512_crypt__max_rounds = 50000
|
||||
sha512_crypt__min_rounds = 40000
|
||||
|
||||
BIN
backend/venv/Lib/site-packages/passlib/tests/sample1c.cfg
Normal file
BIN
backend/venv/Lib/site-packages/passlib/tests/sample1c.cfg
Normal file
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all.vary_rounds = 10%%
|
||||
bsdi_crypt.max_rounds = 30000
|
||||
bsdi_crypt.default_rounds = 25000
|
||||
sha512_crypt.max_rounds = 50000
|
||||
sha512_crypt.min_rounds = 40000
|
||||
769
backend/venv/Lib/site-packages/passlib/tests/test_apache.py
Normal file
769
backend/venv/Lib/site-packages/passlib/tests/test_apache.py
Normal file
@@ -0,0 +1,769 @@
|
||||
"""tests for passlib.apache -- (c) Assurance Technologies 2008-2011"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import os
|
||||
import subprocess
|
||||
# site
|
||||
# pkg
|
||||
from passlib import apache, registry
|
||||
from passlib.exc import MissingBackendError
|
||||
from passlib.utils.compat import irange
|
||||
from passlib.tests.backports import unittest
|
||||
from passlib.tests.utils import TestCase, get_file, set_file, ensure_mtime_changed
|
||||
from passlib.utils.compat import u
|
||||
from passlib.utils import to_bytes
|
||||
from passlib.utils.handlers import to_unicode_for_identify
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
# helpers
|
||||
#=============================================================================
|
||||
|
||||
def backdate_file_mtime(path, offset=10):
|
||||
"""backdate file's mtime by specified amount"""
|
||||
# NOTE: this is used so we can test code which detects mtime changes,
|
||||
# without having to actually *pause* for that long.
|
||||
atime = os.path.getatime(path)
|
||||
mtime = os.path.getmtime(path)-offset
|
||||
os.utime(path, (atime, mtime))
|
||||
|
||||
#=============================================================================
|
||||
# detect external HTPASSWD tool
|
||||
#=============================================================================
|
||||
|
||||
|
||||
htpasswd_path = os.environ.get("PASSLIB_TEST_HTPASSWD_PATH") or "htpasswd"
|
||||
|
||||
|
||||
def _call_htpasswd(args, stdin=None):
|
||||
"""
|
||||
helper to run htpasswd cmd
|
||||
"""
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode("utf-8")
|
||||
proc = subprocess.Popen([htpasswd_path] + args, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdin=subprocess.PIPE if stdin else None)
|
||||
out, err = proc.communicate(stdin)
|
||||
rc = proc.wait()
|
||||
out = to_unicode_for_identify(out or "")
|
||||
return out, rc
|
||||
|
||||
|
||||
def _call_htpasswd_verify(path, user, password):
|
||||
"""
|
||||
wrapper for htpasswd verify
|
||||
"""
|
||||
out, rc = _call_htpasswd(["-vi", path, user], password)
|
||||
return not rc
|
||||
|
||||
|
||||
def _detect_htpasswd():
|
||||
"""
|
||||
helper to check if htpasswd is present
|
||||
"""
|
||||
try:
|
||||
out, rc = _call_htpasswd([])
|
||||
except OSError:
|
||||
# TODO: under py3, could trap the more specific FileNotFoundError
|
||||
# cmd not found
|
||||
return False, False
|
||||
# when called w/o args, it should print usage to stderr & return rc=2
|
||||
if not rc:
|
||||
log.warning("htpasswd test returned with rc=0")
|
||||
have_bcrypt = " -B " in out
|
||||
return True, have_bcrypt
|
||||
|
||||
|
||||
HAVE_HTPASSWD, HAVE_HTPASSWD_BCRYPT = _detect_htpasswd()
|
||||
|
||||
requires_htpasswd_cmd = unittest.skipUnless(HAVE_HTPASSWD, "requires `htpasswd` cmdline tool")
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# htpasswd
|
||||
#=============================================================================
|
||||
class HtpasswdFileTest(TestCase):
|
||||
"""test HtpasswdFile class"""
|
||||
descriptionPrefix = "HtpasswdFile"
|
||||
|
||||
# sample with 4 users
|
||||
sample_01 = (b'user2:2CHkkwa2AtqGs\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n')
|
||||
|
||||
# sample 1 with user 1, 2 deleted; 4 changed
|
||||
sample_02 = b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\n'
|
||||
|
||||
# sample 1 with user2 updated, user 1 first entry removed, and user 5 added
|
||||
sample_03 = (b'user2:pass2x\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n'
|
||||
b'user5:pass5\n')
|
||||
|
||||
# standalone sample with 8-bit username
|
||||
sample_04_utf8 = b'user\xc3\xa6:2CHkkwa2AtqGs\n'
|
||||
sample_04_latin1 = b'user\xe6:2CHkkwa2AtqGs\n'
|
||||
|
||||
sample_dup = b'user1:pass1\nuser1:pass2\n'
|
||||
|
||||
# sample with bcrypt & sha256_crypt hashes
|
||||
sample_05 = (b'user2:2CHkkwa2AtqGs\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n'
|
||||
b'user5:$2a$12$yktDxraxijBZ360orOyCOePFGhuis/umyPNJoL5EbsLk.s6SWdrRO\n'
|
||||
b'user6:$5$rounds=110000$cCRp/xUUGVgwR4aP$'
|
||||
b'p0.QKFS5qLNRqw1/47lXYiAcgIjJK.WjCO8nrEKuUK.\n')
|
||||
|
||||
def test_00_constructor_autoload(self):
|
||||
"""test constructor autoload"""
|
||||
# check with existing file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertTrue(ht.mtime)
|
||||
|
||||
# check changing path
|
||||
ht.path = path + "x"
|
||||
self.assertEqual(ht.path, path + "x")
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check new=True
|
||||
ht = apache.HtpasswdFile(path, new=True)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check autoload=False (deprecated alias for new=True)
|
||||
with self.assertWarningList("``autoload=False`` is deprecated"):
|
||||
ht = apache.HtpasswdFile(path, autoload=False)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check missing file
|
||||
os.remove(path)
|
||||
self.assertRaises(IOError, apache.HtpasswdFile, path)
|
||||
|
||||
# NOTE: "default_scheme" option checked via set_password() test, among others
|
||||
|
||||
def test_00_from_path(self):
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile.from_path(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
self.assertEqual(ht.path, None)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
def test_01_delete(self):
|
||||
"""test delete()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.delete("user1")) # should delete both entries
|
||||
self.assertTrue(ht.delete("user2"))
|
||||
self.assertFalse(ht.delete("user5")) # user not present
|
||||
self.assertEqual(ht.to_string(), self.sample_02)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.delete, "user:")
|
||||
|
||||
def test_01_delete_autosave(self):
|
||||
path = self.mktemp()
|
||||
sample = b'user1:pass1\nuser2:pass2\n'
|
||||
set_file(path, sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path)
|
||||
ht.delete("user1")
|
||||
self.assertEqual(get_file(path), sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path, autosave=True)
|
||||
ht.delete("user1")
|
||||
self.assertEqual(get_file(path), b"user2:pass2\n")
|
||||
|
||||
def test_02_set_password(self):
|
||||
"""test set_password()"""
|
||||
ht = apache.HtpasswdFile.from_string(
|
||||
self.sample_01, default_scheme="plaintext")
|
||||
self.assertTrue(ht.set_password("user2", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# test legacy default kwd
|
||||
with self.assertWarningList("``default`` is deprecated"):
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01, default="plaintext")
|
||||
self.assertTrue(ht.set_password("user2", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.set_password, "user:", "pass")
|
||||
|
||||
# test that legacy update() still works
|
||||
with self.assertWarningList("update\(\) is deprecated"):
|
||||
ht.update("user2", "test")
|
||||
self.assertTrue(ht.check_password("user2", "test"))
|
||||
|
||||
def test_02_set_password_autosave(self):
|
||||
path = self.mktemp()
|
||||
sample = b'user1:pass1\n'
|
||||
set_file(path, sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path)
|
||||
ht.set_password("user1", "pass2")
|
||||
self.assertEqual(get_file(path), sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path, default_scheme="plaintext", autosave=True)
|
||||
ht.set_password("user1", "pass2")
|
||||
self.assertEqual(get_file(path), b"user1:pass2\n")
|
||||
|
||||
def test_02_set_password_default_scheme(self):
|
||||
"""test set_password() -- default_scheme"""
|
||||
|
||||
def check(scheme):
|
||||
ht = apache.HtpasswdFile(default_scheme=scheme)
|
||||
ht.set_password("user1", "pass1")
|
||||
return ht.context.identify(ht.get_hash("user1"))
|
||||
|
||||
# explicit scheme
|
||||
self.assertEqual(check("sha256_crypt"), "sha256_crypt")
|
||||
self.assertEqual(check("des_crypt"), "des_crypt")
|
||||
|
||||
# unknown scheme
|
||||
self.assertRaises(KeyError, check, "xxx")
|
||||
|
||||
# alias resolution
|
||||
self.assertEqual(check("portable"), apache.htpasswd_defaults["portable"])
|
||||
self.assertEqual(check("portable_apache_22"), apache.htpasswd_defaults["portable_apache_22"])
|
||||
self.assertEqual(check("host_apache_22"), apache.htpasswd_defaults["host_apache_22"])
|
||||
|
||||
# default
|
||||
self.assertEqual(check(None), apache.htpasswd_defaults["portable_apache_22"])
|
||||
|
||||
def test_03_users(self):
|
||||
"""test users()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
ht.set_password("user5", "pass5")
|
||||
ht.delete("user3")
|
||||
ht.set_password("user3", "pass3")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user3", "user4", "user5"])
|
||||
|
||||
def test_04_check_password(self):
|
||||
"""test check_password()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_05)
|
||||
self.assertRaises(TypeError, ht.check_password, 1, 'pass9')
|
||||
self.assertTrue(ht.check_password("user9","pass9") is None)
|
||||
|
||||
# users 1..6 of sample_01 run through all the main hash formats,
|
||||
# to make sure they're recognized.
|
||||
for i in irange(1, 7):
|
||||
i = str(i)
|
||||
try:
|
||||
self.assertTrue(ht.check_password("user"+i, "pass"+i))
|
||||
self.assertTrue(ht.check_password("user"+i, "pass9") is False)
|
||||
except MissingBackendError:
|
||||
if i == "5":
|
||||
# user5 uses bcrypt, which is apparently not available right now
|
||||
continue
|
||||
raise
|
||||
|
||||
self.assertRaises(ValueError, ht.check_password, "user:", "pass")
|
||||
|
||||
# test that legacy verify() still works
|
||||
with self.assertWarningList(["verify\(\) is deprecated"]*2):
|
||||
self.assertTrue(ht.verify("user1", "pass1"))
|
||||
self.assertFalse(ht.verify("user1", "pass2"))
|
||||
|
||||
def test_05_load(self):
|
||||
"""test load()"""
|
||||
# setup empty file
|
||||
path = self.mktemp()
|
||||
set_file(path, "")
|
||||
backdate_file_mtime(path, 5)
|
||||
ha = apache.HtpasswdFile(path, default_scheme="plaintext")
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
# make changes, check load_if_changed() does nothing
|
||||
ha.set_password("user1", "pass1")
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), b"user1:pass1\n")
|
||||
|
||||
# change file
|
||||
set_file(path, self.sample_01)
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# make changes, check load() overwrites them
|
||||
ha.set_password("user5", "pass5")
|
||||
ha.load()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# test load w/ no path
|
||||
hb = apache.HtpasswdFile()
|
||||
self.assertRaises(RuntimeError, hb.load)
|
||||
self.assertRaises(RuntimeError, hb.load_if_changed)
|
||||
|
||||
# test load w/ dups and explicit path
|
||||
set_file(path, self.sample_dup)
|
||||
hc = apache.HtpasswdFile()
|
||||
hc.load(path)
|
||||
self.assertTrue(hc.check_password('user1','pass1'))
|
||||
|
||||
# NOTE: load_string() tested via from_string(), which is used all over this file
|
||||
|
||||
def test_06_save(self):
|
||||
"""test save()"""
|
||||
# load from file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile(path)
|
||||
|
||||
# make changes, check they saved
|
||||
ht.delete("user1")
|
||||
ht.delete("user2")
|
||||
ht.save()
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
# test save w/ no path
|
||||
hb = apache.HtpasswdFile(default_scheme="plaintext")
|
||||
hb.set_password("user1", "pass1")
|
||||
self.assertRaises(RuntimeError, hb.save)
|
||||
|
||||
# test save w/ explicit path
|
||||
hb.save(path)
|
||||
self.assertEqual(get_file(path), b"user1:pass1\n")
|
||||
|
||||
def test_07_encodings(self):
|
||||
"""test 'encoding' kwd"""
|
||||
# test bad encodings cause failure in constructor
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile, encoding="utf-16")
|
||||
|
||||
# check sample utf-8
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding="utf-8",
|
||||
return_unicode=True)
|
||||
self.assertEqual(ht.users(), [ u("user\u00e6") ])
|
||||
|
||||
# test deprecated encoding=None
|
||||
with self.assertWarningList("``encoding=None`` is deprecated"):
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding=None)
|
||||
self.assertEqual(ht.users(), [ b'user\xc3\xa6' ])
|
||||
|
||||
# check sample latin-1
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_latin1,
|
||||
encoding="latin-1", return_unicode=True)
|
||||
self.assertEqual(ht.users(), [ u("user\u00e6") ])
|
||||
|
||||
def test_08_get_hash(self):
|
||||
"""test get_hash()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.get_hash("user3"), b"{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=")
|
||||
self.assertEqual(ht.get_hash("user4"), b"pass4")
|
||||
self.assertEqual(ht.get_hash("user5"), None)
|
||||
|
||||
with self.assertWarningList("find\(\) is deprecated"):
|
||||
self.assertEqual(ht.find("user4"), b"pass4")
|
||||
|
||||
def test_09_to_string(self):
|
||||
"""test to_string"""
|
||||
|
||||
# check with known sample
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# test blank
|
||||
ht = apache.HtpasswdFile()
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_10_repr(self):
|
||||
ht = apache.HtpasswdFile("fakepath", autosave=True, new=True, encoding="latin-1")
|
||||
repr(ht)
|
||||
|
||||
def test_11_malformed(self):
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile.from_string,
|
||||
b'realm:user1:pass1\n')
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile.from_string,
|
||||
b'pass1\n')
|
||||
|
||||
def test_12_from_string(self):
|
||||
# forbid path kwd
|
||||
self.assertRaises(TypeError, apache.HtpasswdFile.from_string,
|
||||
b'', path=None)
|
||||
|
||||
def test_13_whitespace(self):
|
||||
"""whitespace & comment handling"""
|
||||
|
||||
# per htpasswd source (https://github.com/apache/httpd/blob/trunk/support/htpasswd.c),
|
||||
# lines that match "^\s*(#.*)?$" should be ignored
|
||||
source = to_bytes(
|
||||
'\n'
|
||||
'user2:pass2\n'
|
||||
'user4:pass4\n'
|
||||
'user7:pass7\r\n'
|
||||
' \t \n'
|
||||
'user1:pass1\n'
|
||||
' # legacy users\n'
|
||||
'#user6:pass6\n'
|
||||
'user5:pass5\n\n'
|
||||
)
|
||||
|
||||
# loading should see all users (except user6, who was commented out)
|
||||
ht = apache.HtpasswdFile.from_string(source)
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user7"])
|
||||
|
||||
# update existing user
|
||||
ht.set_hash("user4", "althash4")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user7"])
|
||||
|
||||
# add a new user
|
||||
ht.set_hash("user6", "althash6")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user6", "user7"])
|
||||
|
||||
# delete existing user
|
||||
ht.delete("user7")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user6"])
|
||||
|
||||
# re-serialization should preserve whitespace
|
||||
target = to_bytes(
|
||||
'\n'
|
||||
'user2:pass2\n'
|
||||
'user4:althash4\n'
|
||||
' \t \n'
|
||||
'user1:pass1\n'
|
||||
' # legacy users\n'
|
||||
'#user6:pass6\n'
|
||||
'user5:pass5\n'
|
||||
'user6:althash6\n'
|
||||
)
|
||||
self.assertEqual(ht.to_string(), target)
|
||||
|
||||
@requires_htpasswd_cmd
|
||||
def test_htpasswd_cmd_verify(self):
|
||||
"""
|
||||
verify "htpasswd" command can read output
|
||||
"""
|
||||
path = self.mktemp()
|
||||
ht = apache.HtpasswdFile(path=path, new=True)
|
||||
|
||||
def hash_scheme(pwd, scheme):
|
||||
return ht.context.handler(scheme).hash(pwd)
|
||||
|
||||
# base scheme
|
||||
ht.set_hash("user1", hash_scheme("password","apr_md5_crypt"))
|
||||
|
||||
# 2.2-compat scheme
|
||||
host_no_bcrypt = apache.htpasswd_defaults["host_apache_22"]
|
||||
ht.set_hash("user2", hash_scheme("password", host_no_bcrypt))
|
||||
|
||||
# 2.4-compat scheme
|
||||
host_best = apache.htpasswd_defaults["host"]
|
||||
ht.set_hash("user3", hash_scheme("password", host_best))
|
||||
|
||||
# unsupported scheme -- should always fail to verify
|
||||
ht.set_hash("user4", "$xxx$foo$bar$baz")
|
||||
|
||||
# make sure htpasswd properly recognizes hashes
|
||||
ht.save()
|
||||
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user2", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user3", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user4", "wrong"))
|
||||
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user1", "password"))
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user2", "password"))
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user3", "password"))
|
||||
|
||||
@requires_htpasswd_cmd
|
||||
@unittest.skipUnless(registry.has_backend("bcrypt"), "bcrypt support required")
|
||||
def test_htpasswd_cmd_verify_bcrypt(self):
|
||||
"""
|
||||
verify "htpasswd" command can read bcrypt format
|
||||
|
||||
this tests for regression of issue 95, where we output "$2b$" instead of "$2y$";
|
||||
fixed in v1.7.2.
|
||||
"""
|
||||
path = self.mktemp()
|
||||
ht = apache.HtpasswdFile(path=path, new=True)
|
||||
def hash_scheme(pwd, scheme):
|
||||
return ht.context.handler(scheme).hash(pwd)
|
||||
ht.set_hash("user1", hash_scheme("password", "bcrypt"))
|
||||
ht.save()
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong"))
|
||||
if HAVE_HTPASSWD_BCRYPT:
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user1", "password"))
|
||||
else:
|
||||
# apache2.2 should fail, acting like it's an unknown hash format
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "password"))
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# htdigest
|
||||
#=============================================================================
|
||||
class HtdigestFileTest(TestCase):
|
||||
"""test HtdigestFile class"""
|
||||
descriptionPrefix = "HtdigestFile"
|
||||
|
||||
# sample with 4 users
|
||||
sample_01 = (b'user2:realm:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n'
|
||||
b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')
|
||||
|
||||
# sample 1 with user 1, 2 deleted; 4 changed
|
||||
sample_02 = (b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n')
|
||||
|
||||
# sample 1 with user2 updated, user 1 first entry removed, and user 5 added
|
||||
sample_03 = (b'user2:realm:5ba6d8328943c23c64b50f8b29566059\n'
|
||||
b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n'
|
||||
b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n'
|
||||
b'user5:realm:03c55fdc6bf71552356ad401bdb9af19\n')
|
||||
|
||||
# standalone sample with 8-bit username & realm
|
||||
sample_04_utf8 = b'user\xc3\xa6:realm\xc3\xa6:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
sample_04_latin1 = b'user\xe6:realm\xe6:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
|
||||
def test_00_constructor_autoload(self):
|
||||
"""test constructor autoload"""
|
||||
# check with existing file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtdigestFile(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# check without autoload
|
||||
ht = apache.HtdigestFile(path, new=True)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
# check missing file
|
||||
os.remove(path)
|
||||
self.assertRaises(IOError, apache.HtdigestFile, path)
|
||||
|
||||
# NOTE: default_realm option checked via other tests.
|
||||
|
||||
def test_01_delete(self):
|
||||
"""test delete()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.delete("user1", "realm"))
|
||||
self.assertTrue(ht.delete("user2", "realm"))
|
||||
self.assertFalse(ht.delete("user5", "realm"))
|
||||
self.assertFalse(ht.delete("user3", "realm5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_02)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.delete, "user:", "realm")
|
||||
|
||||
# invalid realm
|
||||
self.assertRaises(ValueError, ht.delete, "user", "realm:")
|
||||
|
||||
def test_01_delete_autosave(self):
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
|
||||
ht = apache.HtdigestFile(path)
|
||||
self.assertTrue(ht.delete("user1", "realm"))
|
||||
self.assertFalse(ht.delete("user3", "realm5"))
|
||||
self.assertFalse(ht.delete("user5", "realm"))
|
||||
self.assertEqual(get_file(path), self.sample_01)
|
||||
|
||||
ht.autosave = True
|
||||
self.assertTrue(ht.delete("user2", "realm"))
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
def test_02_set_password(self):
|
||||
"""test update()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.set_password("user2", "realm", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "realm", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# default realm
|
||||
self.assertRaises(TypeError, ht.set_password, "user2", "pass3")
|
||||
ht.default_realm = "realm2"
|
||||
ht.set_password("user2", "pass3")
|
||||
ht.check_password("user2", "realm2", "pass3")
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.set_password, "user:", "realm", "pass")
|
||||
self.assertRaises(ValueError, ht.set_password, "u"*256, "realm", "pass")
|
||||
|
||||
# invalid realm
|
||||
self.assertRaises(ValueError, ht.set_password, "user", "realm:", "pass")
|
||||
self.assertRaises(ValueError, ht.set_password, "user", "r"*256, "pass")
|
||||
|
||||
# test that legacy update() still works
|
||||
with self.assertWarningList("update\(\) is deprecated"):
|
||||
ht.update("user2", "realm2", "test")
|
||||
self.assertTrue(ht.check_password("user2", "test"))
|
||||
|
||||
# TODO: test set_password autosave
|
||||
|
||||
def test_03_users(self):
|
||||
"""test users()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
ht.set_password("user5", "realm", "pass5")
|
||||
ht.delete("user3", "realm")
|
||||
ht.set_password("user3", "realm", "pass3")
|
||||
self.assertEqual(sorted(ht.users("realm")), ["user1", "user2", "user3", "user4", "user5"])
|
||||
|
||||
self.assertRaises(TypeError, ht.users, 1)
|
||||
|
||||
def test_04_check_password(self):
|
||||
"""test check_password()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertRaises(TypeError, ht.check_password, 1, 'realm', 'pass5')
|
||||
self.assertRaises(TypeError, ht.check_password, 'user', 1, 'pass5')
|
||||
self.assertIs(ht.check_password("user5", "realm","pass5"), None)
|
||||
for i in irange(1,5):
|
||||
i = str(i)
|
||||
self.assertTrue(ht.check_password("user"+i, "realm", "pass"+i))
|
||||
self.assertIs(ht.check_password("user"+i, "realm", "pass5"), False)
|
||||
|
||||
# default realm
|
||||
self.assertRaises(TypeError, ht.check_password, "user5", "pass5")
|
||||
ht.default_realm = "realm"
|
||||
self.assertTrue(ht.check_password("user1", "pass1"))
|
||||
self.assertIs(ht.check_password("user5", "pass5"), None)
|
||||
|
||||
# test that legacy verify() still works
|
||||
with self.assertWarningList(["verify\(\) is deprecated"]*2):
|
||||
self.assertTrue(ht.verify("user1", "realm", "pass1"))
|
||||
self.assertFalse(ht.verify("user1", "realm", "pass2"))
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.check_password, "user:", "realm", "pass")
|
||||
|
||||
def test_05_load(self):
|
||||
"""test load()"""
|
||||
# setup empty file
|
||||
path = self.mktemp()
|
||||
set_file(path, "")
|
||||
backdate_file_mtime(path, 5)
|
||||
ha = apache.HtdigestFile(path)
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
# make changes, check load_if_changed() does nothing
|
||||
ha.set_password("user1", "realm", "pass1")
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')
|
||||
|
||||
# change file
|
||||
set_file(path, self.sample_01)
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# make changes, check load_if_changed overwrites them
|
||||
ha.set_password("user5", "realm", "pass5")
|
||||
ha.load()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# test load w/ no path
|
||||
hb = apache.HtdigestFile()
|
||||
self.assertRaises(RuntimeError, hb.load)
|
||||
self.assertRaises(RuntimeError, hb.load_if_changed)
|
||||
|
||||
# test load w/ explicit path
|
||||
hc = apache.HtdigestFile()
|
||||
hc.load(path)
|
||||
self.assertEqual(hc.to_string(), self.sample_01)
|
||||
|
||||
# change file, test deprecated force=False kwd
|
||||
ensure_mtime_changed(path)
|
||||
set_file(path, "")
|
||||
with self.assertWarningList(r"load\(force=False\) is deprecated"):
|
||||
ha.load(force=False)
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
def test_06_save(self):
|
||||
"""test save()"""
|
||||
# load from file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtdigestFile(path)
|
||||
|
||||
# make changes, check they saved
|
||||
ht.delete("user1", "realm")
|
||||
ht.delete("user2", "realm")
|
||||
ht.save()
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
# test save w/ no path
|
||||
hb = apache.HtdigestFile()
|
||||
hb.set_password("user1", "realm", "pass1")
|
||||
self.assertRaises(RuntimeError, hb.save)
|
||||
|
||||
# test save w/ explicit path
|
||||
hb.save(path)
|
||||
self.assertEqual(get_file(path), hb.to_string())
|
||||
|
||||
def test_07_realms(self):
|
||||
"""test realms() & delete_realm()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
|
||||
self.assertEqual(ht.delete_realm("x"), 0)
|
||||
self.assertEqual(ht.realms(), ['realm'])
|
||||
|
||||
self.assertEqual(ht.delete_realm("realm"), 4)
|
||||
self.assertEqual(ht.realms(), [])
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_08_get_hash(self):
|
||||
"""test get_hash()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.get_hash("user3", "realm"), "a500bb8c02f6a9170ae46af10c898744")
|
||||
self.assertEqual(ht.get_hash("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519")
|
||||
self.assertEqual(ht.get_hash("user5", "realm"), None)
|
||||
|
||||
with self.assertWarningList("find\(\) is deprecated"):
|
||||
self.assertEqual(ht.find("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519")
|
||||
|
||||
def test_09_encodings(self):
|
||||
"""test encoding parameter"""
|
||||
# test bad encodings cause failure in constructor
|
||||
self.assertRaises(ValueError, apache.HtdigestFile, encoding="utf-16")
|
||||
|
||||
# check sample utf-8
|
||||
ht = apache.HtdigestFile.from_string(self.sample_04_utf8, encoding="utf-8", return_unicode=True)
|
||||
self.assertEqual(ht.realms(), [ u("realm\u00e6") ])
|
||||
self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ])
|
||||
|
||||
# check sample latin-1
|
||||
ht = apache.HtdigestFile.from_string(self.sample_04_latin1, encoding="latin-1", return_unicode=True)
|
||||
self.assertEqual(ht.realms(), [ u("realm\u00e6") ])
|
||||
self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ])
|
||||
|
||||
def test_10_to_string(self):
|
||||
"""test to_string()"""
|
||||
|
||||
# check sample
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# check blank
|
||||
ht = apache.HtdigestFile()
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_11_malformed(self):
|
||||
self.assertRaises(ValueError, apache.HtdigestFile.from_string,
|
||||
b'realm:user1:pass1:other\n')
|
||||
self.assertRaises(ValueError, apache.HtdigestFile.from_string,
|
||||
b'user1:pass1\n')
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
139
backend/venv/Lib/site-packages/passlib/tests/test_apps.py
Normal file
139
backend/venv/Lib/site-packages/passlib/tests/test_apps.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""test passlib.apps"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import apps, hash as hashmod
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# test predefined app contexts
|
||||
#=============================================================================
|
||||
class AppsTest(TestCase):
|
||||
"""perform general tests to make sure contexts work"""
|
||||
# NOTE: these tests are not really comprehensive,
|
||||
# since they would do little but duplicate
|
||||
# the presets in apps.py
|
||||
#
|
||||
# they mainly try to ensure no typos
|
||||
# or dynamic behavior foul-ups.
|
||||
|
||||
def test_master_context(self):
|
||||
ctx = apps.master_context
|
||||
self.assertGreater(len(ctx.schemes()), 50)
|
||||
|
||||
def test_custom_app_context(self):
|
||||
ctx = apps.custom_app_context
|
||||
self.assertEqual(ctx.schemes(), ("sha512_crypt", "sha256_crypt"))
|
||||
for hash in [
|
||||
('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'),
|
||||
('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17'),
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_django16_context(self):
|
||||
ctx = apps.django16_context
|
||||
for hash in [
|
||||
'pbkdf2_sha256$29000$ZsgquwnCyBs2$fBxRQpfKd2PIeMxtkKPy0h7SrnrN+EU/cm67aitoZ2s=',
|
||||
'sha1$0d082$cdb462ae8b6be8784ef24b20778c4d0c82d5957f',
|
||||
'md5$b887a$37767f8a745af10612ad44c80ff52e92',
|
||||
'crypt$95a6d$95x74hLDQKXI2',
|
||||
'098f6bcd4621d373cade4e832627b4f6',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertEqual(ctx.identify("!"), "django_disabled")
|
||||
self.assertFalse(ctx.verify("test", "!"))
|
||||
|
||||
def test_django_context(self):
|
||||
ctx = apps.django_context
|
||||
for hash in [
|
||||
'pbkdf2_sha256$29000$ZsgquwnCyBs2$fBxRQpfKd2PIeMxtkKPy0h7SrnrN+EU/cm67aitoZ2s=',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertEqual(ctx.identify("!"), "django_disabled")
|
||||
self.assertFalse(ctx.verify("test", "!"))
|
||||
|
||||
def test_ldap_nocrypt_context(self):
|
||||
ctx = apps.ldap_nocrypt_context
|
||||
for hash in [
|
||||
'{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F',
|
||||
'test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertIs(ctx.identify('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5'
|
||||
'n6$p4E.pdPBWx19OajgjLRiOW0itGnyxDGgMlDcOsfaI17'), None)
|
||||
|
||||
def test_ldap_context(self):
|
||||
ctx = apps.ldap_context
|
||||
for hash in [
|
||||
('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0'
|
||||
'itGnyxDGgMlDcOsfaI17'),
|
||||
'{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F',
|
||||
'test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_ldap_mysql_context(self):
|
||||
ctx = apps.mysql_context
|
||||
for hash in [
|
||||
'*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29',
|
||||
'378b243e220ca493',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_postgres_context(self):
|
||||
ctx = apps.postgres_context
|
||||
hash = 'md55d9c68c6c50ed3d02a2fcf54f63993b6'
|
||||
self.assertTrue(ctx.verify("test", hash, user='user'))
|
||||
|
||||
def test_phppass_context(self):
|
||||
ctx = apps.phpass_context
|
||||
for hash in [
|
||||
'$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..',
|
||||
'$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.',
|
||||
'_cD..aBxeRhYFJvtUvsI',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
h1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
if hashmod.bcrypt.has_backend():
|
||||
self.assertTrue(ctx.verify("test", h1))
|
||||
self.assertEqual(ctx.default_scheme(), "bcrypt")
|
||||
self.assertEqual(ctx.handler().name, "bcrypt")
|
||||
else:
|
||||
self.assertEqual(ctx.identify(h1), "bcrypt")
|
||||
self.assertEqual(ctx.default_scheme(), "phpass")
|
||||
self.assertEqual(ctx.handler().name, "phpass")
|
||||
|
||||
def test_phpbb3_context(self):
|
||||
ctx = apps.phpbb3_context
|
||||
for hash in [
|
||||
'$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..',
|
||||
'$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
self.assertTrue(ctx.hash("test").startswith("$H$"))
|
||||
|
||||
def test_roundup_context(self):
|
||||
ctx = apps.roundup_context
|
||||
for hash in [
|
||||
'{PBKDF2}9849$JMTYu3eOUSoFYExprVVqbQ$N5.gV.uR1.BTgLSvi0qyPiRlGZ0',
|
||||
'{SHA}a94a8fe5ccb19ba61c4c0873d391e987982fbbd3',
|
||||
'{CRYPT}dptOmKDriOGfU',
|
||||
'{plaintext}test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1786
backend/venv/Lib/site-packages/passlib/tests/test_context.py
Normal file
1786
backend/venv/Lib/site-packages/passlib/tests/test_context.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,743 @@
|
||||
"""tests for passlib.context
|
||||
|
||||
this file is a clone of the 1.5 test_context.py,
|
||||
containing the tests using the legacy CryptPolicy api.
|
||||
it's being preserved here to ensure the old api doesn't break
|
||||
(until Passlib 1.8, when this and the legacy api will be removed).
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import os
|
||||
import warnings
|
||||
# site
|
||||
try:
|
||||
from pkg_resources import resource_filename
|
||||
except ImportError:
|
||||
resource_filename = None
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.context import CryptContext, CryptPolicy, LazyCryptContext
|
||||
from passlib.utils import to_bytes, to_unicode
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.tests.utils import TestCase, set_file
|
||||
from passlib.registry import (register_crypt_handler_path,
|
||||
_has_crypt_handler as has_crypt_handler,
|
||||
_unload_handler_name as unload_handler_name,
|
||||
)
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class CryptPolicyTest(TestCase):
|
||||
"""test CryptPolicy object"""
|
||||
|
||||
# TODO: need to test user categories w/in all this
|
||||
|
||||
descriptionPrefix = "CryptPolicy"
|
||||
|
||||
#===================================================================
|
||||
# sample crypt policies used for testing
|
||||
#===================================================================
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 1 - average config file
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg
|
||||
sample_config_1s = """\
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all.vary_rounds = 10%%
|
||||
bsdi_crypt.max_rounds = 30000
|
||||
bsdi_crypt.default_rounds = 25000
|
||||
sha512_crypt.max_rounds = 50000
|
||||
sha512_crypt.min_rounds = 40000
|
||||
"""
|
||||
sample_config_1s_path = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "sample_config_1s.cfg"))
|
||||
if not os.path.exists(sample_config_1s_path) and resource_filename:
|
||||
# in case we're zipped up in an egg.
|
||||
sample_config_1s_path = resource_filename("passlib.tests",
|
||||
"sample_config_1s.cfg")
|
||||
|
||||
# make sure sample_config_1s uses \n linesep - tests rely on this
|
||||
assert sample_config_1s.startswith("[passlib]\nschemes")
|
||||
|
||||
sample_config_1pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__max_rounds = 30000,
|
||||
bsdi_crypt__default_rounds = 25000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds = 40000,
|
||||
)
|
||||
|
||||
sample_config_1pid = {
|
||||
"schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt",
|
||||
"default": "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
"all.vary_rounds": 0.1,
|
||||
"bsdi_crypt.max_rounds": 30000,
|
||||
"bsdi_crypt.default_rounds": 25000,
|
||||
"sha512_crypt.max_rounds": 50000,
|
||||
"sha512_crypt.min_rounds": 40000,
|
||||
}
|
||||
|
||||
sample_config_1prd = dict(
|
||||
schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt],
|
||||
default = "md5_crypt", # NOTE: passlib <= 1.5 was handler obj.
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__max_rounds = 30000,
|
||||
bsdi_crypt__default_rounds = 25000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds = 40000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 2 - partial policy & result of overlay on sample 1
|
||||
#---------------------------------------------------------------
|
||||
sample_config_2s = """\
|
||||
[passlib]
|
||||
bsdi_crypt.min_rounds = 29000
|
||||
bsdi_crypt.max_rounds = 35000
|
||||
bsdi_crypt.default_rounds = 31000
|
||||
sha512_crypt.min_rounds = 45000
|
||||
"""
|
||||
|
||||
sample_config_2pd = dict(
|
||||
# using this to test full replacement of existing options
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
# using this to test partial replacement of existing options
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
sample_config_12pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 3 - just changing default
|
||||
#---------------------------------------------------------------
|
||||
sample_config_3pd = dict(
|
||||
default="sha512_crypt",
|
||||
)
|
||||
|
||||
sample_config_123pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "sha512_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 4 - category specific
|
||||
#---------------------------------------------------------------
|
||||
sample_config_4s = """
|
||||
[passlib]
|
||||
schemes = sha512_crypt
|
||||
all.vary_rounds = 10%%
|
||||
default.sha512_crypt.max_rounds = 20000
|
||||
admin.all.vary_rounds = 5%%
|
||||
admin.sha512_crypt.max_rounds = 40000
|
||||
"""
|
||||
|
||||
sample_config_4pd = dict(
|
||||
schemes = [ "sha512_crypt" ],
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
sha512_crypt__max_rounds = 20000,
|
||||
# NOTE: not maintaining backwards compat for rendering to "5%"
|
||||
admin__all__vary_rounds = 0.05,
|
||||
admin__sha512_crypt__max_rounds = 40000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 5 - to_string & deprecation testing
|
||||
#---------------------------------------------------------------
|
||||
sample_config_5s = sample_config_1s + """\
|
||||
deprecated = des_crypt
|
||||
admin__context__deprecated = des_crypt, bsdi_crypt
|
||||
"""
|
||||
|
||||
sample_config_5pd = sample_config_1pd.copy()
|
||||
sample_config_5pd.update(
|
||||
deprecated = [ "des_crypt" ],
|
||||
admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ],
|
||||
)
|
||||
|
||||
sample_config_5pid = sample_config_1pid.copy()
|
||||
sample_config_5pid.update({
|
||||
"deprecated": "des_crypt",
|
||||
"admin.context.deprecated": "des_crypt, bsdi_crypt",
|
||||
})
|
||||
|
||||
sample_config_5prd = sample_config_1prd.copy()
|
||||
sample_config_5prd.update({
|
||||
# XXX: should deprecated return the actual handlers in this case?
|
||||
# would have to modify how policy stores info, for one.
|
||||
"deprecated": ["des_crypt"],
|
||||
"admin__context__deprecated": ["des_crypt", "bsdi_crypt"],
|
||||
})
|
||||
|
||||
#===================================================================
|
||||
# constructors
|
||||
#===================================================================
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
warnings.filterwarnings("ignore",
|
||||
r"The CryptPolicy class has been deprecated")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"the method.*hash_needs_update.*is deprecated")
|
||||
warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*")
|
||||
warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd")
|
||||
|
||||
def test_00_constructor(self):
|
||||
"""test CryptPolicy() constructor"""
|
||||
policy = CryptPolicy(**self.sample_config_1pd)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
policy = CryptPolicy(self.sample_config_1pd)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
self.assertRaises(TypeError, CryptPolicy, {}, {})
|
||||
self.assertRaises(TypeError, CryptPolicy, {}, dummy=1)
|
||||
|
||||
# check key with too many separators is rejected
|
||||
self.assertRaises(TypeError, CryptPolicy,
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
bad__key__bsdi_crypt__max_rounds = 30000,
|
||||
)
|
||||
|
||||
# check nameless handler rejected
|
||||
class nameless(uh.StaticHandler):
|
||||
name = None
|
||||
self.assertRaises(ValueError, CryptPolicy, schemes=[nameless])
|
||||
|
||||
# check scheme must be name or crypt handler
|
||||
self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler])
|
||||
|
||||
# check name conflicts are rejected
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = 'dummy_1'
|
||||
self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1])
|
||||
|
||||
# with unknown deprecated value
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=['des_crypt'],
|
||||
deprecated=['md5_crypt'])
|
||||
|
||||
# with unknown default value
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=['des_crypt'],
|
||||
default='md5_crypt')
|
||||
|
||||
def test_01_from_path_simple(self):
|
||||
"""test CryptPolicy.from_path() constructor"""
|
||||
# NOTE: this is separate so it can also run under GAE
|
||||
|
||||
# test preset stored in existing file
|
||||
path = self.sample_config_1s_path
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test if path missing
|
||||
self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx')
|
||||
|
||||
def test_01_from_path(self):
|
||||
"""test CryptPolicy.from_path() constructor with encodings"""
|
||||
path = self.mktemp()
|
||||
|
||||
# test "\n" linesep
|
||||
set_file(path, self.sample_config_1s)
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test "\r\n" linesep
|
||||
set_file(path, self.sample_config_1s.replace("\n","\r\n"))
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with custom encoding
|
||||
uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
|
||||
set_file(path, uc2)
|
||||
policy = CryptPolicy.from_path(path, encoding="utf-16")
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
def test_02_from_string(self):
|
||||
"""test CryptPolicy.from_string() constructor"""
|
||||
# test "\n" linesep
|
||||
policy = CryptPolicy.from_string(self.sample_config_1s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test "\r\n" linesep
|
||||
policy = CryptPolicy.from_string(
|
||||
self.sample_config_1s.replace("\n","\r\n"))
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with unicode
|
||||
data = to_unicode(self.sample_config_1s)
|
||||
policy = CryptPolicy.from_string(data)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with non-ascii-compatible encoding
|
||||
uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
|
||||
policy = CryptPolicy.from_string(uc2, encoding="utf-16")
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test category specific options
|
||||
policy = CryptPolicy.from_string(self.sample_config_4s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_4pd)
|
||||
|
||||
def test_03_from_source(self):
|
||||
"""test CryptPolicy.from_source() constructor"""
|
||||
# pass it a path
|
||||
policy = CryptPolicy.from_source(self.sample_config_1s_path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it a string
|
||||
policy = CryptPolicy.from_source(self.sample_config_1s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it a dict (NOTE: make a copy to detect in-place modifications)
|
||||
policy = CryptPolicy.from_source(self.sample_config_1pd.copy())
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it existing policy
|
||||
p2 = CryptPolicy.from_source(policy)
|
||||
self.assertIs(policy, p2)
|
||||
|
||||
# pass it something wrong
|
||||
self.assertRaises(TypeError, CryptPolicy.from_source, 1)
|
||||
self.assertRaises(TypeError, CryptPolicy.from_source, [])
|
||||
|
||||
def test_04_from_sources(self):
|
||||
"""test CryptPolicy.from_sources() constructor"""
|
||||
|
||||
# pass it empty list
|
||||
self.assertRaises(ValueError, CryptPolicy.from_sources, [])
|
||||
|
||||
# pass it one-element list
|
||||
policy = CryptPolicy.from_sources([self.sample_config_1s])
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass multiple sources
|
||||
policy = CryptPolicy.from_sources(
|
||||
[
|
||||
self.sample_config_1s_path,
|
||||
self.sample_config_2s,
|
||||
self.sample_config_3pd,
|
||||
])
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_123pd)
|
||||
|
||||
def test_05_replace(self):
|
||||
"""test CryptPolicy.replace() constructor"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
|
||||
# check overlaying sample 2
|
||||
p2 = p1.replace(**self.sample_config_2pd)
|
||||
self.assertEqual(p2.to_dict(), self.sample_config_12pd)
|
||||
|
||||
# check repeating overlay makes no change
|
||||
p2b = p2.replace(**self.sample_config_2pd)
|
||||
self.assertEqual(p2b.to_dict(), self.sample_config_12pd)
|
||||
|
||||
# check overlaying sample 3
|
||||
p3 = p2.replace(self.sample_config_3pd)
|
||||
self.assertEqual(p3.to_dict(), self.sample_config_123pd)
|
||||
|
||||
def test_06_forbidden(self):
|
||||
"""test CryptPolicy() forbidden kwds"""
|
||||
|
||||
# salt not allowed to be set
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
des_crypt__salt="xx",
|
||||
)
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
all__salt="xx",
|
||||
)
|
||||
|
||||
# schemes not allowed for category
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
user__context__schemes=["md5_crypt"],
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# reading
|
||||
#===================================================================
|
||||
def test_10_has_schemes(self):
|
||||
"""test has_schemes() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
self.assertTrue(p1.has_schemes())
|
||||
|
||||
p3 = CryptPolicy(**self.sample_config_3pd)
|
||||
self.assertTrue(not p3.has_schemes())
|
||||
|
||||
def test_11_iter_handlers(self):
|
||||
"""test iter_handlers() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
s = self.sample_config_1prd['schemes']
|
||||
self.assertEqual(list(p1.iter_handlers()), s)
|
||||
|
||||
p3 = CryptPolicy(**self.sample_config_3pd)
|
||||
self.assertEqual(list(p3.iter_handlers()), [])
|
||||
|
||||
def test_12_get_handler(self):
|
||||
"""test get_handler() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
|
||||
# check by name
|
||||
self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt)
|
||||
|
||||
# check by missing name
|
||||
self.assertIs(p1.get_handler("sha256_crypt"), None)
|
||||
self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True)
|
||||
|
||||
# check default
|
||||
self.assertIs(p1.get_handler(), hash.md5_crypt)
|
||||
|
||||
def test_13_get_options(self):
|
||||
"""test get_options() method"""
|
||||
|
||||
p12 = CryptPolicy(**self.sample_config_12pd)
|
||||
|
||||
self.assertEqual(p12.get_options("bsdi_crypt"),dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds = 0.1,
|
||||
min_rounds = 29000,
|
||||
max_rounds = 35000,
|
||||
default_rounds = 31000,
|
||||
))
|
||||
|
||||
self.assertEqual(p12.get_options("sha512_crypt"),dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds = 0.1,
|
||||
min_rounds = 45000,
|
||||
max_rounds = 50000,
|
||||
))
|
||||
|
||||
p4 = CryptPolicy.from_string(self.sample_config_4s)
|
||||
self.assertEqual(p4.get_options("sha512_crypt"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds=0.1,
|
||||
max_rounds=20000,
|
||||
))
|
||||
|
||||
self.assertEqual(p4.get_options("sha512_crypt", "user"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds=0.1,
|
||||
max_rounds=20000,
|
||||
))
|
||||
|
||||
self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "5%"
|
||||
vary_rounds=0.05,
|
||||
max_rounds=40000,
|
||||
))
|
||||
|
||||
def test_14_handler_is_deprecated(self):
|
||||
"""test handler_is_deprecated() method"""
|
||||
pa = CryptPolicy(**self.sample_config_1pd)
|
||||
pb = CryptPolicy(**self.sample_config_5pd)
|
||||
|
||||
self.assertFalse(pa.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt))
|
||||
self.assertFalse(pa.handler_is_deprecated("sha512_crypt"))
|
||||
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt))
|
||||
self.assertFalse(pb.handler_is_deprecated("sha512_crypt"))
|
||||
|
||||
# check categories as well
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt", "user"))
|
||||
self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user"))
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin"))
|
||||
self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin"))
|
||||
|
||||
# check deprecation is overridden per category
|
||||
pc = CryptPolicy(
|
||||
schemes=["md5_crypt", "des_crypt"],
|
||||
deprecated=["md5_crypt"],
|
||||
user__context__deprecated=["des_crypt"],
|
||||
)
|
||||
self.assertTrue(pc.handler_is_deprecated("md5_crypt"))
|
||||
self.assertFalse(pc.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pc.handler_is_deprecated("md5_crypt", "user"))
|
||||
self.assertTrue(pc.handler_is_deprecated("des_crypt", "user"))
|
||||
|
||||
def test_15_min_verify_time(self):
|
||||
"""test get_min_verify_time() method"""
|
||||
# silence deprecation warnings for min verify time
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
pa = CryptPolicy()
|
||||
self.assertEqual(pa.get_min_verify_time(), 0)
|
||||
self.assertEqual(pa.get_min_verify_time('admin'), 0)
|
||||
|
||||
pb = pa.replace(min_verify_time=.1)
|
||||
self.assertEqual(pb.get_min_verify_time(), 0)
|
||||
self.assertEqual(pb.get_min_verify_time('admin'), 0)
|
||||
|
||||
#===================================================================
|
||||
# serialization
|
||||
#===================================================================
|
||||
def test_20_iter_config(self):
|
||||
"""test iter_config() method"""
|
||||
p5 = CryptPolicy(**self.sample_config_5pd)
|
||||
self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd)
|
||||
self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd)
|
||||
self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid)
|
||||
|
||||
def test_21_to_dict(self):
|
||||
"""test to_dict() method"""
|
||||
p5 = CryptPolicy(**self.sample_config_5pd)
|
||||
self.assertEqual(p5.to_dict(), self.sample_config_5pd)
|
||||
self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd)
|
||||
|
||||
def test_22_to_string(self):
|
||||
"""test to_string() method"""
|
||||
pa = CryptPolicy(**self.sample_config_5pd)
|
||||
s = pa.to_string() # NOTE: can't compare string directly, ordering etc may not match
|
||||
pb = CryptPolicy.from_string(s)
|
||||
self.assertEqual(pb.to_dict(), self.sample_config_5pd)
|
||||
|
||||
s = pa.to_string(encoding="latin-1")
|
||||
self.assertIsInstance(s, bytes)
|
||||
|
||||
#===================================================================
|
||||
#
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# CryptContext
|
||||
#=============================================================================
|
||||
class CryptContextTest(TestCase):
|
||||
"""test CryptContext class"""
|
||||
descriptionPrefix = "CryptContext"
|
||||
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
warnings.filterwarnings("ignore",
|
||||
r"CryptContext\(\)\.replace\(\) has been deprecated.*")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"The CryptContext ``policy`` keyword has been deprecated.*")
|
||||
warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"the method.*hash_needs_update.*is deprecated")
|
||||
|
||||
#===================================================================
|
||||
# constructor
|
||||
#===================================================================
|
||||
def test_00_constructor(self):
|
||||
"""test constructor"""
|
||||
# create crypt context using handlers
|
||||
cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt])
|
||||
c,b,a = cc.policy.iter_handlers()
|
||||
self.assertIs(a, hash.des_crypt)
|
||||
self.assertIs(b, hash.bsdi_crypt)
|
||||
self.assertIs(c, hash.md5_crypt)
|
||||
|
||||
# create context using names
|
||||
cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
|
||||
c,b,a = cc.policy.iter_handlers()
|
||||
self.assertIs(a, hash.des_crypt)
|
||||
self.assertIs(b, hash.bsdi_crypt)
|
||||
self.assertIs(c, hash.md5_crypt)
|
||||
|
||||
# policy kwd
|
||||
policy = cc.policy
|
||||
cc = CryptContext(policy=policy)
|
||||
self.assertEqual(cc.to_dict(), policy.to_dict())
|
||||
|
||||
cc = CryptContext(policy=policy, default="bsdi_crypt")
|
||||
self.assertNotEqual(cc.to_dict(), policy.to_dict())
|
||||
self.assertEqual(cc.to_dict(), dict(schemes=["md5_crypt","bsdi_crypt","des_crypt"],
|
||||
default="bsdi_crypt"))
|
||||
|
||||
self.assertRaises(TypeError, setattr, cc, 'policy', None)
|
||||
self.assertRaises(TypeError, CryptContext, policy='x')
|
||||
|
||||
def test_01_replace(self):
|
||||
"""test replace()"""
|
||||
|
||||
cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
|
||||
self.assertIs(cc.policy.get_handler(), hash.md5_crypt)
|
||||
|
||||
cc2 = cc.replace()
|
||||
self.assertIsNot(cc2, cc)
|
||||
# NOTE: was not able to maintain backward compatibility with this...
|
||||
##self.assertIs(cc2.policy, cc.policy)
|
||||
|
||||
cc3 = cc.replace(default="bsdi_crypt")
|
||||
self.assertIsNot(cc3, cc)
|
||||
# NOTE: was not able to maintain backward compatibility with this...
|
||||
##self.assertIs(cc3.policy, cc.policy)
|
||||
self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt)
|
||||
|
||||
def test_02_no_handlers(self):
|
||||
"""test no handlers"""
|
||||
|
||||
# check constructor...
|
||||
cc = CryptContext()
|
||||
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
|
||||
self.assertRaises(KeyError, cc.hash, 'secret')
|
||||
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
|
||||
|
||||
# check updating policy after the fact...
|
||||
cc = CryptContext(['md5_crypt'])
|
||||
p = CryptPolicy(schemes=[])
|
||||
cc.policy = p
|
||||
|
||||
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
|
||||
self.assertRaises(KeyError, cc.hash, 'secret')
|
||||
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
|
||||
|
||||
#===================================================================
|
||||
# policy adaptation
|
||||
#===================================================================
|
||||
sample_policy_1 = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "phpass", "bsdi_crypt",
|
||||
"sha256_crypt"],
|
||||
deprecated = [ "des_crypt", ],
|
||||
default = "sha256_crypt",
|
||||
bsdi_crypt__max_rounds = 30,
|
||||
bsdi_crypt__default_rounds = 25,
|
||||
bsdi_crypt__vary_rounds = 0,
|
||||
sha256_crypt__max_rounds = 3000,
|
||||
sha256_crypt__min_rounds = 2000,
|
||||
sha256_crypt__default_rounds = 3000,
|
||||
phpass__ident = "H",
|
||||
phpass__default_rounds = 7,
|
||||
)
|
||||
|
||||
def test_12_hash_needs_update(self):
|
||||
"""test hash_needs_update() method"""
|
||||
cc = CryptContext(**self.sample_policy_1)
|
||||
|
||||
# check deprecated scheme
|
||||
self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA'))
|
||||
self.assertFalse(cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0'))
|
||||
|
||||
# check min rounds
|
||||
self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/'))
|
||||
self.assertFalse(cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8'))
|
||||
|
||||
# check max rounds
|
||||
self.assertFalse(cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.'))
|
||||
self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA'))
|
||||
|
||||
#===================================================================
|
||||
# border cases
|
||||
#===================================================================
|
||||
def test_30_nonstring_hash(self):
|
||||
"""test non-string hash values cause error"""
|
||||
warnings.filterwarnings("ignore", ".*needs_update.*'scheme' keyword is deprecated.*")
|
||||
|
||||
#
|
||||
# test hash=None or some other non-string causes TypeError
|
||||
# and that explicit-scheme code path behaves the same.
|
||||
#
|
||||
cc = CryptContext(["des_crypt"])
|
||||
for hash, kwds in [
|
||||
(None, {}),
|
||||
# NOTE: 'scheme' kwd is deprecated...
|
||||
(None, {"scheme": "des_crypt"}),
|
||||
(1, {}),
|
||||
((), {}),
|
||||
]:
|
||||
|
||||
self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds)
|
||||
|
||||
cc2 = CryptContext(["mysql323"])
|
||||
self.assertRaises(TypeError, cc2.hash_needs_update, None)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# LazyCryptContext
|
||||
#=============================================================================
|
||||
class dummy_2(uh.StaticHandler):
|
||||
name = "dummy_2"
|
||||
|
||||
class LazyCryptContextTest(TestCase):
|
||||
descriptionPrefix = "LazyCryptContext"
|
||||
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
|
||||
# make sure this isn't registered before OR after
|
||||
unload_handler_name("dummy_2")
|
||||
self.addCleanup(unload_handler_name, "dummy_2")
|
||||
|
||||
# silence some warnings
|
||||
warnings.filterwarnings("ignore",
|
||||
r"CryptContext\(\)\.replace\(\) has been deprecated")
|
||||
warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
|
||||
|
||||
def test_kwd_constructor(self):
|
||||
"""test plain kwds"""
|
||||
self.assertFalse(has_crypt_handler("dummy_2"))
|
||||
register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
|
||||
|
||||
cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
|
||||
|
||||
self.assertFalse(has_crypt_handler("dummy_2", True))
|
||||
|
||||
self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
|
||||
self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
|
||||
|
||||
self.assertTrue(has_crypt_handler("dummy_2", True))
|
||||
|
||||
def test_callable_constructor(self):
|
||||
"""test create_policy() hook, returning CryptPolicy"""
|
||||
self.assertFalse(has_crypt_handler("dummy_2"))
|
||||
register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
|
||||
|
||||
def create_policy(flag=False):
|
||||
self.assertTrue(flag)
|
||||
return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
|
||||
|
||||
cc = LazyCryptContext(create_policy=create_policy, flag=True)
|
||||
|
||||
self.assertFalse(has_crypt_handler("dummy_2", True))
|
||||
|
||||
self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
|
||||
self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
|
||||
|
||||
self.assertTrue(has_crypt_handler("dummy_2", True))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,160 @@
|
||||
"""passlib.tests -- unittests for passlib.crypto._md4"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.utils.compat import bascii_to_str, PY3, u
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
from passlib.tests.utils import TestCase, skipUnless
|
||||
# local
|
||||
__all__ = [
|
||||
"_Common_MD4_Test",
|
||||
"MD4_Builtin_Test",
|
||||
"MD4_SSL_Test",
|
||||
]
|
||||
#=============================================================================
|
||||
# test pure-python MD4 implementation
|
||||
#=============================================================================
|
||||
class _Common_MD4_Test(TestCase):
|
||||
"""common code for testing md4 backends"""
|
||||
|
||||
vectors = [
|
||||
# input -> hex digest
|
||||
# test vectors from http://www.faqs.org/rfcs/rfc1320.html - A.5
|
||||
(b"", "31d6cfe0d16ae931b73c59d7e0c089c0"),
|
||||
(b"a", "bde52cb31de33e46245e05fbdbd6fb24"),
|
||||
(b"abc", "a448017aaf21d8525fc10ae87aa6729d"),
|
||||
(b"message digest", "d9130a8164549fe818874806e1c7014b"),
|
||||
(b"abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9"),
|
||||
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4"),
|
||||
(b"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "e33b4ddc9c38f2199c3e7b164fcc0536"),
|
||||
]
|
||||
|
||||
def get_md4_const(self):
|
||||
"""
|
||||
get md4 constructor --
|
||||
overridden by subclasses to use alternate backends.
|
||||
"""
|
||||
return lookup_hash("md4").const
|
||||
|
||||
def test_attrs(self):
|
||||
"""informational attributes"""
|
||||
h = self.get_md4_const()()
|
||||
self.assertEqual(h.name, "md4")
|
||||
self.assertEqual(h.digest_size, 16)
|
||||
self.assertEqual(h.block_size, 64)
|
||||
|
||||
def test_md4_update(self):
|
||||
"""update() method"""
|
||||
md4 = self.get_md4_const()
|
||||
h = md4(b'')
|
||||
self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
|
||||
|
||||
h.update(b'a')
|
||||
self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
|
||||
|
||||
h.update(b'bcdefghijklmnopqrstuvwxyz')
|
||||
self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9")
|
||||
|
||||
if PY3:
|
||||
# reject unicode, hash should return digest of b''
|
||||
h = md4()
|
||||
self.assertRaises(TypeError, h.update, u('a'))
|
||||
self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
|
||||
else:
|
||||
# coerce unicode to ascii, hash should return digest of b'a'
|
||||
h = md4()
|
||||
h.update(u('a'))
|
||||
self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
|
||||
|
||||
def test_md4_hexdigest(self):
|
||||
"""hexdigest() method"""
|
||||
md4 = self.get_md4_const()
|
||||
for input, hex in self.vectors:
|
||||
out = md4(input).hexdigest()
|
||||
self.assertEqual(out, hex)
|
||||
|
||||
def test_md4_digest(self):
|
||||
"""digest() method"""
|
||||
md4 = self.get_md4_const()
|
||||
for input, hex in self.vectors:
|
||||
out = bascii_to_str(hexlify(md4(input).digest()))
|
||||
self.assertEqual(out, hex)
|
||||
|
||||
def test_md4_copy(self):
|
||||
"""copy() method"""
|
||||
md4 = self.get_md4_const()
|
||||
h = md4(b'abc')
|
||||
|
||||
h2 = h.copy()
|
||||
h2.update(b'def')
|
||||
self.assertEqual(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131')
|
||||
|
||||
h.update(b'ghi')
|
||||
self.assertEqual(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c')
|
||||
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# create subclasses to test various backends
|
||||
#------------------------------------------------------------------------
|
||||
|
||||
def has_native_md4(): # pragma: no cover -- runtime detection
|
||||
"""
|
||||
check if hashlib natively supports md4.
|
||||
"""
|
||||
try:
|
||||
hashlib.new("md4")
|
||||
return True
|
||||
except ValueError:
|
||||
# not supported - ssl probably missing (e.g. ironpython)
|
||||
return False
|
||||
|
||||
|
||||
@skipUnless(has_native_md4(), "hashlib lacks ssl/md4 support")
|
||||
class MD4_SSL_Test(_Common_MD4_Test):
|
||||
descriptionPrefix = "hashlib.new('md4')"
|
||||
|
||||
# NOTE: we trust ssl got md4 implementation right,
|
||||
# this is more to test our test is correct :)
|
||||
|
||||
def setUp(self):
|
||||
super(MD4_SSL_Test, self).setUp()
|
||||
|
||||
# make sure we're using right constructor.
|
||||
self.assertEqual(self.get_md4_const().__module__, "hashlib")
|
||||
|
||||
|
||||
class MD4_Builtin_Test(_Common_MD4_Test):
|
||||
descriptionPrefix = "passlib.crypto._md4.md4()"
|
||||
|
||||
def setUp(self):
|
||||
super(MD4_Builtin_Test, self).setUp()
|
||||
|
||||
if has_native_md4():
|
||||
|
||||
# Temporarily make lookup_hash() use builtin pure-python implementation,
|
||||
# by monkeypatching hashlib.new() to ensure we fall back to passlib's md4 class.
|
||||
orig = hashlib.new
|
||||
def wrapper(name, *args):
|
||||
if name == "md4":
|
||||
raise ValueError("md4 disabled for testing")
|
||||
return orig(name, *args)
|
||||
self.patchAttr(hashlib, "new", wrapper)
|
||||
|
||||
# flush cache before & after test, since we're mucking with it.
|
||||
lookup_hash.clear_cache()
|
||||
self.addCleanup(lookup_hash.clear_cache)
|
||||
|
||||
# make sure we're using right constructor.
|
||||
self.assertEqual(self.get_md4_const().__module__, "passlib.crypto._md4")
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
194
backend/venv/Lib/site-packages/passlib/tests/test_crypto_des.py
Normal file
194
backend/venv/Lib/site-packages/passlib/tests/test_crypto_des.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""passlib.tests -- unittests for passlib.crypto.des"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from functools import partial
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.utils import getrandbytes
|
||||
from passlib.tests.utils import TestCase
|
||||
|
||||
#=============================================================================
|
||||
# test DES routines
|
||||
#=============================================================================
|
||||
class DesTest(TestCase):
|
||||
descriptionPrefix = "passlib.crypto.des"
|
||||
|
||||
# test vectors taken from http://www.skepticfiles.org/faq/testdes.htm
|
||||
des_test_vectors = [
|
||||
# key, plaintext, ciphertext
|
||||
(0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
|
||||
(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7359B2163E4EDC58),
|
||||
(0x3000000000000000, 0x1000000000000001, 0x958E6E627A05557B),
|
||||
(0x1111111111111111, 0x1111111111111111, 0xF40379AB9E0EC533),
|
||||
(0x0123456789ABCDEF, 0x1111111111111111, 0x17668DFC7292532D),
|
||||
(0x1111111111111111, 0x0123456789ABCDEF, 0x8A5AE1F81AB8F2DD),
|
||||
(0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
|
||||
(0xFEDCBA9876543210, 0x0123456789ABCDEF, 0xED39D950FA74BCC4),
|
||||
(0x7CA110454A1A6E57, 0x01A1D6D039776742, 0x690F5B0D9A26939B),
|
||||
(0x0131D9619DC1376E, 0x5CD54CA83DEF57DA, 0x7A389D10354BD271),
|
||||
(0x07A1133E4A0B2686, 0x0248D43806F67172, 0x868EBB51CAB4599A),
|
||||
(0x3849674C2602319E, 0x51454B582DDF440A, 0x7178876E01F19B2A),
|
||||
(0x04B915BA43FEB5B6, 0x42FD443059577FA2, 0xAF37FB421F8C4095),
|
||||
(0x0113B970FD34F2CE, 0x059B5E0851CF143A, 0x86A560F10EC6D85B),
|
||||
(0x0170F175468FB5E6, 0x0756D8E0774761D2, 0x0CD3DA020021DC09),
|
||||
(0x43297FAD38E373FE, 0x762514B829BF486A, 0xEA676B2CB7DB2B7A),
|
||||
(0x07A7137045DA2A16, 0x3BDD119049372802, 0xDFD64A815CAF1A0F),
|
||||
(0x04689104C2FD3B2F, 0x26955F6835AF609A, 0x5C513C9C4886C088),
|
||||
(0x37D06BB516CB7546, 0x164D5E404F275232, 0x0A2AEEAE3FF4AB77),
|
||||
(0x1F08260D1AC2465E, 0x6B056E18759F5CCA, 0xEF1BF03E5DFA575A),
|
||||
(0x584023641ABA6176, 0x004BD6EF09176062, 0x88BF0DB6D70DEE56),
|
||||
(0x025816164629B007, 0x480D39006EE762F2, 0xA1F9915541020B56),
|
||||
(0x49793EBC79B3258F, 0x437540C8698F3CFA, 0x6FBF1CAFCFFD0556),
|
||||
(0x4FB05E1515AB73A7, 0x072D43A077075292, 0x2F22E49BAB7CA1AC),
|
||||
(0x49E95D6D4CA229BF, 0x02FE55778117F12A, 0x5A6B612CC26CCE4A),
|
||||
(0x018310DC409B26D6, 0x1D9D5C5018F728C2, 0x5F4C038ED12B2E41),
|
||||
(0x1C587F1C13924FEF, 0x305532286D6F295A, 0x63FAC0D034D9F793),
|
||||
(0x0101010101010101, 0x0123456789ABCDEF, 0x617B3A0CE8F07100),
|
||||
(0x1F1F1F1F0E0E0E0E, 0x0123456789ABCDEF, 0xDB958605F8C8C606),
|
||||
(0xE0FEE0FEF1FEF1FE, 0x0123456789ABCDEF, 0xEDBFD1C66C29CCC7),
|
||||
(0x0000000000000000, 0xFFFFFFFFFFFFFFFF, 0x355550B2150E2451),
|
||||
(0xFFFFFFFFFFFFFFFF, 0x0000000000000000, 0xCAAAAF4DEAF1DBAE),
|
||||
(0x0123456789ABCDEF, 0x0000000000000000, 0xD5D44FF720683D0D),
|
||||
(0xFEDCBA9876543210, 0xFFFFFFFFFFFFFFFF, 0x2A2BB008DF97C2F2),
|
||||
]
|
||||
|
||||
def test_01_expand(self):
|
||||
"""expand_des_key()"""
|
||||
from passlib.crypto.des import expand_des_key, shrink_des_key, \
|
||||
_KDATA_MASK, INT_56_MASK
|
||||
|
||||
# make sure test vectors are preserved (sans parity bits)
|
||||
# uses ints, bytes are tested under # 02
|
||||
for key1, _, _ in self.des_test_vectors:
|
||||
key2 = shrink_des_key(key1)
|
||||
key3 = expand_des_key(key2)
|
||||
# NOTE: this assumes expand_des_key() sets parity bits to 0
|
||||
self.assertEqual(key3, key1 & _KDATA_MASK)
|
||||
|
||||
# type checks
|
||||
self.assertRaises(TypeError, expand_des_key, 1.0)
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, expand_des_key, INT_56_MASK+1)
|
||||
self.assertRaises(ValueError, expand_des_key, b"\x00"*8)
|
||||
|
||||
# too small
|
||||
self.assertRaises(ValueError, expand_des_key, -1)
|
||||
self.assertRaises(ValueError, expand_des_key, b"\x00"*6)
|
||||
|
||||
def test_02_shrink(self):
|
||||
"""shrink_des_key()"""
|
||||
from passlib.crypto.des import expand_des_key, shrink_des_key, INT_64_MASK
|
||||
rng = self.getRandom()
|
||||
|
||||
# make sure reverse works for some random keys
|
||||
# uses bytes, ints are tested under # 01
|
||||
for i in range(20):
|
||||
key1 = getrandbytes(rng, 7)
|
||||
key2 = expand_des_key(key1)
|
||||
key3 = shrink_des_key(key2)
|
||||
self.assertEqual(key3, key1)
|
||||
|
||||
# type checks
|
||||
self.assertRaises(TypeError, shrink_des_key, 1.0)
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, shrink_des_key, INT_64_MASK+1)
|
||||
self.assertRaises(ValueError, shrink_des_key, b"\x00"*9)
|
||||
|
||||
# too small
|
||||
self.assertRaises(ValueError, shrink_des_key, -1)
|
||||
self.assertRaises(ValueError, shrink_des_key, b"\x00"*7)
|
||||
|
||||
def _random_parity(self, key):
|
||||
"""randomize parity bits"""
|
||||
from passlib.crypto.des import _KDATA_MASK, _KPARITY_MASK, INT_64_MASK
|
||||
rng = self.getRandom()
|
||||
return (key & _KDATA_MASK) | (rng.randint(0,INT_64_MASK) & _KPARITY_MASK)
|
||||
|
||||
def test_03_encrypt_bytes(self):
|
||||
"""des_encrypt_block()"""
|
||||
from passlib.crypto.des import (des_encrypt_block, shrink_des_key,
|
||||
_pack64, _unpack64)
|
||||
|
||||
# run through test vectors
|
||||
for key, plaintext, correct in self.des_test_vectors:
|
||||
# convert to bytes
|
||||
key = _pack64(key)
|
||||
plaintext = _pack64(plaintext)
|
||||
correct = _pack64(correct)
|
||||
|
||||
# test 64-bit key
|
||||
result = des_encrypt_block(key, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r plaintext=%r:" %
|
||||
(key, plaintext))
|
||||
|
||||
# test 56-bit version
|
||||
key2 = shrink_des_key(key)
|
||||
result = des_encrypt_block(key2, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r shrink(key)=%r plaintext=%r:" %
|
||||
(key, key2, plaintext))
|
||||
|
||||
# test with random parity bits
|
||||
for _ in range(20):
|
||||
key3 = _pack64(self._random_parity(_unpack64(key)))
|
||||
result = des_encrypt_block(key3, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
|
||||
(key, key3, plaintext))
|
||||
|
||||
# check invalid keys
|
||||
stub = b'\x00' * 8
|
||||
self.assertRaises(TypeError, des_encrypt_block, 0, stub)
|
||||
self.assertRaises(ValueError, des_encrypt_block, b'\x00'*6, stub)
|
||||
|
||||
# check invalid input
|
||||
self.assertRaises(TypeError, des_encrypt_block, stub, 0)
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, b'\x00'*7)
|
||||
|
||||
# check invalid salts
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=-1)
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=1<<24)
|
||||
|
||||
# check invalid rounds
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, 0, rounds=0)
|
||||
|
||||
def test_04_encrypt_ints(self):
|
||||
"""des_encrypt_int_block()"""
|
||||
from passlib.crypto.des import des_encrypt_int_block
|
||||
|
||||
# run through test vectors
|
||||
for key, plaintext, correct in self.des_test_vectors:
|
||||
# test 64-bit key
|
||||
result = des_encrypt_int_block(key, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r plaintext=%r:" %
|
||||
(key, plaintext))
|
||||
|
||||
# test with random parity bits
|
||||
for _ in range(20):
|
||||
key3 = self._random_parity(key)
|
||||
result = des_encrypt_int_block(key3, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
|
||||
(key, key3, plaintext))
|
||||
|
||||
# check invalid keys
|
||||
self.assertRaises(TypeError, des_encrypt_int_block, b'\x00', 0)
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, -1, 0)
|
||||
|
||||
# check invalid input
|
||||
self.assertRaises(TypeError, des_encrypt_int_block, 0, b'\x00')
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, -1)
|
||||
|
||||
# check invalid salts
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=-1)
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=1<<24)
|
||||
|
||||
# check invalid rounds
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, 0, rounds=0)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,544 @@
|
||||
"""tests for passlib.utils.(des|pbkdf2|md4)"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.exc import UnknownHashError
|
||||
from passlib.utils.compat import PY3, u, JYTHON
|
||||
from passlib.tests.utils import TestCase, TEST_MODE, skipUnless, hb
|
||||
|
||||
#=============================================================================
|
||||
# test assorted crypto helpers
|
||||
#=============================================================================
|
||||
class HashInfoTest(TestCase):
|
||||
"""test various crypto functions"""
|
||||
descriptionPrefix = "passlib.crypto.digest"
|
||||
|
||||
#: list of formats norm_hash_name() should support
|
||||
norm_hash_formats = ["hashlib", "iana"]
|
||||
|
||||
#: test cases for norm_hash_name()
|
||||
#: each row contains (iana name, hashlib name, ... 0+ unnormalized names)
|
||||
norm_hash_samples = [
|
||||
# real hashes
|
||||
("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"),
|
||||
("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"),
|
||||
("sha256", "sha-256", "SHA_256", "sha2-256"),
|
||||
("ripemd160", "ripemd-160", "SCRAM-RIPEMD-160", "RIPEmd160",
|
||||
# NOTE: there was an older "RIPEMD" & "RIPEMD-128", but python treates "RIPEMD"
|
||||
# as alias for "RIPEMD-160"
|
||||
"ripemd", "SCRAM-RIPEMD"),
|
||||
|
||||
# fake hashes (to check if fallback normalization behaves sanely)
|
||||
("sha4_256", "sha4-256", "SHA4-256", "SHA-4-256"),
|
||||
("test128", "test-128", "TEST128"),
|
||||
("test2", "test2", "TEST-2"),
|
||||
("test3_128", "test3-128", "TEST-3-128"),
|
||||
]
|
||||
|
||||
def test_norm_hash_name(self):
|
||||
"""norm_hash_name()"""
|
||||
from itertools import chain
|
||||
from passlib.crypto.digest import norm_hash_name, _known_hash_names
|
||||
|
||||
# snapshot warning state, ignore unknown hash warnings
|
||||
ctx = warnings.catch_warnings()
|
||||
ctx.__enter__()
|
||||
self.addCleanup(ctx.__exit__)
|
||||
warnings.filterwarnings("ignore", '.*unknown hash')
|
||||
warnings.filterwarnings("ignore", '.*unsupported hash')
|
||||
|
||||
# test string types
|
||||
self.assertEqual(norm_hash_name(u("MD4")), "md4")
|
||||
self.assertEqual(norm_hash_name(b"MD4"), "md4")
|
||||
self.assertRaises(TypeError, norm_hash_name, None)
|
||||
|
||||
# test selected results
|
||||
for row in chain(_known_hash_names, self.norm_hash_samples):
|
||||
for idx, format in enumerate(self.norm_hash_formats):
|
||||
correct = row[idx]
|
||||
for value in row:
|
||||
result = norm_hash_name(value, format)
|
||||
self.assertEqual(result, correct,
|
||||
"name=%r, format=%r:" % (value,
|
||||
format))
|
||||
|
||||
def test_lookup_hash_ctor(self):
|
||||
"""lookup_hash() -- constructor"""
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# invalid/unknown names should be rejected
|
||||
self.assertRaises(ValueError, lookup_hash, "new")
|
||||
self.assertRaises(ValueError, lookup_hash, "__name__")
|
||||
self.assertRaises(ValueError, lookup_hash, "sha4")
|
||||
|
||||
# 1. should return hashlib builtin if found
|
||||
self.assertEqual(lookup_hash("md5"), (hashlib.md5, 16, 64))
|
||||
|
||||
# 2. should return wrapper around hashlib.new() if found
|
||||
try:
|
||||
hashlib.new("sha")
|
||||
has_sha = True
|
||||
except ValueError:
|
||||
has_sha = False
|
||||
if has_sha:
|
||||
record = lookup_hash("sha")
|
||||
const = record[0]
|
||||
self.assertEqual(record, (const, 20, 64))
|
||||
self.assertEqual(hexlify(const(b"abc").digest()),
|
||||
b"0164b8a914cd2a5e74c4f7ff082c4d97f1edf880")
|
||||
|
||||
else:
|
||||
self.assertRaises(ValueError, lookup_hash, "sha")
|
||||
|
||||
# 3. should fall back to builtin md4
|
||||
try:
|
||||
hashlib.new("md4")
|
||||
has_md4 = True
|
||||
except ValueError:
|
||||
has_md4 = False
|
||||
record = lookup_hash("md4")
|
||||
const = record[0]
|
||||
if not has_md4:
|
||||
from passlib.crypto._md4 import md4
|
||||
self.assertIs(const, md4)
|
||||
self.assertEqual(record, (const, 16, 64))
|
||||
self.assertEqual(hexlify(const(b"abc").digest()),
|
||||
b"a448017aaf21d8525fc10ae87aa6729d")
|
||||
|
||||
# should memoize records
|
||||
self.assertIs(lookup_hash("md5"), lookup_hash("md5"))
|
||||
|
||||
def test_lookup_hash_w_unknown_name(self):
|
||||
"""lookup_hash() -- unknown hash name"""
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# unknown names should be rejected by default
|
||||
self.assertRaises(UnknownHashError, lookup_hash, "xxx256")
|
||||
|
||||
# required=False should return stub record instead
|
||||
info = lookup_hash("xxx256", required=False)
|
||||
self.assertFalse(info.supported)
|
||||
self.assertRaisesRegex(UnknownHashError, "unknown hash: 'xxx256'", info.const)
|
||||
self.assertEqual(info.name, "xxx256")
|
||||
self.assertEqual(info.digest_size, None)
|
||||
self.assertEqual(info.block_size, None)
|
||||
|
||||
# should cache stub records
|
||||
info2 = lookup_hash("xxx256", required=False)
|
||||
self.assertIs(info2, info)
|
||||
|
||||
def test_mock_fips_mode(self):
|
||||
"""
|
||||
lookup_hash() -- test set_mock_fips_mode()
|
||||
"""
|
||||
from passlib.crypto.digest import lookup_hash, _set_mock_fips_mode
|
||||
|
||||
# check if md5 is available so we can test mock helper
|
||||
if not lookup_hash("md5", required=False).supported:
|
||||
raise self.skipTest("md5 not supported")
|
||||
|
||||
# enable monkeypatch to mock up fips mode
|
||||
_set_mock_fips_mode()
|
||||
self.addCleanup(_set_mock_fips_mode, False)
|
||||
|
||||
pat = "'md5' hash disabled for fips"
|
||||
self.assertRaisesRegex(UnknownHashError, pat, lookup_hash, "md5")
|
||||
|
||||
info = lookup_hash("md5", required=False)
|
||||
self.assertRegex(info.error_text, pat)
|
||||
self.assertRaisesRegex(UnknownHashError, pat, info.const)
|
||||
|
||||
# should use hardcoded fallback info
|
||||
self.assertEqual(info.digest_size, 16)
|
||||
self.assertEqual(info.block_size, 64)
|
||||
|
||||
def test_lookup_hash_metadata(self):
|
||||
"""lookup_hash() -- metadata"""
|
||||
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# quick test of metadata using known reference - sha256
|
||||
info = lookup_hash("sha256")
|
||||
self.assertEqual(info.name, "sha256")
|
||||
self.assertEqual(info.iana_name, "sha-256")
|
||||
self.assertEqual(info.block_size, 64)
|
||||
self.assertEqual(info.digest_size, 32)
|
||||
self.assertIs(lookup_hash("SHA2-256"), info)
|
||||
|
||||
# quick test of metadata using known reference - md5
|
||||
info = lookup_hash("md5")
|
||||
self.assertEqual(info.name, "md5")
|
||||
self.assertEqual(info.iana_name, "md5")
|
||||
self.assertEqual(info.block_size, 64)
|
||||
self.assertEqual(info.digest_size, 16)
|
||||
|
||||
def test_lookup_hash_alt_types(self):
|
||||
"""lookup_hash() -- alternate types"""
|
||||
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
info = lookup_hash("sha256")
|
||||
self.assertIs(lookup_hash(info), info)
|
||||
self.assertIs(lookup_hash(info.const), info)
|
||||
|
||||
self.assertRaises(TypeError, lookup_hash, 123)
|
||||
|
||||
# TODO: write full test of compile_hmac() -- currently relying on pbkdf2_hmac() tests
|
||||
|
||||
#=============================================================================
|
||||
# test PBKDF1 support
|
||||
#=============================================================================
|
||||
class Pbkdf1_Test(TestCase):
|
||||
"""test kdf helpers"""
|
||||
descriptionPrefix = "passlib.crypto.digest.pbkdf1"
|
||||
|
||||
pbkdf1_tests = [
|
||||
# (password, salt, rounds, keylen, hash, result)
|
||||
|
||||
#
|
||||
# from http://www.di-mgt.com.au/cryptoKDFs.html
|
||||
#
|
||||
(b'password', hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
(b'password', b'salt', 1000, 0, 'md5', b''),
|
||||
(b'password', b'salt', 1000, 1, 'md5', hb('84')),
|
||||
(b'password', b'salt', 1000, 8, 'md5', hb('8475c6a8531a5d27')),
|
||||
(b'password', b'salt', 1000, 16, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
|
||||
(b'password', b'salt', 1000, None, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
|
||||
(b'password', b'salt', 1000, None, 'sha1', hb('4a8fd48e426ed081b535be5769892fa396293efb')),
|
||||
]
|
||||
if not JYTHON: # FIXME: find out why not jython, or reenable this.
|
||||
pbkdf1_tests.append(
|
||||
(b'password', b'salt', 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453'))
|
||||
)
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
for secret, salt, rounds, keylen, digest, correct in self.pbkdf1_tests:
|
||||
result = pbkdf1(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
def helper(secret=b'secret', salt=b'salt', rounds=1, keylen=1, hash='md5'):
|
||||
return pbkdf1(hash, secret, salt, rounds, keylen)
|
||||
helper()
|
||||
|
||||
# salt/secret wrong type
|
||||
self.assertRaises(TypeError, helper, secret=1)
|
||||
self.assertRaises(TypeError, helper, salt=1)
|
||||
|
||||
# non-existent hashes
|
||||
self.assertRaises(ValueError, helper, hash='missing')
|
||||
|
||||
# rounds < 1 and wrong type
|
||||
self.assertRaises(ValueError, helper, rounds=0)
|
||||
self.assertRaises(TypeError, helper, rounds='1')
|
||||
|
||||
# keylen < 0, keylen > block_size, and wrong type
|
||||
self.assertRaises(ValueError, helper, keylen=-1)
|
||||
self.assertRaises(ValueError, helper, keylen=17, hash='md5')
|
||||
self.assertRaises(TypeError, helper, keylen='1')
|
||||
|
||||
#=============================================================================
|
||||
# test PBKDF2-HMAC support
|
||||
#=============================================================================
|
||||
|
||||
# import the test subject
|
||||
from passlib.crypto.digest import pbkdf2_hmac, PBKDF2_BACKENDS
|
||||
|
||||
# NOTE: relying on tox to verify this works under all the various backends.
|
||||
class Pbkdf2Test(TestCase):
|
||||
"""test pbkdf2() support"""
|
||||
descriptionPrefix = "passlib.crypto.digest.pbkdf2_hmac() <backends: %s>" % ", ".join(PBKDF2_BACKENDS)
|
||||
|
||||
pbkdf2_test_vectors = [
|
||||
# (result, secret, salt, rounds, keylen, digest="sha1")
|
||||
|
||||
#
|
||||
# from rfc 3962
|
||||
#
|
||||
|
||||
# test case 1 / 128 bit
|
||||
(
|
||||
hb("cdedb5281bb2f801565a1122b2563515"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 1, 16
|
||||
),
|
||||
|
||||
# test case 2 / 128 bit
|
||||
(
|
||||
hb("01dbee7f4a9e243e988b62c73cda935d"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 2, 16
|
||||
),
|
||||
|
||||
# test case 2 / 256 bit
|
||||
(
|
||||
hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 2, 32
|
||||
),
|
||||
|
||||
# test case 3 / 256 bit
|
||||
(
|
||||
hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 1200, 32
|
||||
),
|
||||
|
||||
# test case 4 / 256 bit
|
||||
(
|
||||
hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"),
|
||||
b"password", b'\x12\x34\x56\x78\x78\x56\x34\x12', 5, 32
|
||||
),
|
||||
|
||||
# test case 5 / 256 bit
|
||||
(
|
||||
hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"),
|
||||
b"X"*64, b"pass phrase equals block size", 1200, 32
|
||||
),
|
||||
|
||||
# test case 6 / 256 bit
|
||||
(
|
||||
hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"),
|
||||
b"X"*65, b"pass phrase exceeds block size", 1200, 32
|
||||
),
|
||||
|
||||
#
|
||||
# from rfc 6070
|
||||
#
|
||||
(
|
||||
hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
|
||||
b"password", b"salt", 1, 20,
|
||||
),
|
||||
|
||||
(
|
||||
hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
|
||||
b"password", b"salt", 2, 20,
|
||||
),
|
||||
|
||||
(
|
||||
hb("4b007901b765489abead49d926f721d065a429c1"),
|
||||
b"password", b"salt", 4096, 20,
|
||||
),
|
||||
|
||||
# just runs too long - could enable if ALL option is set
|
||||
##(
|
||||
##
|
||||
## hb("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
|
||||
## "password", "salt", 16777216, 20,
|
||||
##),
|
||||
|
||||
(
|
||||
hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
|
||||
b"passwordPASSWORDpassword",
|
||||
b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
|
||||
4096, 25,
|
||||
),
|
||||
|
||||
(
|
||||
hb("56fa6aa75548099dcc37d7f03425e0c3"),
|
||||
b"pass\00word", b"sa\00lt", 4096, 16,
|
||||
),
|
||||
|
||||
#
|
||||
# from example in http://grub.enbug.org/Authentication
|
||||
#
|
||||
(
|
||||
hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED"
|
||||
"97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC"
|
||||
"6C29E293F0A0"),
|
||||
b"hello",
|
||||
hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71"
|
||||
"784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073"
|
||||
"994D79080136"),
|
||||
10000, 64, "sha512"
|
||||
),
|
||||
|
||||
#
|
||||
# test vectors from fastpbkdf2 <https://github.com/ctz/fastpbkdf2/blob/master/testdata.py>
|
||||
#
|
||||
(
|
||||
hb('55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc'
|
||||
'49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783'),
|
||||
b'passwd', b'salt', 1, 64, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56'
|
||||
'a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d'),
|
||||
b'Password', b'NaCl', 80000, 64, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b'),
|
||||
b'password', b'salt', 1, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43'),
|
||||
b'password', b'salt', 2, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a'),
|
||||
b'password', b'salt', 4096, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c'
|
||||
'635518c7dac47e9'),
|
||||
b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt',
|
||||
4096, 40, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('9e83f279c040f2a11aa4a02b24c418f2d3cb39560c9627fa4f47e3bcc2897c3d'),
|
||||
b'', b'salt', 1024, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('ea5808411eb0c7e830deab55096cee582761e22a9bc034e3ece925225b07bf46'),
|
||||
b'password', b'', 1024, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('89b69d0516f829893c696226650a8687'),
|
||||
b'pass\x00word', b'sa\x00lt', 4096, 16, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252'),
|
||||
b'password', b'salt', 1, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f0040713f18aefdb866d53c'),
|
||||
b'password', b'salt', 2, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('d197b1b33db0143e018b12f3d1d1479e6cdebdcc97c5c0f87f6902e072f457b5'),
|
||||
b'password', b'salt', 4096, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('6e23f27638084b0f7ea1734e0d9841f55dd29ea60a834466f3396bac801fac1eeb'
|
||||
'63802f03a0b4acd7603e3699c8b74437be83ff01ad7f55dac1ef60f4d56480c35e'
|
||||
'e68fd52c6936'),
|
||||
b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt',
|
||||
1, 72, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('0c60c80f961f0e71f3a9b524af6012062fe037a6'),
|
||||
b'password', b'salt', 1, 20, 'sha1',
|
||||
),
|
||||
|
||||
#
|
||||
# custom tests
|
||||
#
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc222812'),
|
||||
b"secret", b"salt", 10, 16, "sha1",
|
||||
),
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc2228127872da6d'),
|
||||
b"secret", b"salt", 10, None, "sha1",
|
||||
),
|
||||
(
|
||||
hb('b1d5485772e6f76d5ebdc11b38d3eff0a5b2bd50dc11f937e86ecacd0cd40d1b'
|
||||
'9113e0734e3b76a3'),
|
||||
b"secret", b"salt", 62, 40, "md5",
|
||||
),
|
||||
(
|
||||
hb('ea014cc01f78d3883cac364bb5d054e2be238fb0b6081795a9d84512126e3129'
|
||||
'062104d2183464c4'),
|
||||
b"secret", b"salt", 62, 40, "md4",
|
||||
),
|
||||
]
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
for row in self.pbkdf2_test_vectors:
|
||||
correct, secret, salt, rounds, keylen = row[:5]
|
||||
digest = row[5] if len(row) == 6 else "sha1"
|
||||
result = pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_backends(self):
|
||||
"""verify expected backends are present"""
|
||||
from passlib.crypto.digest import PBKDF2_BACKENDS
|
||||
|
||||
# check for fastpbkdf2
|
||||
try:
|
||||
import fastpbkdf2
|
||||
has_fastpbkdf2 = True
|
||||
except ImportError:
|
||||
has_fastpbkdf2 = False
|
||||
self.assertEqual("fastpbkdf2" in PBKDF2_BACKENDS, has_fastpbkdf2)
|
||||
|
||||
# check for hashlib
|
||||
try:
|
||||
from hashlib import pbkdf2_hmac
|
||||
has_hashlib_ssl = pbkdf2_hmac.__module__ != "hashlib"
|
||||
except ImportError:
|
||||
has_hashlib_ssl = False
|
||||
self.assertEqual("hashlib-ssl" in PBKDF2_BACKENDS, has_hashlib_ssl)
|
||||
|
||||
# check for appropriate builtin
|
||||
from passlib.utils.compat import PY3
|
||||
if PY3:
|
||||
self.assertIn("builtin-from-bytes", PBKDF2_BACKENDS)
|
||||
else:
|
||||
# XXX: only true as long as this is preferred over hexlify
|
||||
self.assertIn("builtin-unpack", PBKDF2_BACKENDS)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"):
|
||||
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
helper()
|
||||
|
||||
# invalid rounds
|
||||
self.assertRaises(ValueError, helper, rounds=-1)
|
||||
self.assertRaises(ValueError, helper, rounds=0)
|
||||
self.assertRaises(TypeError, helper, rounds='x')
|
||||
|
||||
# invalid keylen
|
||||
helper(keylen=1)
|
||||
self.assertRaises(ValueError, helper, keylen=-1)
|
||||
self.assertRaises(ValueError, helper, keylen=0)
|
||||
# NOTE: hashlib actually throws error for keylen>=MAX_SINT32,
|
||||
# but pbkdf2 forbids anything > MAX_UINT32 * digest_size
|
||||
self.assertRaises(OverflowError, helper, keylen=20*(2**32-1)+1)
|
||||
self.assertRaises(TypeError, helper, keylen='x')
|
||||
|
||||
# invalid secret/salt type
|
||||
self.assertRaises(TypeError, helper, salt=5)
|
||||
self.assertRaises(TypeError, helper, secret=5)
|
||||
|
||||
# invalid hash
|
||||
self.assertRaises(ValueError, helper, digest='foo')
|
||||
self.assertRaises(TypeError, helper, digest=5)
|
||||
|
||||
def test_default_keylen(self):
|
||||
"""test keylen==None"""
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"):
|
||||
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(len(helper(digest='sha1')), 20)
|
||||
self.assertEqual(len(helper(digest='sha256')), 32)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,634 @@
|
||||
"""tests for passlib.utils.scrypt"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import struct
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
# site
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils import getrandbytes
|
||||
from passlib.utils.compat import PYPY, u, bascii_to_str
|
||||
from passlib.utils.decor import classproperty
|
||||
from passlib.tests.utils import TestCase, skipUnless, TEST_MODE, hb
|
||||
# subject
|
||||
from passlib.crypto import scrypt as scrypt_mod
|
||||
# local
|
||||
__all__ = [
|
||||
"ScryptEngineTest",
|
||||
"BuiltinScryptTest",
|
||||
"FastScryptTest",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# support functions
|
||||
#=============================================================================
|
||||
def hexstr(data):
|
||||
"""return bytes as hex str"""
|
||||
return bascii_to_str(hexlify(data))
|
||||
|
||||
def unpack_uint32_list(data, check_count=None):
|
||||
"""unpack bytes as list of uint32 values"""
|
||||
count = len(data) // 4
|
||||
assert check_count is None or check_count == count
|
||||
return struct.unpack("<%dI" % count, data)
|
||||
|
||||
def seed_bytes(seed, count):
|
||||
"""
|
||||
generate random reference bytes from specified seed.
|
||||
used to generate some predictable test vectors.
|
||||
"""
|
||||
if hasattr(seed, "encode"):
|
||||
seed = seed.encode("ascii")
|
||||
buf = b''
|
||||
i = 0
|
||||
while len(buf) < count:
|
||||
buf += hashlib.sha256(seed + struct.pack("<I", i)).digest()
|
||||
i += 1
|
||||
return buf[:count]
|
||||
|
||||
#=============================================================================
|
||||
# test builtin engine's internals
|
||||
#=============================================================================
|
||||
class ScryptEngineTest(TestCase):
|
||||
descriptionPrefix = "passlib.crypto.scrypt._builtin"
|
||||
|
||||
def test_smix(self):
|
||||
"""smix()"""
|
||||
from passlib.crypto.scrypt._builtin import ScryptEngine
|
||||
rng = self.getRandom()
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 9)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
input = hb("""
|
||||
f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
|
||||
77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
|
||||
89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
|
||||
09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
|
||||
89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
|
||||
cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
|
||||
67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
|
||||
7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
|
||||
""")
|
||||
|
||||
output = hb("""
|
||||
79 cc c1 93 62 9d eb ca 04 7f 0b 70 60 4b f6 b6
|
||||
2c e3 dd 4a 96 26 e3 55 fa fc 61 98 e6 ea 2b 46
|
||||
d5 84 13 67 3b 99 b0 29 d6 65 c3 57 60 1f b4 26
|
||||
a0 b2 f4 bb a2 00 ee 9f 0a 43 d1 9b 57 1a 9c 71
|
||||
ef 11 42 e6 5d 5a 26 6f dd ca 83 2c e5 9f aa 7c
|
||||
ac 0b 9c f1 be 2b ff ca 30 0d 01 ee 38 76 19 c4
|
||||
ae 12 fd 44 38 f2 03 a0 e4 e1 c4 7e c3 14 86 1f
|
||||
4e 90 87 cb 33 39 6a 68 73 e8 f9 d2 53 9a 4b 8e
|
||||
""")
|
||||
|
||||
# NOTE: p value should be ignored, so testing w/ random inputs.
|
||||
engine = ScryptEngine(n=16, r=1, p=rng.randint(1, 1023))
|
||||
self.assertEqual(engine.smix(input), output)
|
||||
|
||||
def test_bmix(self):
|
||||
"""bmix()"""
|
||||
from passlib.crypto.scrypt._builtin import ScryptEngine
|
||||
rng = self.getRandom()
|
||||
|
||||
# NOTE: bmix() call signature currently takes in list of 32*r uint32 elements,
|
||||
# and writes to target buffer of same size.
|
||||
|
||||
def check_bmix(r, input, output):
|
||||
"""helper to check bmix() output against reference"""
|
||||
# NOTE: * n & p values should be ignored, so testing w/ rng inputs.
|
||||
# * target buffer contents should be ignored, so testing w/ random inputs.
|
||||
engine = ScryptEngine(r=r, n=1 << rng.randint(1, 32), p=rng.randint(1, 1023))
|
||||
target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
|
||||
engine.bmix(input, target)
|
||||
self.assertEqual(target, list(output))
|
||||
|
||||
# ScryptEngine special-cases bmix() for r=1.
|
||||
# this removes the special case patching, so we also test original bmix function.
|
||||
if r == 1:
|
||||
del engine.bmix
|
||||
target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
|
||||
engine.bmix(input, target)
|
||||
self.assertEqual(target, list(output))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 8)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: this pair corresponds to the first input & output pair
|
||||
# from the test vector in test_smix(), above.
|
||||
# NOTE: original reference lists input & output as two separate 64 byte blocks.
|
||||
# current internal representation used by bmix() uses single 2*r*16 array of uint32,
|
||||
# combining all the B blocks into a single flat array.
|
||||
input = unpack_uint32_list(hb("""
|
||||
f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
|
||||
77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
|
||||
89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
|
||||
09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
|
||||
|
||||
89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
|
||||
cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
|
||||
67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
|
||||
7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
|
||||
"""), 32)
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
|
||||
04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
|
||||
b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
|
||||
e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
|
||||
|
||||
20 ed c9 75 32 38 81 a8 05 40 f6 4c 16 2d cd 3c
|
||||
21 07 7c fe 5f 8d 5f e2 b1 a4 16 8f 95 36 78 b7
|
||||
7d 3b 3d 80 3b 60 e4 ab 92 09 96 e5 9b 4d 53 b6
|
||||
5d 2a 22 58 77 d5 ed f5 84 2c b9 f1 4e ef e4 25
|
||||
"""), 32)
|
||||
|
||||
# check_bmix(1, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=2
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 2
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=2", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
ba240854954f4585f3d0573321f10beee96f12acdc1feb498131e40512934fd7
|
||||
43e8139c17d0743c89d09ac8c3582c273c60ab85db63e410d049a9e17a42c6a1
|
||||
|
||||
6c7831b11bf370266afdaff997ae1286920dea1dedf0f4a1795ba710ba9017f1
|
||||
a374400766f13ebd8969362de2d153965e9941bdde0768fa5b53e8522f116ce0
|
||||
|
||||
d14774afb88f46cd919cba4bc64af7fca0ecb8732d1fc2191e0d7d1b6475cb2e
|
||||
e3db789ee478d056c4eb6c6e28b99043602dbb8dfb60c6e048bf90719da8d57d
|
||||
|
||||
3c42250e40ab79a1ada6aae9299b9790f767f54f388d024a1465b30cbbe9eb89
|
||||
002d4f5c215c4259fac4d083bac5fb0b47463747d568f40bb7fa87c42f0a1dc1
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=3
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 3
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=3", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
11ddd8cf60c61f59a6e5b128239bdc77b464101312c88bd1ccf6be6e75461b29
|
||||
7370d4770c904d0b09c402573cf409bf2db47b91ba87d5a3de469df8fb7a003c
|
||||
|
||||
95a66af96dbdd88beddc8df51a2f72a6f588d67e7926e9c2b676c875da13161e
|
||||
b6262adac39e6b3003e9a6fbc8c1a6ecf1e227c03bc0af3e5f8736c339b14f84
|
||||
|
||||
c7ae5b89f5e16d0faf8983551165f4bb712d97e4f81426e6b78eb63892d3ff54
|
||||
80bf406c98e479496d0f76d23d728e67d2a3d2cdbc4a932be6db36dc37c60209
|
||||
|
||||
a5ca76ca2d2979f995f73fe8182eefa1ce0ba0d4fc27d5b827cb8e67edd6552f
|
||||
00a5b3ab6b371bd985a158e728011314eb77f32ade619b3162d7b5078a19886c
|
||||
|
||||
06f12bc8ae8afa46489e5b0239954d5216967c928982984101e4a88bae1f60ae
|
||||
3f8a456e169a8a1c7450e7955b8a13a202382ae19d41ce8ef8b6a15eeef569a7
|
||||
|
||||
20f54c48e44cb5543dda032c1a50d5ddf2919030624978704eb8db0290052a1f
|
||||
5d88989b0ef931b6befcc09e9d5162320e71e80b89862de7e2f0b6c67229b93f
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=4
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 4
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=4", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
803fcf7362702f30ef43250f20bc6b1b8925bf5c4a0f5a14bbfd90edce545997
|
||||
3047bd81655f72588ca93f5c2f4128adaea805e0705a35e14417101fdb1c498c
|
||||
|
||||
33bec6f4e5950d66098da8469f3fe633f9a17617c0ea21275185697c0e4608f7
|
||||
e6b38b7ec71704a810424637e2c296ca30d9cbf8172a71a266e0393deccf98eb
|
||||
|
||||
abc430d5f144eb0805308c38522f2973b7b6a48498851e4c762874497da76b88
|
||||
b769b471fbfc144c0e8e859b2b3f5a11f51604d268c8fd28db55dff79832741a
|
||||
|
||||
1ac0dfdaff10f0ada0d93d3b1f13062e4107c640c51df05f4110bdda15f51b53
|
||||
3a75bfe56489a6d8463440c78fb8c0794135e38591bdc5fa6cec96a124178a4a
|
||||
|
||||
d1a976e985bfe13d2b4af51bd0fc36dd4cfc3af08efe033b2323a235205dc43d
|
||||
e57778a492153f9527338b3f6f5493a03d8015cd69737ee5096ad4cbe660b10f
|
||||
|
||||
b75b1595ddc96e3748f5c9f61fba1ef1f0c51b6ceef8bbfcc34b46088652e6f7
|
||||
edab61521cbad6e69b77be30c9c97ea04a4af359dafc205c7878cc9a6c5d122f
|
||||
|
||||
8d77f3cbe65ab14c3c491ef94ecb3f5d2c2dd13027ea4c3606262bb3c9ce46e7
|
||||
dc424729dc75f6e8f06096c0ad8ad4d549c42f0cad9b33cb95d10fb3cadba27c
|
||||
|
||||
5f4bf0c1ac677c23ba23b64f56afc3546e62d96f96b58d7afc5029f8168cbab4
|
||||
533fd29fc83c8d2a32b81923992e4938281334e0c3694f0ee56f8ff7df7dc4ae
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
def test_salsa(self):
|
||||
"""salsa20()"""
|
||||
from passlib.crypto.scrypt._builtin import salsa20
|
||||
|
||||
# NOTE: salsa2() currently operates on lists of 16 uint32 elements,
|
||||
# which is what unpack_uint32_list(hb(() is for...
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 7)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: this pair corresponds to the first input & output pair
|
||||
# from the test vector in test_bmix(), above.
|
||||
|
||||
input = unpack_uint32_list(hb("""
|
||||
7e 87 9a 21 4f 3e c9 86 7c a9 40 e6 41 71 8f 26
|
||||
ba ee 55 5b 8c 61 c1 b5 0d f8 46 11 6d cd 3b 1d
|
||||
ee 24 f3 19 df 9b 3d 85 14 12 1e 4b 5a c5 aa 32
|
||||
76 02 1d 29 09 c7 48 29 ed eb c6 8d b8 b8 c2 5e
|
||||
"""))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
|
||||
04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
|
||||
b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
|
||||
e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
|
||||
"""))
|
||||
self.assertEqual(salsa20(input), output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector,
|
||||
# used to check for salsa20() breakage while optimizing _gen_files output.
|
||||
#-----------------------------------------------------------------------
|
||||
input = list(range(16))
|
||||
output = unpack_uint32_list(hb("""
|
||||
f518dd4fb98883e0a87954c05cab867083bb8808552810752285a05822f56c16
|
||||
9d4a2a0fd2142523d758c60b36411b682d53860514b871d27659042a5afa475d
|
||||
"""))
|
||||
self.assertEqual(salsa20(input), output)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# test scrypt
|
||||
#=============================================================================
|
||||
class _CommonScryptTest(TestCase):
|
||||
"""
|
||||
base class for testing various scrypt backends against same set of reference vectors.
|
||||
"""
|
||||
#=============================================================================
|
||||
# class attrs
|
||||
#=============================================================================
|
||||
|
||||
@classproperty
|
||||
def descriptionPrefix(cls):
|
||||
return "passlib.utils.scrypt.scrypt() <%s backend>" % cls.backend
|
||||
backend = None
|
||||
|
||||
#=============================================================================
|
||||
# setup
|
||||
#=============================================================================
|
||||
def setUp(self):
|
||||
assert self.backend
|
||||
scrypt_mod._set_backend(self.backend)
|
||||
super(_CommonScryptTest, self).setUp()
|
||||
|
||||
#=============================================================================
|
||||
# reference vectors
|
||||
#=============================================================================
|
||||
|
||||
reference_vectors = [
|
||||
# entry format: (secret, salt, n, r, p, keylen, result)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# test vectors from scrypt whitepaper --
|
||||
# http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b
|
||||
#
|
||||
# also present in (expired) scrypt rfc draft --
|
||||
# https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 11
|
||||
#------------------------------------------------------------------------
|
||||
("", "", 16, 1, 1, 64, hb("""
|
||||
77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
|
||||
f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
|
||||
fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
|
||||
e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06
|
||||
""")),
|
||||
|
||||
("password", "NaCl", 1024, 8, 16, 64, hb("""
|
||||
fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
|
||||
7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
|
||||
2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
|
||||
c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40
|
||||
""")),
|
||||
|
||||
# NOTE: the following are skipped for all backends unless TEST_MODE="full"
|
||||
|
||||
("pleaseletmein", "SodiumChloride", 16384, 8, 1, 64, hb("""
|
||||
70 23 bd cb 3a fd 73 48 46 1c 06 cd 81 fd 38 eb
|
||||
fd a8 fb ba 90 4f 8e 3e a9 b5 43 f6 54 5d a1 f2
|
||||
d5 43 29 55 61 3f 0f cf 62 d4 97 05 24 2a 9a f9
|
||||
e6 1e 85 dc 0d 65 1e 40 df cf 01 7b 45 57 58 87
|
||||
""")),
|
||||
|
||||
# NOTE: the following are always skipped for the builtin backend,
|
||||
# (just takes too long to be worth it)
|
||||
|
||||
("pleaseletmein", "SodiumChloride", 1048576, 8, 1, 64, hb("""
|
||||
21 01 cb 9b 6a 51 1a ae ad db be 09 cf 70 f8 81
|
||||
ec 56 8d 57 4a 2f fd 4d ab e5 ee 98 20 ad aa 47
|
||||
8e 56 fd 8f 4b a5 d0 9f fa 1c 6d 92 7c 40 f4 c3
|
||||
37 30 40 49 e8 a9 52 fb cb f4 5c 6f a7 7a 41 a4
|
||||
""")),
|
||||
]
|
||||
|
||||
def test_reference_vectors(self):
|
||||
"""reference vectors"""
|
||||
for secret, salt, n, r, p, keylen, result in self.reference_vectors:
|
||||
if n >= 1024 and TEST_MODE(max="default"):
|
||||
# skip large values unless we're running full test suite
|
||||
continue
|
||||
if n > 16384 and self.backend == "builtin":
|
||||
# skip largest vector for builtin, takes WAAY too long
|
||||
# (46s under pypy, ~5m under cpython)
|
||||
continue
|
||||
log.debug("scrypt reference vector: %r %r n=%r r=%r p=%r", secret, salt, n, r, p)
|
||||
self.assertEqual(scrypt_mod.scrypt(secret, salt, n, r, p, keylen), result)
|
||||
|
||||
#=============================================================================
|
||||
# fuzz testing
|
||||
#=============================================================================
|
||||
|
||||
_already_tested_others = None
|
||||
|
||||
def test_other_backends(self):
|
||||
"""compare output to other backends"""
|
||||
# only run once, since test is symetric.
|
||||
# maybe this means it should go somewhere else?
|
||||
if self._already_tested_others:
|
||||
raise self.skipTest("already run under %r backend test" % self._already_tested_others)
|
||||
self._already_tested_others = self.backend
|
||||
rng = self.getRandom()
|
||||
|
||||
# get available backends
|
||||
orig = scrypt_mod.backend
|
||||
available = set(name for name in scrypt_mod.backend_values
|
||||
if scrypt_mod._has_backend(name))
|
||||
scrypt_mod._set_backend(orig)
|
||||
available.discard(self.backend)
|
||||
if not available:
|
||||
raise self.skipTest("no other backends found")
|
||||
|
||||
warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
|
||||
category=exc.PasslibSecurityWarning)
|
||||
|
||||
# generate some random options, and cross-check output
|
||||
for _ in range(10):
|
||||
# NOTE: keeping values low due to builtin test
|
||||
secret = getrandbytes(rng, rng.randint(0, 64))
|
||||
salt = getrandbytes(rng, rng.randint(0, 64))
|
||||
n = 1 << rng.randint(1, 10)
|
||||
r = rng.randint(1, 8)
|
||||
p = rng.randint(1, 3)
|
||||
ks = rng.randint(1, 64)
|
||||
previous = None
|
||||
backends = set()
|
||||
for name in available:
|
||||
scrypt_mod._set_backend(name)
|
||||
self.assertNotIn(scrypt_mod._scrypt, backends)
|
||||
backends.add(scrypt_mod._scrypt)
|
||||
result = hexstr(scrypt_mod.scrypt(secret, salt, n, r, p, ks))
|
||||
self.assertEqual(len(result), 2*ks)
|
||||
if previous is not None:
|
||||
self.assertEqual(result, previous,
|
||||
msg="%r output differs from others %r: %r" %
|
||||
(name, available, [secret, salt, n, r, p, ks]))
|
||||
|
||||
#=============================================================================
|
||||
# test input types
|
||||
#=============================================================================
|
||||
def test_backend(self):
|
||||
"""backend management"""
|
||||
# clobber backend
|
||||
scrypt_mod.backend = None
|
||||
scrypt_mod._scrypt = None
|
||||
self.assertRaises(TypeError, scrypt_mod.scrypt, 's', 's', 2, 2, 2, 16)
|
||||
|
||||
# reload backend
|
||||
scrypt_mod._set_backend(self.backend)
|
||||
self.assertEqual(scrypt_mod.backend, self.backend)
|
||||
scrypt_mod.scrypt('s', 's', 2, 2, 2, 16)
|
||||
|
||||
# throw error for unknown backend
|
||||
self.assertRaises(ValueError, scrypt_mod._set_backend, 'xxx')
|
||||
self.assertEqual(scrypt_mod.backend, self.backend)
|
||||
|
||||
def test_secret_param(self):
|
||||
"""'secret' parameter"""
|
||||
|
||||
def run_scrypt(secret):
|
||||
return hexstr(scrypt_mod.scrypt(secret, "salt", 2, 2, 2, 16))
|
||||
|
||||
# unicode
|
||||
TEXT = u("abc\u00defg")
|
||||
self.assertEqual(run_scrypt(TEXT), '05717106997bfe0da42cf4779a2f8bd8')
|
||||
|
||||
# utf8 bytes
|
||||
TEXT_UTF8 = b'abc\xc3\x9efg'
|
||||
self.assertEqual(run_scrypt(TEXT_UTF8), '05717106997bfe0da42cf4779a2f8bd8')
|
||||
|
||||
# latin1 bytes
|
||||
TEXT_LATIN1 = b'abc\xdefg'
|
||||
self.assertEqual(run_scrypt(TEXT_LATIN1), '770825d10eeaaeaf98e8a3c40f9f441d')
|
||||
|
||||
# accept empty string
|
||||
self.assertEqual(run_scrypt(""), 'ca1399e5fae5d3b9578dcd2b1faff6e2')
|
||||
|
||||
# reject other types
|
||||
self.assertRaises(TypeError, run_scrypt, None)
|
||||
self.assertRaises(TypeError, run_scrypt, 1)
|
||||
|
||||
def test_salt_param(self):
|
||||
"""'salt' parameter"""
|
||||
|
||||
def run_scrypt(salt):
|
||||
return hexstr(scrypt_mod.scrypt("secret", salt, 2, 2, 2, 16))
|
||||
|
||||
# unicode
|
||||
TEXT = u("abc\u00defg")
|
||||
self.assertEqual(run_scrypt(TEXT), 'a748ec0f4613929e9e5f03d1ab741d88')
|
||||
|
||||
# utf8 bytes
|
||||
TEXT_UTF8 = b'abc\xc3\x9efg'
|
||||
self.assertEqual(run_scrypt(TEXT_UTF8), 'a748ec0f4613929e9e5f03d1ab741d88')
|
||||
|
||||
# latin1 bytes
|
||||
TEXT_LATIN1 = b'abc\xdefg'
|
||||
self.assertEqual(run_scrypt(TEXT_LATIN1), '91d056fb76fb6e9a7d1cdfffc0a16cd1')
|
||||
|
||||
# reject other types
|
||||
self.assertRaises(TypeError, run_scrypt, None)
|
||||
self.assertRaises(TypeError, run_scrypt, 1)
|
||||
|
||||
def test_n_param(self):
|
||||
"""'n' (rounds) parameter"""
|
||||
|
||||
def run_scrypt(n):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, 2, 2, 16))
|
||||
|
||||
# must be > 1, and a power of 2
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertRaises(ValueError, run_scrypt, 1)
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertRaises(ValueError, run_scrypt, 3)
|
||||
self.assertRaises(ValueError, run_scrypt, 15)
|
||||
self.assertEqual(run_scrypt(16), '0272b8fc72bc54b1159340ed99425233')
|
||||
|
||||
def test_r_param(self):
|
||||
"""'r' (block size) parameter"""
|
||||
def run_scrypt(r, n=2, p=2):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
|
||||
|
||||
# must be > 1
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), '3d630447d9f065363b8a79b0b3670251')
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertEqual(run_scrypt(5), '114f05e985a903c27237b5578e763736')
|
||||
|
||||
# reject r*p >= 2**30
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30), p=1)
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, p=2)
|
||||
|
||||
def test_p_param(self):
|
||||
"""'p' (parallelism) parameter"""
|
||||
def run_scrypt(p, n=2, r=2):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
|
||||
|
||||
# must be > 1
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), 'f2960ea8b7d48231fcec1b89b784a6fa')
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertEqual(run_scrypt(5), '848a0eeb2b3543e7f543844d6ca79782')
|
||||
|
||||
# reject r*p >= 2**30
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30), r=1)
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, r=2)
|
||||
|
||||
def test_keylen_param(self):
|
||||
"""'keylen' parameter"""
|
||||
rng = self.getRandom()
|
||||
|
||||
def run_scrypt(keylen):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", 2, 2, 2, keylen))
|
||||
|
||||
# must be > 0
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), 'da')
|
||||
|
||||
# pick random value
|
||||
ksize = rng.randint(1, 1 << 10)
|
||||
self.assertEqual(len(run_scrypt(ksize)), 2*ksize) # 2 hex chars per output
|
||||
|
||||
# one more than upper bound
|
||||
self.assertRaises(ValueError, run_scrypt, ((2**32) - 1) * 32 + 1)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# check what backends 'should' be available
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
def _can_import_cffi_scrypt():
|
||||
try:
|
||||
import scrypt
|
||||
except ImportError as err:
|
||||
if "scrypt" in str(err):
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
|
||||
has_cffi_scrypt = _can_import_cffi_scrypt()
|
||||
|
||||
|
||||
def _can_import_stdlib_scrypt():
|
||||
try:
|
||||
from hashlib import scrypt
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
has_stdlib_scrypt = _can_import_stdlib_scrypt()
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test individual backends
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: builtin version runs VERY slow (except under PyPy, where it's only 11x slower),
|
||||
# so skipping under quick test mode.
|
||||
@skipUnless(PYPY or TEST_MODE(min="default"), "skipped under current test mode")
|
||||
class BuiltinScryptTest(_CommonScryptTest):
|
||||
backend = "builtin"
|
||||
|
||||
def setUp(self):
|
||||
super(BuiltinScryptTest, self).setUp()
|
||||
warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
|
||||
category=exc.PasslibSecurityWarning)
|
||||
|
||||
def test_missing_backend(self):
|
||||
"""backend management -- missing backend"""
|
||||
if has_stdlib_scrypt or has_cffi_scrypt:
|
||||
raise self.skipTest("non-builtin backend is present")
|
||||
self.assertRaises(exc.MissingBackendError, scrypt_mod._set_backend, 'scrypt')
|
||||
|
||||
|
||||
@skipUnless(has_cffi_scrypt, "'scrypt' package not found")
|
||||
class ScryptPackageTest(_CommonScryptTest):
|
||||
backend = "scrypt"
|
||||
|
||||
def test_default_backend(self):
|
||||
"""backend management -- default backend"""
|
||||
if has_stdlib_scrypt:
|
||||
raise self.skipTest("higher priority backend present")
|
||||
scrypt_mod._set_backend("default")
|
||||
self.assertEqual(scrypt_mod.backend, "scrypt")
|
||||
|
||||
|
||||
@skipUnless(has_stdlib_scrypt, "'hashlib.scrypt()' not found")
|
||||
class StdlibScryptTest(_CommonScryptTest):
|
||||
backend = "stdlib"
|
||||
|
||||
def test_default_backend(self):
|
||||
"""backend management -- default backend"""
|
||||
scrypt_mod._set_backend("default")
|
||||
self.assertEqual(scrypt_mod.backend, "stdlib")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1080
backend/venv/Lib/site-packages/passlib/tests/test_ext_django.py
Normal file
1080
backend/venv/Lib/site-packages/passlib/tests/test_ext_django.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
test passlib.ext.django against django source tests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import suppress_cause
|
||||
from passlib.ext.django.utils import DJANGO_VERSION, DjangoTranslator, _PasslibHasherWrapper
|
||||
# tests
|
||||
from passlib.tests.utils import TestCase, TEST_MODE
|
||||
from .test_ext_django import (
|
||||
has_min_django, stock_config, _ExtensionSupport,
|
||||
)
|
||||
if has_min_django:
|
||||
from .test_ext_django import settings
|
||||
# local
|
||||
__all__ = [
|
||||
"HashersTest",
|
||||
]
|
||||
#=============================================================================
|
||||
# HashersTest --
|
||||
# hack up the some of the real django tests to run w/ extension loaded,
|
||||
# to ensure we mimic their behavior.
|
||||
# however, the django tests were moved out of the package, and into a source-only location
|
||||
# as of django 1.7. so we disable tests from that point on unless test-runner specifies
|
||||
#=============================================================================
|
||||
|
||||
#: ref to django unittest root module (if found)
|
||||
test_hashers_mod = None
|
||||
|
||||
#: message about why test module isn't present (if not found)
|
||||
hashers_skip_msg = None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# try to load django's tests/auth_tests/test_hasher.py module,
|
||||
# or note why we failed.
|
||||
#----------------------------------------------------------------------
|
||||
if TEST_MODE(max="quick"):
|
||||
hashers_skip_msg = "requires >= 'default' test mode"
|
||||
|
||||
elif has_min_django:
|
||||
import os
|
||||
import sys
|
||||
source_path = os.environ.get("PASSLIB_TESTS_DJANGO_SOURCE_PATH")
|
||||
|
||||
if source_path:
|
||||
if not os.path.exists(source_path):
|
||||
raise EnvironmentError("django source path not found: %r" % source_path)
|
||||
if not all(os.path.exists(os.path.join(source_path, name))
|
||||
for name in ["django", "tests"]):
|
||||
raise EnvironmentError("invalid django source path: %r" % source_path)
|
||||
log.info("using django tests from source path: %r", source_path)
|
||||
tests_path = os.path.join(source_path, "tests")
|
||||
sys.path.insert(0, tests_path)
|
||||
try:
|
||||
from auth_tests import test_hashers as test_hashers_mod
|
||||
except ImportError as err:
|
||||
raise suppress_cause(
|
||||
EnvironmentError("error trying to import django tests "
|
||||
"from source path (%r): %r" %
|
||||
(source_path, err)))
|
||||
finally:
|
||||
sys.path.remove(tests_path)
|
||||
|
||||
else:
|
||||
hashers_skip_msg = "requires PASSLIB_TESTS_DJANGO_SOURCE_PATH to be set"
|
||||
|
||||
if TEST_MODE("full"):
|
||||
# print warning so user knows what's happening
|
||||
sys.stderr.write("\nWARNING: $PASSLIB_TESTS_DJANGO_SOURCE_PATH is not set; "
|
||||
"can't run Django's own unittests against passlib.ext.django\n")
|
||||
|
||||
elif DJANGO_VERSION:
|
||||
hashers_skip_msg = "django version too old"
|
||||
|
||||
else:
|
||||
hashers_skip_msg = "django not installed"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# if found module, create wrapper to run django's own tests,
|
||||
# but with passlib monkeypatched in.
|
||||
#----------------------------------------------------------------------
|
||||
if test_hashers_mod:
|
||||
from django.core.signals import setting_changed
|
||||
from django.dispatch import receiver
|
||||
from django.utils.module_loading import import_string
|
||||
from passlib.utils.compat import get_unbound_method_function
|
||||
|
||||
class HashersTest(test_hashers_mod.TestUtilsHashPass, _ExtensionSupport):
|
||||
"""
|
||||
Run django's hasher unittests against passlib's extension
|
||||
and workalike implementations
|
||||
"""
|
||||
|
||||
#==================================================================
|
||||
# helpers
|
||||
#==================================================================
|
||||
|
||||
# port patchAttr() helper method from passlib.tests.utils.TestCase
|
||||
patchAttr = get_unbound_method_function(TestCase.patchAttr)
|
||||
|
||||
#==================================================================
|
||||
# custom setup
|
||||
#==================================================================
|
||||
def setUp(self):
|
||||
#---------------------------------------------------------
|
||||
# install passlib.ext.django adapter, and get context
|
||||
#---------------------------------------------------------
|
||||
self.load_extension(PASSLIB_CONTEXT=stock_config, check=False)
|
||||
from passlib.ext.django.models import adapter
|
||||
context = adapter.context
|
||||
|
||||
#---------------------------------------------------------
|
||||
# patch tests module to use our versions of patched funcs
|
||||
# (which should be installed in hashers module)
|
||||
#---------------------------------------------------------
|
||||
from django.contrib.auth import hashers
|
||||
for attr in ["make_password",
|
||||
"check_password",
|
||||
"identify_hasher",
|
||||
"is_password_usable",
|
||||
"get_hasher"]:
|
||||
self.patchAttr(test_hashers_mod, attr, getattr(hashers, attr))
|
||||
|
||||
#---------------------------------------------------------
|
||||
# django tests expect empty django_des_crypt salt field
|
||||
#---------------------------------------------------------
|
||||
from passlib.hash import django_des_crypt
|
||||
self.patchAttr(django_des_crypt, "use_duplicate_salt", False)
|
||||
|
||||
#---------------------------------------------------------
|
||||
# install receiver to update scheme list if test changes settings
|
||||
#---------------------------------------------------------
|
||||
django_to_passlib_name = DjangoTranslator().django_to_passlib_name
|
||||
|
||||
@receiver(setting_changed, weak=False)
|
||||
def update_schemes(**kwds):
|
||||
if kwds and kwds['setting'] != 'PASSWORD_HASHERS':
|
||||
return
|
||||
assert context is adapter.context
|
||||
schemes = [
|
||||
django_to_passlib_name(import_string(hash_path)())
|
||||
for hash_path in settings.PASSWORD_HASHERS
|
||||
]
|
||||
# workaround for a few tests that only specify hex_md5,
|
||||
# but test for django_salted_md5 format.
|
||||
if "hex_md5" in schemes and "django_salted_md5" not in schemes:
|
||||
schemes.append("django_salted_md5")
|
||||
schemes.append("django_disabled")
|
||||
context.update(schemes=schemes, deprecated="auto")
|
||||
adapter.reset_hashers()
|
||||
|
||||
self.addCleanup(setting_changed.disconnect, update_schemes)
|
||||
|
||||
update_schemes()
|
||||
|
||||
#---------------------------------------------------------
|
||||
# need password_context to keep up to date with django_hasher.iterations,
|
||||
# which is frequently patched by django tests.
|
||||
#
|
||||
# HACK: to fix this, inserting wrapper around a bunch of context
|
||||
# methods so that any time adapter calls them,
|
||||
# attrs are resynced first.
|
||||
#---------------------------------------------------------
|
||||
|
||||
def update_rounds():
|
||||
"""
|
||||
sync django hasher config -> passlib hashers
|
||||
"""
|
||||
for handler in context.schemes(resolve=True):
|
||||
if 'rounds' not in handler.setting_kwds:
|
||||
continue
|
||||
hasher = adapter.passlib_to_django(handler)
|
||||
if isinstance(hasher, _PasslibHasherWrapper):
|
||||
continue
|
||||
rounds = getattr(hasher, "rounds", None) or \
|
||||
getattr(hasher, "iterations", None)
|
||||
if rounds is None:
|
||||
continue
|
||||
# XXX: this doesn't modify the context, which would
|
||||
# cause other weirdness (since it would replace handler factories completely,
|
||||
# instead of just updating their state)
|
||||
handler.min_desired_rounds = handler.max_desired_rounds = handler.default_rounds = rounds
|
||||
|
||||
_in_update = [False]
|
||||
|
||||
def update_wrapper(wrapped, *args, **kwds):
|
||||
"""
|
||||
wrapper around arbitrary func, that first triggers sync
|
||||
"""
|
||||
if not _in_update[0]:
|
||||
_in_update[0] = True
|
||||
try:
|
||||
update_rounds()
|
||||
finally:
|
||||
_in_update[0] = False
|
||||
return wrapped(*args, **kwds)
|
||||
|
||||
# sync before any context call
|
||||
for attr in ["schemes", "handler", "default_scheme", "hash",
|
||||
"verify", "needs_update", "verify_and_update"]:
|
||||
self.patchAttr(context, attr, update_wrapper, wrap=True)
|
||||
|
||||
# sync whenever adapter tries to resolve passlib hasher
|
||||
self.patchAttr(adapter, "django_to_passlib", update_wrapper, wrap=True)
|
||||
|
||||
def tearDown(self):
|
||||
# NOTE: could rely on addCleanup() instead, but need py26 compat
|
||||
self.unload_extension()
|
||||
super(HashersTest, self).tearDown()
|
||||
|
||||
#==================================================================
|
||||
# skip a few methods that can't be replicated properly
|
||||
# *want to minimize these as much as possible*
|
||||
#==================================================================
|
||||
|
||||
_OMIT = lambda self: self.skipTest("omitted by passlib")
|
||||
|
||||
# XXX: this test registers two classes w/ same algorithm id,
|
||||
# something we don't support -- how does django sanely handle
|
||||
# that anyways? get_hashers_by_algorithm() should throw KeyError, right?
|
||||
test_pbkdf2_upgrade_new_hasher = _OMIT
|
||||
|
||||
# TODO: support wrapping django's harden-runtime feature?
|
||||
# would help pass their tests.
|
||||
test_check_password_calls_harden_runtime = _OMIT
|
||||
test_bcrypt_harden_runtime = _OMIT
|
||||
test_pbkdf2_harden_runtime = _OMIT
|
||||
|
||||
#==================================================================
|
||||
# eoc
|
||||
#==================================================================
|
||||
|
||||
else:
|
||||
# otherwise leave a stub so test log tells why test was skipped.
|
||||
|
||||
class HashersTest(TestCase):
|
||||
|
||||
def test_external_django_hasher_tests(self):
|
||||
"""external django hasher tests"""
|
||||
raise self.skipTest(hashers_skip_msg)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1819
backend/venv/Lib/site-packages/passlib/tests/test_handlers.py
Normal file
1819
backend/venv/Lib/site-packages/passlib/tests/test_handlers.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,507 @@
|
||||
"""passlib.tests.test_handlers_argon2 - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
import re
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils.compat import unicode
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# a bunch of tests lifted nearlky verbatim from official argon2 UTs...
|
||||
# https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c
|
||||
#=============================================================================
|
||||
def hashtest(version, t, logM, p, secret, salt, hex_digest, hash):
|
||||
return dict(version=version, rounds=t, logM=logM, memory_cost=1<<logM, parallelism=p,
|
||||
secret=secret, salt=salt, hex_digest=hex_digest, hash=hash)
|
||||
|
||||
# version 1.3 "I" tests
|
||||
version = 0x10
|
||||
reference_data = [
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
|
||||
"$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"),
|
||||
hashtest(version, 2, 20, 1, "password", "somesalt",
|
||||
"9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9",
|
||||
"$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ"
|
||||
"$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467",
|
||||
"$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06",
|
||||
"$argon2i$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb",
|
||||
"$argon2i$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2",
|
||||
"$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b",
|
||||
"$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3",
|
||||
"$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497",
|
||||
"$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc"),
|
||||
]
|
||||
|
||||
# version 1.9 "I" tests
|
||||
version = 0x13
|
||||
reference_data.extend([
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA"),
|
||||
hashtest(version, 2, 20, 1, "password", "somesalt",
|
||||
"d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41",
|
||||
"$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ"
|
||||
"$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
|
||||
"$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
|
||||
"$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
|
||||
"$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
|
||||
"$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
|
||||
"$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE"),
|
||||
])
|
||||
|
||||
# version 1.9 "ID" tests
|
||||
version = 0x13
|
||||
reference_data.extend([
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
|
||||
"$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
|
||||
"$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
|
||||
"$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
|
||||
"$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
|
||||
"$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw"),
|
||||
])
|
||||
|
||||
#=============================================================================
|
||||
# argon2
|
||||
#=============================================================================
|
||||
class _base_argon2_test(HandlerCase):
|
||||
handler = hash.argon2
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# sample test
|
||||
("password", '$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'),
|
||||
|
||||
# sample w/ all parameters different
|
||||
("password", '$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE, '$argon2i$v=19$m=512,t=2,p=2$1sV0O4PWLtc12Ypv1f7oGw$'
|
||||
'z+yqzlKtrq3SaNfXDfIDnQ'),
|
||||
(PASS_TABLE_UTF8, '$argon2i$v=19$m=512,t=2,p=2$1sV0O4PWLtc12Ypv1f7oGw$'
|
||||
'z+yqzlKtrq3SaNfXDfIDnQ'),
|
||||
|
||||
# ensure trailing null bytes handled correctly
|
||||
('password\x00', '$argon2i$v=19$m=512,t=2,p=2$c29tZXNhbHQ$Fb5+nPuLzZvtqKRwqUEtUQ'),
|
||||
|
||||
# sample with type D (generated via argon_cffi2.PasswordHasher)
|
||||
("password", '$argon2d$v=19$m=102400,t=2,p=8$g2RodLh8j8WbSdCp+lUy/A$zzAJqL/HSjm809PYQu6qkA'),
|
||||
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# unknown hash type
|
||||
"$argon2qq$v=19$t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# missing 'm' param
|
||||
"$argon2i$v=19$t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# 't' param > max uint32
|
||||
"$argon2i$v=19$m=65536,t=8589934592,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# unexpected param
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,q=5$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# wrong param order
|
||||
"$argon2i$v=19$t=2,m=65536,p=4,q=5$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# constraint violation: m < 8 * p
|
||||
"$argon2i$v=19$m=127,t=2,p=16$c29tZXNhbHQ$IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4",
|
||||
]
|
||||
|
||||
known_parsehash_results = [
|
||||
('$argon2i$v=19$m=256,t=2,p=3$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A',
|
||||
dict(type="i", memory_cost=256, rounds=2, parallelism=3, salt=b'somesalt',
|
||||
checksum=b'\x00\x91H\xb0\xd6S0\xa4\xc0{\x00x\xf8D\xcd\xd4')),
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(_base_argon2_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*Using argon2pure backend.*")
|
||||
|
||||
def do_stub_encrypt(self, handler=None, **settings):
|
||||
if self.backend == "argon2_cffi":
|
||||
# overriding default since no way to get stub config from argon2._calc_hash()
|
||||
# (otherwise test_21b_max_rounds blocks trying to do max rounds)
|
||||
handler = (handler or self.handler).using(**settings)
|
||||
self = handler(use_defaults=True)
|
||||
self.checksum = self._stub_checksum
|
||||
assert self.checksum
|
||||
return self.to_string()
|
||||
else:
|
||||
return super(_base_argon2_test, self).do_stub_encrypt(handler, **settings)
|
||||
|
||||
def test_03_legacy_hash_workflow(self):
|
||||
# override base method
|
||||
raise self.skipTest("legacy 1.6 workflow not supported")
|
||||
|
||||
def test_keyid_parameter(self):
|
||||
# NOTE: keyid parameter currently not supported by official argon2 hash parser,
|
||||
# even though it's mentioned in the format spec.
|
||||
# we're trying to be consistent w/ this, so hashes w/ keyid should
|
||||
# always through a NotImplementedError.
|
||||
self.assertRaises(NotImplementedError, self.handler.verify, 'password',
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,keyid=ABCD$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4")
|
||||
|
||||
def test_data_parameter(self):
|
||||
# NOTE: argon2 c library doesn't support passing in a data parameter to argon2_hash();
|
||||
# but argon2_verify() appears to parse that info... but then discards it (!?).
|
||||
# not sure what proper behavior is, filed issue -- https://github.com/P-H-C/phc-winner-argon2/issues/143
|
||||
# For now, replicating behavior we have for the two backends, to detect when things change.
|
||||
handler = self.handler
|
||||
|
||||
# ref hash of 'password' when 'data' is correctly passed into argon2()
|
||||
sample1 = '$argon2i$v=19$m=512,t=2,p=2,data=c29tZWRhdGE$c29tZXNhbHQ$KgHyCesFyyjkVkihZ5VNFw'
|
||||
|
||||
# ref hash of 'password' when 'data' is silently discarded (same digest as w/o data)
|
||||
sample2 = '$argon2i$v=19$m=512,t=2,p=2,data=c29tZWRhdGE$c29tZXNhbHQ$uEeXt1dxN1iFKGhklseW4w'
|
||||
|
||||
# hash of 'password' w/o the data field
|
||||
sample3 = '$argon2i$v=19$m=512,t=2,p=2$c29tZXNhbHQ$uEeXt1dxN1iFKGhklseW4w'
|
||||
|
||||
#
|
||||
# test sample 1
|
||||
#
|
||||
|
||||
if self.backend == "argon2_cffi":
|
||||
# argon2_cffi v16.1 would incorrectly return False here.
|
||||
# but v16.2 patches so it throws error on data parameter.
|
||||
# our code should detect that, and adapt it into a NotImplementedError
|
||||
self.assertRaises(NotImplementedError, handler.verify, "password", sample1)
|
||||
|
||||
# incorrectly returns sample3, dropping data parameter
|
||||
self.assertEqual(handler.genhash("password", sample1), sample3)
|
||||
|
||||
else:
|
||||
assert self.backend == "argon2pure"
|
||||
# should parse and verify
|
||||
self.assertTrue(handler.verify("password", sample1))
|
||||
|
||||
# should preserve sample1
|
||||
self.assertEqual(handler.genhash("password", sample1), sample1)
|
||||
|
||||
#
|
||||
# test sample 2
|
||||
#
|
||||
|
||||
if self.backend == "argon2_cffi":
|
||||
# argon2_cffi v16.1 would incorrectly return True here.
|
||||
# but v16.2 patches so it throws error on data parameter.
|
||||
# our code should detect that, and adapt it into a NotImplementedError
|
||||
self.assertRaises(NotImplementedError, handler.verify,"password", sample2)
|
||||
|
||||
# incorrectly returns sample3, dropping data parameter
|
||||
self.assertEqual(handler.genhash("password", sample1), sample3)
|
||||
|
||||
else:
|
||||
assert self.backend == "argon2pure"
|
||||
# should parse, but fail to verify
|
||||
self.assertFalse(self.handler.verify("password", sample2))
|
||||
|
||||
# should return sample1 (corrected digest)
|
||||
self.assertEqual(handler.genhash("password", sample2), sample1)
|
||||
|
||||
def test_keyid_and_data_parameters(self):
|
||||
# test combination of the two, just in case
|
||||
self.assertRaises(NotImplementedError, self.handler.verify, 'stub',
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,keyid=ABCD,data=EFGH$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4")
|
||||
|
||||
def test_type_kwd(self):
|
||||
cls = self.handler
|
||||
|
||||
# XXX: this mirrors test_30_HasManyIdents();
|
||||
# maybe switch argon2 class to use that mixin instead of "type" kwd?
|
||||
|
||||
# check settings
|
||||
self.assertTrue("type" in cls.setting_kwds)
|
||||
|
||||
# check supported type_values
|
||||
for value in cls.type_values:
|
||||
self.assertIsInstance(value, unicode)
|
||||
self.assertTrue("i" in cls.type_values)
|
||||
self.assertTrue("d" in cls.type_values)
|
||||
|
||||
# check default
|
||||
self.assertTrue(cls.type in cls.type_values)
|
||||
|
||||
# check constructor validates ident correctly.
|
||||
handler = cls
|
||||
hash = self.get_sample_hash()[1]
|
||||
kwds = handler.parsehash(hash)
|
||||
del kwds['type']
|
||||
|
||||
# ... accepts good type
|
||||
handler(type=cls.type, **kwds)
|
||||
|
||||
# XXX: this is policy "ident" uses, maybe switch to it?
|
||||
# # ... requires type w/o defaults
|
||||
# self.assertRaises(TypeError, handler, **kwds)
|
||||
handler(**kwds)
|
||||
|
||||
# ... supplies default type
|
||||
handler(use_defaults=True, **kwds)
|
||||
|
||||
# ... rejects bad type
|
||||
self.assertRaises(ValueError, handler, type='xXx', **kwds)
|
||||
|
||||
def test_type_using(self):
|
||||
handler = self.handler
|
||||
|
||||
# XXX: this mirrors test_has_many_idents_using();
|
||||
# maybe switch argon2 class to use that mixin instead of "type" kwd?
|
||||
|
||||
orig_type = handler.type
|
||||
for alt_type in handler.type_values:
|
||||
if alt_type != orig_type:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("expected to find alternate type: default=%r values=%r" %
|
||||
(orig_type, handler.type_values))
|
||||
|
||||
def effective_type(cls):
|
||||
return cls(use_defaults=True).type
|
||||
|
||||
# keep default if nothing else specified
|
||||
subcls = handler.using()
|
||||
self.assertEqual(subcls.type, orig_type)
|
||||
|
||||
# accepts alt type
|
||||
subcls = handler.using(type=alt_type)
|
||||
self.assertEqual(subcls.type, alt_type)
|
||||
self.assertEqual(handler.type, orig_type)
|
||||
|
||||
# check subcls actually *generates* default type,
|
||||
# and that we didn't affect orig handler
|
||||
self.assertEqual(effective_type(subcls), alt_type)
|
||||
self.assertEqual(effective_type(handler), orig_type)
|
||||
|
||||
# rejects bad type
|
||||
self.assertRaises(ValueError, handler.using, type='xXx')
|
||||
|
||||
# honor 'type' alias
|
||||
subcls = handler.using(type=alt_type)
|
||||
self.assertEqual(subcls.type, alt_type)
|
||||
self.assertEqual(handler.type, orig_type)
|
||||
|
||||
# check type aliases are being honored
|
||||
self.assertEqual(effective_type(handler.using(type="I")), "i")
|
||||
|
||||
def test_needs_update_w_type(self):
|
||||
handler = self.handler
|
||||
|
||||
hash = handler.hash("stub")
|
||||
self.assertFalse(handler.needs_update(hash))
|
||||
|
||||
hash2 = re.sub(r"\$argon2\w+\$", "$argon2d$", hash)
|
||||
self.assertTrue(handler.needs_update(hash2))
|
||||
|
||||
def test_needs_update_w_version(self):
|
||||
handler = self.handler.using(memory_cost=65536, time_cost=2, parallelism=4,
|
||||
digest_size=32)
|
||||
hash = ("$argon2i$m=65536,t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$"
|
||||
"QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY")
|
||||
if handler.max_version == 0x10:
|
||||
self.assertFalse(handler.needs_update(hash))
|
||||
else:
|
||||
self.assertTrue(handler.needs_update(hash))
|
||||
|
||||
def test_argon_byte_encoding(self):
|
||||
"""verify we're using right base64 encoding for argon2"""
|
||||
handler = self.handler
|
||||
if handler.version != 0x13:
|
||||
# TODO: make this fatal, and add refs for other version.
|
||||
raise self.skipTest("handler uses wrong version for sample hashes")
|
||||
|
||||
# 8 byte salt
|
||||
salt = b'somesalt'
|
||||
temp = handler.using(memory_cost=256, time_cost=2, parallelism=2, salt=salt,
|
||||
checksum_size=32, type="i")
|
||||
hash = temp.hash("password")
|
||||
self.assertEqual(hash, "$argon2i$v=19$m=256,t=2,p=2"
|
||||
"$c29tZXNhbHQ"
|
||||
"$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E")
|
||||
|
||||
# 16 byte salt
|
||||
salt = b'somesalt\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
temp = handler.using(memory_cost=256, time_cost=2, parallelism=2, salt=salt,
|
||||
checksum_size=32, type="i")
|
||||
hash = temp.hash("password")
|
||||
self.assertEqual(hash, "$argon2i$v=19$m=256,t=2,p=2"
|
||||
"$c29tZXNhbHQAAAAAAAAAAA"
|
||||
"$rqnbEp1/jFDUEKZZmw+z14amDsFqMDC53dIe57ZHD38")
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
settings_map = HandlerCase.FuzzHashGenerator.settings_map.copy()
|
||||
settings_map.update(memory_cost="random_memory_cost", type="random_type")
|
||||
|
||||
def random_type(self):
|
||||
return self.rng.choice(self.handler.type_values)
|
||||
|
||||
def random_memory_cost(self):
|
||||
if self.test.backend == "argon2pure":
|
||||
return self.randintgauss(128, 384, 256, 128)
|
||||
else:
|
||||
return self.randintgauss(128, 32767, 16384, 4096)
|
||||
|
||||
# TODO: fuzz parallelism, digest_size
|
||||
|
||||
#-----------------------------------------
|
||||
# test suites for specific backends
|
||||
#-----------------------------------------
|
||||
|
||||
class argon2_argon2_cffi_test(_base_argon2_test.create_backend_case("argon2_cffi")):
|
||||
|
||||
# add some more test vectors that take too long under argon2pure
|
||||
known_correct_hashes = _base_argon2_test.known_correct_hashes + [
|
||||
#
|
||||
# sample hashes from argon2 cffi package's unittests,
|
||||
# which in turn were generated by official argon2 cmdline tool.
|
||||
#
|
||||
|
||||
# v1.2, type I, w/o a version tag
|
||||
('password', "$argon2i$m=65536,t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$"
|
||||
"QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY"),
|
||||
|
||||
# v1.3, type I
|
||||
('password', "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4"),
|
||||
|
||||
# v1.3, type D
|
||||
('password', "$argon2d$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"cZn5d+rFh+ZfuRhm2iGUGgcrW5YLeM6q7L3vBsdmFA0"),
|
||||
|
||||
# v1.3, type ID
|
||||
('password', "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"GpZ3sK/oH9p7VIiV56G/64Zo/8GaUw434IimaPqxwCo"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# ensure trailing null bytes handled correctly
|
||||
('password\x00', "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"Vpzuc0v0SrP88LcVvmg+z5RoOYpMDKH/lt6O+CZabIQ"),
|
||||
|
||||
]
|
||||
|
||||
# add reference hashes from argon2 clib tests
|
||||
known_correct_hashes.extend(
|
||||
(info['secret'], info['hash']) for info in reference_data
|
||||
if info['logM'] <= (18 if TEST_MODE("full") else 16)
|
||||
)
|
||||
|
||||
class argon2_argon2pure_test(_base_argon2_test.create_backend_case("argon2pure")):
|
||||
|
||||
# XXX: setting max_threads at 1 to prevent argon2pure from using multiprocessing,
|
||||
# which causes big problems when testing under pypy.
|
||||
# would like a "pure_use_threads" option instead, to make it use multiprocessing.dummy instead.
|
||||
handler = hash.argon2.using(memory_cost=32, parallelism=2)
|
||||
|
||||
# don't use multiprocessing for unittests, makes it a lot harder to ctrl-c
|
||||
# XXX: make this controlled by env var?
|
||||
handler.pure_use_threads = True
|
||||
|
||||
# add reference hashes from argon2 clib tests
|
||||
known_correct_hashes = _base_argon2_test.known_correct_hashes[:]
|
||||
|
||||
known_correct_hashes.extend(
|
||||
(info['secret'], info['hash']) for info in reference_data
|
||||
if info['logM'] < 16
|
||||
)
|
||||
|
||||
class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(1, 3, 2, 1)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,688 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import os
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.handlers.bcrypt import IDENT_2, IDENT_2X
|
||||
from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input
|
||||
from passlib.utils.compat import irange, PY3
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# bcrypt
|
||||
#=============================================================================
|
||||
class _bcrypt_test(HandlerCase):
|
||||
"""base for BCrypt test cases"""
|
||||
handler = hash.bcrypt
|
||||
reduce_default_rounds = True
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# from JTR 1.7.9
|
||||
#
|
||||
('U*U*U*U*', '$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O'),
|
||||
('U*U***U', '$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq'),
|
||||
('U*U***U*', '$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi'),
|
||||
('*U*U*U*U', '$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2'),
|
||||
('', '$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO'),
|
||||
|
||||
#
|
||||
# test vectors from http://www.openwall.com/crypt v1.2
|
||||
# note that this omits any hashes that depend on crypt_blowfish's
|
||||
# various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password,
|
||||
# and any 2x hashes); and only contain hashes which are correct
|
||||
# under both crypt_blowfish 1.2 AND OpenBSD.
|
||||
#
|
||||
('U*U', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW'),
|
||||
('U*U*', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK'),
|
||||
('U*U*U', '$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a'),
|
||||
('', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'),
|
||||
('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
'0123456789chars after 72 are ignored',
|
||||
'$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui'),
|
||||
(b'\xa3',
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'),
|
||||
(b'\xff\xa3345',
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e'),
|
||||
(b'\xa3ab',
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS'),
|
||||
(b'\xaa'*72 + b'chars after 72 are ignored as usual',
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6'),
|
||||
(b'\xaa\x55'*36,
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy'),
|
||||
(b'\x55\xaa\xff'*24,
|
||||
'$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe'),
|
||||
|
||||
# keeping one of their 2y tests, because we are supporting that.
|
||||
(b'\xa3',
|
||||
'$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'),
|
||||
|
||||
#
|
||||
# 8bit bug (fixed in 2y/2b)
|
||||
#
|
||||
|
||||
# NOTE: see assert_lacks_8bit_bug() for origins of this test vector.
|
||||
(b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"),
|
||||
|
||||
#
|
||||
# bsd wraparound bug (fixed in 2b)
|
||||
#
|
||||
|
||||
# NOTE: if backend is vulnerable, password will hash the same as '0'*72
|
||||
# ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"),
|
||||
# rather than same as ("0123456789"*8)[:72]
|
||||
# 255 should be sufficient, but checking
|
||||
(("0123456789"*26)[:254], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:255], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:256], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:257], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
|
||||
|
||||
#
|
||||
# from py-bcrypt tests
|
||||
#
|
||||
('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
|
||||
('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'),
|
||||
('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'),
|
||||
('abcdefghijklmnopqrstuvwxyz',
|
||||
'$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
|
||||
('~!@#$%^&*() ~!@#$%^&*()PNBFRD',
|
||||
'$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'),
|
||||
|
||||
#
|
||||
# custom test vectors
|
||||
#
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE,
|
||||
'$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'),
|
||||
|
||||
# ensure 2b support
|
||||
(UPASS_TABLE,
|
||||
'$2b$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'),
|
||||
|
||||
]
|
||||
|
||||
if TEST_MODE("full"):
|
||||
#
|
||||
# add some extra tests related to 2/2a
|
||||
#
|
||||
CONFIG_2 = '$2$05$' + '.'*22
|
||||
CONFIG_A = '$2a$05$' + '.'*22
|
||||
known_correct_hashes.extend([
|
||||
("", CONFIG_2 + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'),
|
||||
("", CONFIG_A + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'),
|
||||
("abc", CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
("abc", CONFIG_A + 'ev6gDwpVye3oMCUpLY85aTpfBNHD0Ga'),
|
||||
("abc"*23, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
("abc"*23, CONFIG_A + '2kIdfSj/4/R/Q6n847VTvc68BXiRYZC'),
|
||||
("abc"*24, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
("abc"*24, CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
("abc"*24+'x', CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
("abc"*24+'x', CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'),
|
||||
])
|
||||
|
||||
known_correct_configs = [
|
||||
('$2a$04$uM6csdM8R9SXTex/gbTaye', UPASS_TABLE,
|
||||
'$2a$04$uM6csdM8R9SXTex/gbTayezuvzFEufYGd2uB6of7qScLjQ4GwcD4G'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
# invalid minor version
|
||||
"$2f$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
|
||||
"$2`$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# bad char in otherwise correct hash
|
||||
# \/
|
||||
"$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
|
||||
|
||||
# unsupported (but recognized) minor version
|
||||
"$2x$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
|
||||
|
||||
# rounds not zero-padded (py-bcrypt rejects this, therefore so do we)
|
||||
'$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'
|
||||
|
||||
# NOTE: salts with padding bits set are technically malformed,
|
||||
# but we can reliably correct & issue a warning for that.
|
||||
]
|
||||
|
||||
platform_crypt_support = [
|
||||
("freedbsd|openbsd|netbsd", True),
|
||||
("darwin", False),
|
||||
("linux", None), # may be present via addon, e.g. debian's libpam-unix2
|
||||
("solaris", None), # depends on system policy
|
||||
]
|
||||
|
||||
#===================================================================
|
||||
# override some methods
|
||||
#===================================================================
|
||||
def setUp(self):
|
||||
# ensure builtin is enabled for duration of test.
|
||||
if TEST_MODE("full") and self.backend == "builtin":
|
||||
key = "PASSLIB_BUILTIN_BCRYPT"
|
||||
orig = os.environ.get(key)
|
||||
if orig:
|
||||
self.addCleanup(os.environ.__setitem__, key, orig)
|
||||
else:
|
||||
self.addCleanup(os.environ.__delitem__, key)
|
||||
os.environ[key] = "true"
|
||||
|
||||
super(_bcrypt_test, self).setUp()
|
||||
|
||||
# silence this warning, will come up a bunch during testing of old 2a hashes.
|
||||
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# builtin is still just way too slow.
|
||||
if self.backend == "builtin":
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(_bcrypt_test, self).populate_settings(kwds)
|
||||
|
||||
#===================================================================
|
||||
# fuzz testing
|
||||
#===================================================================
|
||||
def crypt_supports_variant(self, hash):
|
||||
"""check if OS crypt is expected to support given ident"""
|
||||
from passlib.handlers.bcrypt import bcrypt, IDENT_2X, IDENT_2Y
|
||||
from passlib.utils import safe_crypt
|
||||
ident = bcrypt.from_string(hash)
|
||||
return (safe_crypt("test", ident + "04$5BJqKfqMQvV7nS.yUguNcu") or "").startswith(ident)
|
||||
|
||||
fuzz_verifiers = HandlerCase.fuzz_verifiers + (
|
||||
"fuzz_verifier_bcrypt",
|
||||
"fuzz_verifier_pybcrypt",
|
||||
"fuzz_verifier_bcryptor",
|
||||
)
|
||||
|
||||
def fuzz_verifier_bcrypt(self):
|
||||
# test against bcrypt, if available
|
||||
from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, _detect_pybcrypt
|
||||
from passlib.utils import to_native_str, to_bytes
|
||||
try:
|
||||
import bcrypt
|
||||
except ImportError:
|
||||
return
|
||||
if _detect_pybcrypt():
|
||||
return
|
||||
def check_bcrypt(secret, hash):
|
||||
"""bcrypt"""
|
||||
secret = to_bytes(secret, self.FuzzHashGenerator.password_encoding)
|
||||
if hash.startswith(IDENT_2B):
|
||||
# bcrypt <1.1 lacks 2B support
|
||||
hash = IDENT_2A + hash[4:]
|
||||
elif hash.startswith(IDENT_2):
|
||||
# bcrypt doesn't support $2$ hashes; but we can fake it
|
||||
# using the $2a$ algorithm, by repeating the password until
|
||||
# it's 72 chars in length.
|
||||
hash = IDENT_2A + hash[3:]
|
||||
if secret:
|
||||
secret = repeat_string(secret, 72)
|
||||
elif hash.startswith(IDENT_2Y) and bcrypt.__version__ == "3.0.0":
|
||||
hash = IDENT_2B + hash[4:]
|
||||
hash = to_bytes(hash)
|
||||
try:
|
||||
return bcrypt.hashpw(secret, hash) == hash
|
||||
except ValueError:
|
||||
raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret))
|
||||
return check_bcrypt
|
||||
|
||||
def fuzz_verifier_pybcrypt(self):
|
||||
# test against py-bcrypt, if available
|
||||
from passlib.handlers.bcrypt import (
|
||||
IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y,
|
||||
_PyBcryptBackend,
|
||||
)
|
||||
from passlib.utils import to_native_str
|
||||
|
||||
loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False)
|
||||
if not loaded:
|
||||
return
|
||||
|
||||
from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod
|
||||
|
||||
lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2
|
||||
|
||||
def check_pybcrypt(secret, hash):
|
||||
"""pybcrypt"""
|
||||
secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding)
|
||||
if len(secret) > 200: # vulnerable to wraparound bug
|
||||
secret = secret[:200]
|
||||
if hash.startswith((IDENT_2B, IDENT_2Y)):
|
||||
hash = IDENT_2A + hash[4:]
|
||||
try:
|
||||
if lock:
|
||||
with lock:
|
||||
return bcrypt_mod.hashpw(secret, hash) == hash
|
||||
else:
|
||||
return bcrypt_mod.hashpw(secret, hash) == hash
|
||||
except ValueError:
|
||||
raise ValueError("py-bcrypt rejected hash: %r" % (hash,))
|
||||
return check_pybcrypt
|
||||
|
||||
def fuzz_verifier_bcryptor(self):
|
||||
# test against bcryptor if available
|
||||
from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y, IDENT_2B
|
||||
from passlib.utils import to_native_str
|
||||
try:
|
||||
from bcryptor.engine import Engine
|
||||
except ImportError:
|
||||
return
|
||||
def check_bcryptor(secret, hash):
|
||||
"""bcryptor"""
|
||||
secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding)
|
||||
if hash.startswith((IDENT_2B, IDENT_2Y)):
|
||||
hash = IDENT_2A + hash[4:]
|
||||
elif hash.startswith(IDENT_2):
|
||||
# bcryptor doesn't support $2$ hashes; but we can fake it
|
||||
# using the $2a$ algorithm, by repeating the password until
|
||||
# it's 72 chars in length.
|
||||
hash = IDENT_2A + hash[3:]
|
||||
if secret:
|
||||
secret = repeat_string(secret, 72)
|
||||
return Engine(False).hash_key(secret, hash) == hash
|
||||
return check_bcryptor
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def generate(self):
|
||||
opts = super(_bcrypt_test.FuzzHashGenerator, self).generate()
|
||||
|
||||
secret = opts['secret']
|
||||
other = opts['other']
|
||||
settings = opts['settings']
|
||||
ident = settings.get('ident')
|
||||
|
||||
if ident == IDENT_2X:
|
||||
# 2x is just recognized, not supported. don't test with it.
|
||||
del settings['ident']
|
||||
|
||||
elif ident == IDENT_2 and other and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret):
|
||||
# avoid false failure due to flaw in 0-revision bcrypt:
|
||||
# repeated strings like 'abc' and 'abcabc' hash identically.
|
||||
opts['secret'], opts['other'] = self.random_password_pair()
|
||||
|
||||
return opts
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
#===================================================================
|
||||
# custom tests
|
||||
#===================================================================
|
||||
known_incorrect_padding = [
|
||||
# password, bad hash, good hash
|
||||
|
||||
# 2 bits of salt padding set
|
||||
# ("loppux", # \/
|
||||
# "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C",
|
||||
# "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C"),
|
||||
("test", # \/
|
||||
'$2a$04$oaQbBqq8JnSM1NHRPQGXORY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO',
|
||||
'$2a$04$oaQbBqq8JnSM1NHRPQGXOOY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO'),
|
||||
|
||||
# all 4 bits of salt padding set
|
||||
# ("Passlib11", # \/
|
||||
# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK",
|
||||
# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK"),
|
||||
("test", # \/
|
||||
"$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS",
|
||||
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"),
|
||||
|
||||
# bad checksum padding
|
||||
("test", # \/
|
||||
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV",
|
||||
"$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"),
|
||||
]
|
||||
|
||||
def test_90_bcrypt_padding(self):
|
||||
"""test passlib correctly handles bcrypt padding bits"""
|
||||
self.require_TEST_MODE("full")
|
||||
#
|
||||
# prevents reccurrence of issue 25 (https://code.google.com/p/passlib/issues/detail?id=25)
|
||||
# were some unused bits were incorrectly set in bcrypt salt strings.
|
||||
# (fixed since 1.5.3)
|
||||
#
|
||||
bcrypt = self.handler
|
||||
corr_desc = ".*incorrectly set padding bits"
|
||||
|
||||
#
|
||||
# test hash() / genconfig() don't generate invalid salts anymore
|
||||
#
|
||||
def check_padding(hash):
|
||||
assert hash.startswith(("$2a$", "$2b$")) and len(hash) >= 28, \
|
||||
"unexpectedly malformed hash: %r" % (hash,)
|
||||
self.assertTrue(hash[28] in '.Oeu',
|
||||
"unused bits incorrectly set in hash: %r" % (hash,))
|
||||
for i in irange(6):
|
||||
check_padding(bcrypt.genconfig())
|
||||
for i in irange(3):
|
||||
check_padding(bcrypt.using(rounds=bcrypt.min_rounds).hash("bob"))
|
||||
|
||||
#
|
||||
# test genconfig() corrects invalid salts & issues warning.
|
||||
#
|
||||
with self.assertWarningList(["salt too large", corr_desc]):
|
||||
hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True)
|
||||
self.assertEqual(hash, "$2b$05$" + "." * (22 + 31))
|
||||
|
||||
#
|
||||
# test public methods against good & bad hashes
|
||||
#
|
||||
samples = self.known_incorrect_padding
|
||||
for pwd, bad, good in samples:
|
||||
|
||||
# make sure genhash() corrects bad configs, leaves good unchanged
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertEqual(bcrypt.genhash(pwd, bad), good)
|
||||
with self.assertWarningList([]):
|
||||
self.assertEqual(bcrypt.genhash(pwd, good), good)
|
||||
|
||||
# make sure verify() works correctly with good & bad hashes
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertTrue(bcrypt.verify(pwd, bad))
|
||||
with self.assertWarningList([]):
|
||||
self.assertTrue(bcrypt.verify(pwd, good))
|
||||
|
||||
# make sure normhash() corrects bad hashes, leaves good unchanged
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertEqual(bcrypt.normhash(bad), good)
|
||||
with self.assertWarningList([]):
|
||||
self.assertEqual(bcrypt.normhash(good), good)
|
||||
|
||||
# make sure normhash() leaves non-bcrypt hashes alone
|
||||
self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc")
|
||||
|
||||
def test_needs_update_w_padding(self):
|
||||
"""needs_update corrects bcrypt padding"""
|
||||
# NOTE: see padding test above for details about issue this detects
|
||||
bcrypt = self.handler.using(rounds=4)
|
||||
|
||||
# PASS1 = "test"
|
||||
# bad contains invalid 'c' char at end of salt:
|
||||
# \/
|
||||
BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
|
||||
self.assertTrue(bcrypt.needs_update(BAD1))
|
||||
self.assertFalse(bcrypt.needs_update(GOOD1))
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
# create test cases for specific backends
|
||||
bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt")
|
||||
bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt")
|
||||
bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor")
|
||||
|
||||
class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")):
|
||||
|
||||
# os crypt doesn't support non-utf8 secret bytes
|
||||
known_correct_hashes = [row for row in _bcrypt_test.known_correct_hashes
|
||||
if is_safe_crypt_input(row[0])]
|
||||
|
||||
# os crypt backend doesn't currently implement a per-call fallback if it fails
|
||||
has_os_crypt_fallback = False
|
||||
|
||||
bcrypt_builtin_test = _bcrypt_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# bcrypt
|
||||
#=============================================================================
|
||||
class _bcrypt_sha256_test(HandlerCase):
|
||||
"base for BCrypt-SHA256 test cases"
|
||||
handler = hash.bcrypt_sha256
|
||||
reduce_default_rounds = True
|
||||
forbidden_characters = None
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#-------------------------------------------------------------------
|
||||
# custom test vectors for old v1 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# empty
|
||||
("",
|
||||
'$bcrypt-sha256$2a,5$E/e/2AOhqM5W/KJTFQzLce$F6dYSxOdAEoJZO2eoHUZWZljW/e0TXO'),
|
||||
|
||||
# ascii
|
||||
("password",
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
|
||||
# unicode / utf8
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
(UPASS_TABLE.encode("utf-8"),
|
||||
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
|
||||
# ensure 2b support
|
||||
("password",
|
||||
'$bcrypt-sha256$2b,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$2b,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
|
||||
(repeat_string("abc123", 72),
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$r/hyEtqJ0teqPEmfTLoZ83ciAI1Q74.'),
|
||||
(repeat_string("abc123", 72) + "qwr",
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$021KLEif6epjot5yoxk0m8I0929ohEa'),
|
||||
(repeat_string("abc123", 72) + "xyz",
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$7.1kgpHduMGEjvM3fX6e/QCvfn6OKja'),
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# custom test vectors for v2 format
|
||||
# TODO: convert to v2 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# empty
|
||||
("",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$E/e/2AOhqM5W/KJTFQzLce$WFPIZKtDDTriqWwlmRFfHiOTeheAZWe'),
|
||||
|
||||
# ascii
|
||||
("password",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
|
||||
|
||||
# unicode / utf8
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
|
||||
(UPASS_TABLE.encode("utf-8"),
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
|
||||
(repeat_string("abc123", 72),
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zu1cloESVFIOsUIo7fCEgkdHaI9SSue'),
|
||||
(repeat_string("abc123", 72) + "qwr",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$CBF9csfEdW68xv3DwE6xSULXMtqEFP.'),
|
||||
(repeat_string("abc123", 72) + "xyz",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zC/1UDUG2ofEXB6Onr2vvyFzfhEOS3S'),
|
||||
]
|
||||
|
||||
known_correct_configs =[
|
||||
# v1
|
||||
('$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe',
|
||||
"password", '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
# v2
|
||||
('$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe',
|
||||
"password", '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
#-------------------------------------------------------------------
|
||||
# v1 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# bad char in otherwise correct hash
|
||||
# \/
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized bcrypt variant
|
||||
'$bcrypt-sha256$2c,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported bcrypt variant
|
||||
'$bcrypt-sha256$2x,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# rounds zero-padded
|
||||
'$bcrypt-sha256$2a,05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# config string w/ $ added
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$',
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# v2 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# bad char in otherwise correct hash
|
||||
# \/
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported version (for this format)
|
||||
'$bcrypt-sha256$v=1,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized version
|
||||
'$bcrypt-sha256$v=3,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized bcrypt variant
|
||||
'$bcrypt-sha256$v=2,t=2c,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported bcrypt variant
|
||||
'$bcrypt-sha256$v=2,t=2a,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
'$bcrypt-sha256$v=2,t=2x,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# rounds zero-padded
|
||||
'$bcrypt-sha256$v=2,t=2b,r=05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# config string w/ $ added
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$',
|
||||
]
|
||||
|
||||
#===================================================================
|
||||
# override some methods -- cloned from bcrypt
|
||||
#===================================================================
|
||||
def setUp(self):
|
||||
# ensure builtin is enabled for duration of test.
|
||||
if TEST_MODE("full") and self.backend == "builtin":
|
||||
key = "PASSLIB_BUILTIN_BCRYPT"
|
||||
orig = os.environ.get(key)
|
||||
if orig:
|
||||
self.addCleanup(os.environ.__setitem__, key, orig)
|
||||
else:
|
||||
self.addCleanup(os.environ.__delitem__, key)
|
||||
os.environ[key] = "enabled"
|
||||
super(_bcrypt_sha256_test, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# builtin is still just way too slow.
|
||||
if self.backend == "builtin":
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(_bcrypt_sha256_test, self).populate_settings(kwds)
|
||||
|
||||
#===================================================================
|
||||
# override ident tests for now
|
||||
#===================================================================
|
||||
|
||||
def require_many_idents(self):
|
||||
raise self.skipTest("multiple idents not supported")
|
||||
|
||||
def test_30_HasOneIdent(self):
|
||||
# forbidding ident keyword, we only support "2b" for now
|
||||
handler = self.handler
|
||||
handler(use_defaults=True)
|
||||
self.assertRaises(ValueError, handler, ident="$2y$", use_defaults=True)
|
||||
|
||||
#===================================================================
|
||||
# fuzz testing -- cloned from bcrypt
|
||||
#===================================================================
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
return "2b"
|
||||
|
||||
#===================================================================
|
||||
# custom tests
|
||||
#===================================================================
|
||||
|
||||
def test_using_version(self):
|
||||
# default to v2
|
||||
handler = self.handler
|
||||
self.assertEqual(handler.version, 2)
|
||||
|
||||
# allow v1 explicitly
|
||||
subcls = handler.using(version=1)
|
||||
self.assertEqual(subcls.version, 1)
|
||||
|
||||
# forbid unknown ver
|
||||
self.assertRaises(ValueError, handler.using, version=999)
|
||||
|
||||
# allow '2a' only for v1
|
||||
subcls = handler.using(version=1, ident="2a")
|
||||
self.assertRaises(ValueError, handler.using, ident="2a")
|
||||
|
||||
def test_calc_digest_v2(self):
|
||||
"""
|
||||
test digest calc v2 matches bcrypt()
|
||||
"""
|
||||
from passlib.hash import bcrypt
|
||||
from passlib.crypto.digest import compile_hmac
|
||||
from passlib.utils.binary import b64encode
|
||||
|
||||
# manually calc intermediary digest
|
||||
salt = "nyKYxTAvjmy6lMDYMl11Uu"
|
||||
secret = "test"
|
||||
temp_digest = compile_hmac("sha256", salt.encode("ascii"))(secret.encode("ascii"))
|
||||
temp_digest = b64encode(temp_digest).decode("ascii")
|
||||
self.assertEqual(temp_digest, "J5TlyIDm+IcSWmKiDJm+MeICndBkFVPn4kKdJW8f+xY=")
|
||||
|
||||
# manually final hash from intermediary
|
||||
# XXX: genhash() could be useful here
|
||||
bcrypt_digest = bcrypt(ident="2b", salt=salt, rounds=12)._calc_checksum(temp_digest)
|
||||
self.assertEqual(bcrypt_digest, "M0wE0Ov/9LXoQFCe.jRHu3MSHPF54Ta")
|
||||
self.assertTrue(bcrypt.verify(temp_digest, "$2b$12$" + salt + bcrypt_digest))
|
||||
|
||||
# confirm handler outputs same thing.
|
||||
# XXX: genhash() could be useful here
|
||||
result = self.handler(ident="2b", salt=salt, rounds=12)._calc_checksum(secret)
|
||||
self.assertEqual(result, bcrypt_digest)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
# create test cases for specific backends
|
||||
bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt")
|
||||
bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt")
|
||||
bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor")
|
||||
|
||||
class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")):
|
||||
|
||||
@classmethod
|
||||
def _get_safe_crypt_handler_backend(cls):
|
||||
return bcrypt_os_crypt_test._get_safe_crypt_handler_backend()
|
||||
|
||||
has_os_crypt_fallback = False
|
||||
|
||||
bcrypt_sha256_builtin_test = _bcrypt_sha256_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,457 @@
|
||||
"""
|
||||
passlib.tests.test_handlers_cisco - tests for Cisco-specific algorithms
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash, exc
|
||||
from passlib.utils.compat import u
|
||||
from .utils import UserHandlerMixin, HandlerCase, repeat_string
|
||||
from .test_handlers import UPASS_TABLE
|
||||
# module
|
||||
__all__ = [
|
||||
"cisco_pix_test",
|
||||
"cisco_asa_test",
|
||||
"cisco_type7_test",
|
||||
]
|
||||
#=============================================================================
|
||||
# shared code for cisco PIX & ASA
|
||||
#=============================================================================
|
||||
|
||||
class _PixAsaSharedTest(UserHandlerMixin, HandlerCase):
|
||||
"""
|
||||
class w/ shared info for PIX & ASA tests.
|
||||
"""
|
||||
__unittest_skip = True # for TestCase
|
||||
requires_user = False # for UserHandlerMixin
|
||||
|
||||
#: shared list of hashes which should be identical under pix & asa7
|
||||
#: (i.e. combined secret + user < 17 bytes)
|
||||
pix_asa_shared_hashes = [
|
||||
#
|
||||
# http://www.perlmonks.org/index.pl?node_id=797623
|
||||
#
|
||||
(("cisco", ""), "2KFQnbNIdI.2KYOU"), # confirmed ASA 9.6
|
||||
|
||||
#
|
||||
# http://www.hsc.fr/ressources/breves/pix_crack.html.en
|
||||
#
|
||||
(("hsc", ""), "YtT8/k6Np8F1yz2c"), # confirmed ASA 9.6
|
||||
|
||||
#
|
||||
# www.freerainbowtables.com/phpBB3/viewtopic.php?f=2&t=1441
|
||||
#
|
||||
(("", ""), "8Ry2YjIyt7RRXU24"), # confirmed ASA 9.6
|
||||
(("cisco", "john"), "hN7LzeyYjw12FSIU"),
|
||||
(("cisco", "jack"), "7DrfeZ7cyOj/PslD"),
|
||||
|
||||
#
|
||||
# http://comments.gmane.org/gmane.comp.security.openwall.john.user/2529
|
||||
#
|
||||
(("ripper", "alex"), "h3mJrcH0901pqX/m"),
|
||||
(("cisco", "cisco"), "3USUcOPFUiMCO4Jk"),
|
||||
(("cisco", "cisco1"), "3USUcOPFUiMCO4Jk"),
|
||||
(("CscFw-ITC!", "admcom"), "lZt7HSIXw3.QP7.R"),
|
||||
("cangetin", "TynyB./ftknE77QP"),
|
||||
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
|
||||
|
||||
#
|
||||
# http://openwall.info/wiki/john/sample-hashes
|
||||
#
|
||||
(("phonehome", "rharris"), "zyIIMSYjiPm0L7a6"),
|
||||
|
||||
#
|
||||
# http://www.openwall.com/lists/john-users/2010/08/08/3
|
||||
#
|
||||
(("cangetin", ""), "TynyB./ftknE77QP"),
|
||||
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
|
||||
|
||||
#
|
||||
# from JTR 1.7.9
|
||||
#
|
||||
("test1", "TRPEas6f/aa6JSPL"),
|
||||
("test2", "OMT6mXmAvGyzrCtp"),
|
||||
("test3", "gTC7RIy1XJzagmLm"),
|
||||
("test4", "oWC1WRwqlBlbpf/O"),
|
||||
("password", "NuLKvvWGg.x9HEKO"),
|
||||
("0123456789abcdef", ".7nfVBEIEu4KbF/1"),
|
||||
|
||||
#
|
||||
# http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html#wp5472
|
||||
#
|
||||
(("1234567890123456", ""), "feCkwUGktTCAgIbD"), # canonical source
|
||||
(("watag00s1am", ""), "jMorNbK0514fadBh"), # canonical source
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
(("cisco1", "cisco1"), "jmINXNH6p1BxUppp"),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE, 'CaiIvkLMu2TOHXGT'),
|
||||
|
||||
#
|
||||
# passlib reference vectors
|
||||
#
|
||||
# Some of these have been confirmed on various ASA firewalls,
|
||||
# and the exact version is noted next to each hash.
|
||||
# Would like to verify these under more PIX & ASA versions.
|
||||
#
|
||||
# Those without a note are generally an extrapolation,
|
||||
# to ensure the code stays consistent, but for various reasons,
|
||||
# hasn't been verified.
|
||||
#
|
||||
# * One such case is usernames w/ 1 & 2 digits --
|
||||
# ASA (9.6 at least) requires 3+ digits in username.
|
||||
#
|
||||
# The following hashes (below 13 chars) should be identical for PIX/ASA.
|
||||
# Ones which differ are listed separately in the known_correct_hashes
|
||||
# list for the two test classes.
|
||||
#
|
||||
|
||||
# 4 char password
|
||||
(('1234', ''), 'RLPMUQ26KL4blgFN'), # confirmed ASA 9.6
|
||||
|
||||
# 8 char password
|
||||
(('01234567', ''), '0T52THgnYdV1tlOF'), # confirmed ASA 9.6
|
||||
(('01234567', '3'), '.z0dT9Alkdc7EIGS'),
|
||||
(('01234567', '36'), 'CC3Lam53t/mHhoE7'),
|
||||
(('01234567', '365'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
|
||||
(('01234567', '3333'), '.z0dT9Alkdc7EIGS'), # confirmed ASA 9.6
|
||||
(('01234567', '3636'), 'CC3Lam53t/mHhoE7'), # confirmed ASA 9.6
|
||||
(('01234567', '3653'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
|
||||
(('01234567', 'adm'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'adma'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'admad'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'user'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
|
||||
(('01234567', 'user1234'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
|
||||
|
||||
# 12 char password
|
||||
(('0123456789ab', ''), 'S31BxZOGlAigndcJ'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '36'), 'wFqSX91X5.YaRKsi'),
|
||||
(('0123456789ab', '365'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3333'), 'mcXPL/vIZcIxLUQs'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3636'), 'wFqSX91X5.YaRKsi'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3653'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
|
||||
(('0123456789ab', 'user'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
|
||||
(('0123456789ab', 'user1234'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
|
||||
|
||||
# NOTE: remaining reference vectors for 13+ char passwords
|
||||
# are split up between cisco_pix & cisco_asa tests.
|
||||
|
||||
# unicode passwords
|
||||
# ASA supposedly uses utf-8 encoding, but entering non-ascii
|
||||
# chars is error-prone, and while UTF-8 appears to be intended,
|
||||
# observed behaviors include:
|
||||
# * ssh cli stripping non-ascii chars entirely
|
||||
# * ASDM web iface double-encoding utf-8 strings
|
||||
((u("t\xe1ble").encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'),
|
||||
((u("t\xe1ble").encode("utf-8").decode("latin-1").encode("utf-8"),
|
||||
'user'), 'cMvFC2XVBmK/68yB'), # confirmed ASA 9.6 when typed into ASDM
|
||||
]
|
||||
|
||||
def test_calc_digest_spoiler(self):
|
||||
"""
|
||||
_calc_checksum() -- spoil oversize passwords during verify
|
||||
|
||||
for details, see 'spoil_digest' flag instead that function.
|
||||
this helps cisco_pix/cisco_asa implement their policy of
|
||||
``.truncate_verify_reject=True``.
|
||||
"""
|
||||
def calc(secret, for_hash=False):
|
||||
return self.handler(use_defaults=for_hash)._calc_checksum(secret)
|
||||
|
||||
# short (non-truncated) password
|
||||
short_secret = repeat_string("1234", self.handler.truncate_size)
|
||||
short_hash = calc(short_secret)
|
||||
|
||||
# longer password should have totally different hash,
|
||||
# to prevent verify from matching (i.e. "spoiled").
|
||||
long_secret = short_secret + "X"
|
||||
long_hash = calc(long_secret)
|
||||
self.assertNotEqual(long_hash, short_hash)
|
||||
|
||||
# spoiled hash should depend on whole secret,
|
||||
# so that output isn't predictable
|
||||
alt_long_secret = short_secret + "Y"
|
||||
alt_long_hash = calc(alt_long_secret)
|
||||
self.assertNotEqual(alt_long_hash, short_hash)
|
||||
self.assertNotEqual(alt_long_hash, long_hash)
|
||||
|
||||
# for hash(), should throw error if password too large
|
||||
calc(short_secret, for_hash=True)
|
||||
self.assertRaises(exc.PasswordSizeError, calc, long_secret, for_hash=True)
|
||||
self.assertRaises(exc.PasswordSizeError, calc, alt_long_secret, for_hash=True)
|
||||
|
||||
#=============================================================================
|
||||
# cisco pix
|
||||
#=============================================================================
|
||||
class cisco_pix_test(_PixAsaSharedTest):
|
||||
handler = hash.cisco_pix
|
||||
|
||||
#: known correct pix hashes
|
||||
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
|
||||
#
|
||||
# passlib reference vectors (PIX-specific)
|
||||
#
|
||||
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
|
||||
# and general notes about the 'passlib reference vectors' test set.
|
||||
#
|
||||
# All of the following are PIX-specific, as ASA starts
|
||||
# to use a different padding size at 13 characters.
|
||||
#
|
||||
# TODO: these need confirming w/ an actual PIX system.
|
||||
#
|
||||
|
||||
# 13 char password
|
||||
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'),
|
||||
(('0123456789abc', '3'), 'ylJTd/qei66WZe3w'),
|
||||
(('0123456789abc', '36'), 'hDx8QRlUhwd6bU8N'),
|
||||
(('0123456789abc', '365'), 'vYOOtnkh1HXcMrM7'),
|
||||
(('0123456789abc', '3333'), 'ylJTd/qei66WZe3w'),
|
||||
(('0123456789abc', '3636'), 'hDx8QRlUhwd6bU8N'),
|
||||
(('0123456789abc', '3653'), 'vYOOtnkh1HXcMrM7'),
|
||||
(('0123456789abc', 'user'), 'f4/.SALxqDo59mfV'),
|
||||
(('0123456789abc', 'user1234'), 'f4/.SALxqDo59mfV'),
|
||||
|
||||
# 14 char password
|
||||
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'),
|
||||
(('0123456789abcd', '3'), 'f5lvmqWYj9gJqkIH'),
|
||||
(('0123456789abcd', '36'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '365'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '3333'), 'f5lvmqWYj9gJqkIH'),
|
||||
(('0123456789abcd', '3636'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '3653'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', 'adm'), 'DbPLCFIkHc2SiyDk'),
|
||||
(('0123456789abcd', 'adma'), 'DbPLCFIkHc2SiyDk'),
|
||||
(('0123456789abcd', 'user'), 'WfO2UiTapPkF/FSn'),
|
||||
(('0123456789abcd', 'user1234'), 'WfO2UiTapPkF/FSn'),
|
||||
|
||||
# 15 char password
|
||||
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'),
|
||||
(('0123456789abcde', '3'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '36'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '365'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3333'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3636'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3653'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', 'adm'), 'KgKx1UQvdR/09i9u'),
|
||||
(('0123456789abcde', 'adma'), 'KgKx1UQvdR/09i9u'),
|
||||
(('0123456789abcde', 'user'), 'qLopkenJ4WBqxaZN'),
|
||||
(('0123456789abcde', 'user1234'), 'qLopkenJ4WBqxaZN'),
|
||||
|
||||
# 16 char password
|
||||
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '36'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '365'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3333'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3636'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3653'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', 'user'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', 'user1234'), '.7nfVBEIEu4KbF/1'),
|
||||
]
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# cisco asa
|
||||
#=============================================================================
|
||||
class cisco_asa_test(_PixAsaSharedTest):
|
||||
handler = hash.cisco_asa
|
||||
|
||||
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
|
||||
#
|
||||
# passlib reference vectors (ASA-specific)
|
||||
#
|
||||
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
|
||||
# and general notes about the 'passlib reference vectors' test set.
|
||||
#
|
||||
|
||||
# 13 char password
|
||||
# NOTE: past this point, ASA pads to 32 bytes instead of 16
|
||||
# for all cases where user is set (secret + 4 bytes > 16),
|
||||
# but still uses 16 bytes for enable pwds (secret <= 16).
|
||||
# hashes w/ user WON'T match PIX, but "enable" passwords will.
|
||||
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '36'), 'FRV9JG18UBEgX0.O'),
|
||||
(('0123456789abc', '365'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3333'), 'NmrkP98nT7RAeKZz'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3636'), 'FRV9JG18UBEgX0.O'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3653'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
|
||||
(('0123456789abc', 'user'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
|
||||
(('0123456789abc', 'user1234'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
|
||||
|
||||
# 14 char password
|
||||
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3'), 'yxGoujXKPduTVaYB'),
|
||||
(('0123456789abcd', '36'), 'W0jckhnhjnr/DiT/'),
|
||||
(('0123456789abcd', '365'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3333'), 'yxGoujXKPduTVaYB'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3636'), 'W0jckhnhjnr/DiT/'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3653'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'adm'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'adma'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'user'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'user1234'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
|
||||
|
||||
# 15 char password
|
||||
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3'), 'nAZrQoHaL.fgrIqt'),
|
||||
(('0123456789abcde', '36'), '2GxIQ6ICE795587X'),
|
||||
(('0123456789abcde', '365'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3333'), 'nAZrQoHaL.fgrIqt'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3636'), '2GxIQ6ICE795587X'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3653'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'adm'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'adma'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'user'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'user1234'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
|
||||
|
||||
# 16 char password
|
||||
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '36'), 'GhI8.yFSC5lwoafg'),
|
||||
(('0123456789abcdef', '365'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3333'), 'Ghdi1IlsswgYzzMH'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3636'), 'GhI8.yFSC5lwoafg'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3653'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', 'user'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', 'user1234'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
|
||||
|
||||
# 17 char password
|
||||
# NOTE: past this point, ASA pads to 32 bytes instead of 16
|
||||
# for ALL cases, since secret > 16 bytes even for enable pwds;
|
||||
# and so none of these rest here should match PIX.
|
||||
(('0123456789abcdefq', ''), 'bKshl.EN.X3CVFRQ'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '36'), 'JAeTXHs0n30svlaG'),
|
||||
(('0123456789abcdefq', '365'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3333'), 'USEJbxI6.VY4ecBP'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3636'), 'JAeTXHs0n30svlaG'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3653'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', 'user'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', 'user1234'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
|
||||
|
||||
# 27 char password
|
||||
(('0123456789abcdefqwertyuiopa', ''), '4wp19zS3OCe.2jt5'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '36'), 'PjUoGqWBKPyV9qOe'),
|
||||
(('0123456789abcdefqwertyuiopa', '365'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3333'), 'rd/ZMuGTJFIb2BNG'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3636'), 'PjUoGqWBKPyV9qOe'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3653'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', 'user'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', 'user1234'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
|
||||
|
||||
# 28 char password
|
||||
# NOTE: past this point, ASA stops appending the username AT ALL,
|
||||
# even though there's still room for the first few chars.
|
||||
(('0123456789abcdefqwertyuiopas', ''), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', '36'), 'W6nbOddI0SutTK7m'),
|
||||
(('0123456789abcdefqwertyuiopas', '365'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', 'user'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', 'user1234'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
|
||||
# 32 char password
|
||||
# NOTE: this is max size that ASA allows, and throws error for larger
|
||||
(('0123456789abcdefqwertyuiopasdfgh', ''), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', '36'), '5hPT/iC6DnoBxo6a'),
|
||||
(('0123456789abcdefqwertyuiopasdfgh', '365'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', 'user'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', 'user1234'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
]
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# cisco type 7
|
||||
#=============================================================================
|
||||
class cisco_type7_test(HandlerCase):
|
||||
handler = hash.cisco_type7
|
||||
salt_bits = 4
|
||||
salt_type = int
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# http://mccltd.net/blog/?p=1034
|
||||
#
|
||||
("secure ", "04480E051A33490E"),
|
||||
|
||||
#
|
||||
# http://insecure.org/sploits/cisco.passwords.html
|
||||
#
|
||||
("Its time to go to lunch!",
|
||||
"153B1F1F443E22292D73212D5300194315591954465A0D0B59"),
|
||||
|
||||
#
|
||||
# http://blog.ioshints.info/2007/11/type-7-decryption-in-cisco-ios.html
|
||||
#
|
||||
("t35t:pa55w0rd", "08351F1B1D431516475E1B54382F"),
|
||||
|
||||
#
|
||||
# http://www.m00nie.com/2011/09/cisco-type-7-password-decryption-and-encryption-with-perl/
|
||||
#
|
||||
("hiImTesting:)", "020E0D7206320A325847071E5F5E"),
|
||||
|
||||
#
|
||||
# http://packetlife.net/forums/thread/54/
|
||||
#
|
||||
("cisco123", "060506324F41584B56"),
|
||||
("cisco123", "1511021F07257A767B"),
|
||||
|
||||
#
|
||||
# source ?
|
||||
#
|
||||
('Supe&8ZUbeRp4SS', "06351A3149085123301517391C501918"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE, '0958EDC8A9F495F6F8A5FD'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
# salt with hex value
|
||||
"0A480E051A33490E",
|
||||
|
||||
# salt value > 52. this may in fact be valid, but we reject it for now
|
||||
# (see docs for more).
|
||||
'99400E4812',
|
||||
]
|
||||
|
||||
def test_90_decode(self):
|
||||
"""test cisco_type7.decode()"""
|
||||
from passlib.utils import to_unicode, to_bytes
|
||||
|
||||
handler = self.handler
|
||||
for secret, hash in self.known_correct_hashes:
|
||||
usecret = to_unicode(secret)
|
||||
bsecret = to_bytes(secret)
|
||||
self.assertEqual(handler.decode(hash), usecret)
|
||||
self.assertEqual(handler.decode(hash, None), bsecret)
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, handler.decode,
|
||||
'0958EDC8A9F495F6F8A5FD', 'ascii')
|
||||
|
||||
def test_91_salt(self):
|
||||
"""test salt value border cases"""
|
||||
handler = self.handler
|
||||
self.assertRaises(TypeError, handler, salt=None)
|
||||
handler(salt=None, use_defaults=True)
|
||||
self.assertRaises(TypeError, handler, salt='abc')
|
||||
self.assertRaises(ValueError, handler, salt=-10)
|
||||
self.assertRaises(ValueError, handler, salt=100)
|
||||
|
||||
self.assertRaises(TypeError, handler.using, salt='abc')
|
||||
self.assertRaises(ValueError, handler.using, salt=-10)
|
||||
self.assertRaises(ValueError, handler.using, salt=100)
|
||||
with self.assertWarningList("salt/offset must be.*"):
|
||||
subcls = handler.using(salt=100, relaxed=True)
|
||||
self.assertEqual(subcls(use_defaults=True).salt, 52)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,413 @@
|
||||
"""passlib.tests.test_handlers_django - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils import repeat_string
|
||||
from passlib.utils.compat import u
|
||||
from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest
|
||||
from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE
|
||||
from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \
|
||||
check_django_hasher_has_backend
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# django
|
||||
#=============================================================================
|
||||
|
||||
# standard string django uses
|
||||
UPASS_LETMEIN = u('l\xe8tmein')
|
||||
|
||||
def vstr(version):
|
||||
return ".".join(str(e) for e in version)
|
||||
|
||||
class _DjangoHelper(TestCase):
|
||||
"""
|
||||
mixin for HandlerCase subclasses that are testing a hasher
|
||||
which is also present in django.
|
||||
"""
|
||||
__unittest_skip = True
|
||||
|
||||
#: minimum django version where hash alg is present / that we support testing against
|
||||
min_django_version = MIN_DJANGO_VERSION
|
||||
|
||||
#: max django version where hash alg is present
|
||||
#: TODO: for a bunch of the tests below, this is just max version where
|
||||
#: settings.PASSWORD_HASHERS includes it by default -- could add helper to patch
|
||||
#: desired django hasher back in for duration of test.
|
||||
#: XXX: change this to "disabled_in_django_version" instead?
|
||||
max_django_version = None
|
||||
|
||||
def _require_django_support(self):
|
||||
# make sure min django version
|
||||
if DJANGO_VERSION < self.min_django_version:
|
||||
raise self.skipTest("Django >= %s not installed" % vstr(self.min_django_version))
|
||||
if self.max_django_version and DJANGO_VERSION > self.max_django_version:
|
||||
raise self.skipTest("Django <= %s not installed" % vstr(self.max_django_version))
|
||||
|
||||
# make sure django has a backend for specified hasher
|
||||
name = self.handler.django_name
|
||||
if not check_django_hasher_has_backend(name):
|
||||
raise self.skipTest('django hasher %r not available' % name)
|
||||
|
||||
return True
|
||||
|
||||
extra_fuzz_verifiers = HandlerCase.fuzz_verifiers + (
|
||||
"fuzz_verifier_django",
|
||||
)
|
||||
|
||||
def fuzz_verifier_django(self):
|
||||
try:
|
||||
self._require_django_support()
|
||||
except SkipTest:
|
||||
return None
|
||||
from django.contrib.auth.hashers import check_password
|
||||
|
||||
def verify_django(secret, hash):
|
||||
"""django/check_password"""
|
||||
if self.handler.name == "django_bcrypt" and hash.startswith("bcrypt$$2y$"):
|
||||
hash = hash.replace("$$2y$", "$$2a$")
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
return check_password(secret, hash)
|
||||
return verify_django
|
||||
|
||||
def test_90_django_reference(self):
|
||||
"""run known correct hashes through Django's check_password()"""
|
||||
self._require_django_support()
|
||||
# XXX: esp. when it's no longer supported by django,
|
||||
# should verify it's *NOT* recognized
|
||||
from django.contrib.auth.hashers import check_password
|
||||
assert self.known_correct_hashes
|
||||
for secret, hash in self.iter_known_hashes():
|
||||
self.assertTrue(check_password(secret, hash),
|
||||
"secret=%r hash=%r failed to verify" %
|
||||
(secret, hash))
|
||||
self.assertFalse(check_password('x' + secret, hash),
|
||||
"mangled secret=%r hash=%r incorrect verified" %
|
||||
(secret, hash))
|
||||
|
||||
def test_91_django_generation(self):
|
||||
"""test against output of Django's make_password()"""
|
||||
self._require_django_support()
|
||||
# XXX: esp. when it's no longer supported by django,
|
||||
# should verify it's *NOT* recognized
|
||||
from passlib.utils import tick
|
||||
from django.contrib.auth.hashers import make_password
|
||||
name = self.handler.django_name # set for all the django_* handlers
|
||||
end = tick() + self.max_fuzz_time/2
|
||||
generator = self.FuzzHashGenerator(self, self.getRandom())
|
||||
while tick() < end:
|
||||
secret, other = generator.random_password_pair()
|
||||
if not secret: # django rejects empty passwords.
|
||||
continue
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
hash = make_password(secret, hasher=name)
|
||||
self.assertTrue(self.do_identify(hash))
|
||||
self.assertTrue(self.do_verify(secret, hash))
|
||||
self.assertFalse(self.do_verify(other, hash))
|
||||
|
||||
class django_disabled_test(HandlerCase):
|
||||
"""test django_disabled"""
|
||||
handler = hash.django_disabled
|
||||
disabled_contains_salt = True
|
||||
|
||||
known_correct_hashes = [
|
||||
# *everything* should hash to "!", and nothing should verify
|
||||
("password", "!"),
|
||||
("", "!"),
|
||||
(UPASS_TABLE, "!"),
|
||||
]
|
||||
|
||||
known_alternate_hashes = [
|
||||
# django 1.6 appends random alpnum string
|
||||
("!9wa845vn7098ythaehasldkfj", "password", "!"),
|
||||
]
|
||||
|
||||
class django_des_crypt_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_des_crypt"""
|
||||
handler = hash.django_des_crypt
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# ensures only first two digits of salt count.
|
||||
("password", 'crypt$c2$c2M87q...WWcU'),
|
||||
("password", 'crypt$c2e86$c2M87q...WWcU'),
|
||||
("passwordignoreme", 'crypt$c2.AZ$c2M87q...WWcU'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'),
|
||||
(UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'),
|
||||
(u("hell\u00D6"), "crypt$sa$saykDgk3BPZ9E"),
|
||||
|
||||
# prevent regression of issue 22
|
||||
("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'),
|
||||
]
|
||||
|
||||
known_alternate_hashes = [
|
||||
# ensure django 1.4 empty salt field is accepted;
|
||||
# but that salt field is re-filled (for django 1.0 compatibility)
|
||||
('crypt$$c2M87q...WWcU', "password", 'crypt$c2$c2M87q...WWcU'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'sha1$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'crypt$c2$c2M87q',
|
||||
|
||||
# salt must be >2
|
||||
'crypt$f$c2M87q...WWcU',
|
||||
|
||||
# make sure first 2 chars of salt & chk field agree.
|
||||
'crypt$ffe86$c2M87q...WWcU',
|
||||
]
|
||||
|
||||
class django_salted_md5_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_salted_md5"""
|
||||
handler = hash.django_salted_md5
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# test extra large salt
|
||||
("password", 'md5$123abcdef$c8272612932975ee80e8a35995708e80'),
|
||||
|
||||
# test django 1.4 alphanumeric salt
|
||||
("test", 'md5$3OpqnFAHW5CT$54b29300675271049a1ebae07b395e20'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'md5$c2e86$92105508419a81a6babfaecf876a2fa0'),
|
||||
(UPASS_TABLE, 'md5$d9eb8$01495b32852bffb27cf5d4394fe7a54c'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'sha1$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'md5$aa$bb',
|
||||
]
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_salt_size(self):
|
||||
# workaround for django14 regression --
|
||||
# 1.4 won't accept hashes with empty salt strings, unlike 1.3 and earlier.
|
||||
# looks to be fixed in a future release -- https://code.djangoproject.com/ticket/18144
|
||||
# for now, we avoid salt_size==0 under 1.4
|
||||
handler = self.handler
|
||||
default = handler.default_salt_size
|
||||
assert handler.min_salt_size == 0
|
||||
lower = 1
|
||||
upper = handler.max_salt_size or default*4
|
||||
return self.randintgauss(lower, upper, default, default*.5)
|
||||
|
||||
class django_salted_sha1_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_salted_sha1"""
|
||||
handler = hash.django_salted_sha1
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# test extra large salt
|
||||
("password",'sha1$123abcdef$e4a1877b0e35c47329e7ed7e58014276168a37ba'),
|
||||
|
||||
# test django 1.4 alphanumeric salt
|
||||
("test", 'sha1$bcwHF9Hy8lxS$6b4cfa0651b43161c6f1471ce9523acf1f751ba3'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'sha1$c2e86$0f75c5d7fbd100d587c127ef0b693cde611b4ada'),
|
||||
(UPASS_TABLE, 'sha1$6d853$ef13a4d8fb57aed0cb573fe9c82e28dc7fd372d4'),
|
||||
|
||||
# generic password
|
||||
("MyPassword", 'sha1$54123$893cf12e134c3c215f3a76bd50d13f92404a54d3'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'md5$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'sha1$c2e86$0f75',
|
||||
]
|
||||
|
||||
# reuse custom random_salt_size() helper...
|
||||
FuzzHashGenerator = django_salted_md5_test.FuzzHashGenerator
|
||||
|
||||
class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_pbkdf2_sha256"""
|
||||
handler = hash.django_pbkdf2_sha256
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.4 hasher
|
||||
#
|
||||
('not a password',
|
||||
'pbkdf2_sha256$10000$kjVJaVz6qsnJ$5yPHw3rwJGECpUf70daLGhOrQ5+AMxIJdz1c3bqK1Rs='),
|
||||
(UPASS_TABLE,
|
||||
'pbkdf2_sha256$10000$bEwAfNrH1TlQ$OgYUblFNUX1B8GfMqaCYUK/iHyO0pa7STTDdaEJBuY0='),
|
||||
]
|
||||
|
||||
class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_pbkdf2_sha1"""
|
||||
handler = hash.django_pbkdf2_sha1
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.4 hashers
|
||||
#
|
||||
('not a password',
|
||||
'pbkdf2_sha1$10000$wz5B6WkasRoF$atJmJ1o+XfJxKq1+Nu1f1i57Z5I='),
|
||||
(UPASS_TABLE,
|
||||
'pbkdf2_sha1$10000$KZKWwvqb8BfL$rw5pWsxJEU4JrZAQhHTCO+u0f5Y='),
|
||||
]
|
||||
|
||||
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
|
||||
class django_bcrypt_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt"""
|
||||
handler = hash.django_bcrypt
|
||||
# XXX: not sure when this wasn't in default list anymore. somewhere in [2.0 - 2.2]
|
||||
max_django_version = (2, 0)
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# just copied and adapted a few test vectors from bcrypt (above),
|
||||
# since django_bcrypt is just a wrapper for the real bcrypt class.
|
||||
#
|
||||
('', 'bcrypt$$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
|
||||
('abcdefghijklmnopqrstuvwxyz',
|
||||
'bcrypt$$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
|
||||
(UPASS_TABLE,
|
||||
'bcrypt$$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'),
|
||||
]
|
||||
|
||||
# NOTE: the following have been cloned from _bcrypt_test()
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# speed up test w/ lower rounds
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(django_bcrypt_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
# omit multi-ident tests, only $2a$ counts for this class
|
||||
# XXX: enable this to check 2a / 2b?
|
||||
return None
|
||||
|
||||
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
|
||||
class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt_sha256"""
|
||||
handler = hash.django_bcrypt_sha256
|
||||
forbidden_characters = None
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.6 hasher
|
||||
#
|
||||
('',
|
||||
'bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu'),
|
||||
(UPASS_LETMEIN,
|
||||
'bcrypt_sha256$$2a$08$NDjSAIcas.EcoxCRiArvT.MkNiPYVhrsrnJsRkLueZOoV1bsQqlmC'),
|
||||
(UPASS_TABLE,
|
||||
'bcrypt_sha256$$2a$06$kCXUnRFQptGg491siDKNTu8RxjBGSjALHRuvhPYNFsa4Ea5d9M48u'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
(repeat_string("abc123",72),
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OySmyXA8FoY4PjGizjE1QSDfuL5MXNni'),
|
||||
(repeat_string("abc123",72)+"qwr",
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61Ocy0BEz1RK6xslSNi8PlaLX2pe7x/KQG'),
|
||||
(repeat_string("abc123",72)+"xyz",
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OvY2zoRVUa2Pugv2ExVOUT2YmhvxUFUa'),
|
||||
]
|
||||
|
||||
known_malformed_hashers = [
|
||||
# data in django salt field
|
||||
'bcrypt_sha256$xyz$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu',
|
||||
]
|
||||
|
||||
# NOTE: the following have been cloned from _bcrypt_test()
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# speed up test w/ lower rounds
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(django_bcrypt_sha256_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
# omit multi-ident tests, only $2a$ counts for this class
|
||||
# XXX: enable this to check 2a / 2b?
|
||||
return None
|
||||
|
||||
from passlib.tests.test_handlers_argon2 import _base_argon2_test
|
||||
|
||||
@skipUnless(hash.argon2.has_backend(), "no argon2 backends available")
|
||||
class django_argon2_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt"""
|
||||
handler = hash.django_argon2
|
||||
|
||||
# NOTE: most of this adapted from _base_argon2_test & argon2pure test
|
||||
|
||||
known_correct_hashes = [
|
||||
# sample test
|
||||
("password", 'argon2$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'),
|
||||
|
||||
# sample w/ all parameters different
|
||||
("password", 'argon2$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'),
|
||||
|
||||
# generated from django 1.10.3
|
||||
(UPASS_LETMEIN, 'argon2$argon2i$v=19$m=512,t=2,p=2$V25jN1l4UUJZWkR1$MxpA1BD2Gh7+D79gaAw6sQ'),
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(django_argon2_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*Using argon2pure backend.*")
|
||||
|
||||
def do_stub_encrypt(self, handler=None, **settings):
|
||||
# overriding default since no way to get stub config from argon2._calc_hash()
|
||||
# (otherwise test_21b_max_rounds blocks trying to do max rounds)
|
||||
handler = (handler or self.handler).using(**settings)
|
||||
self = handler.wrapped(use_defaults=True)
|
||||
self.checksum = self._stub_checksum
|
||||
assert self.checksum
|
||||
return handler._wrap_hash(self.to_string())
|
||||
|
||||
def test_03_legacy_hash_workflow(self):
|
||||
# override base method
|
||||
raise self.skipTest("legacy 1.6 workflow not supported")
|
||||
|
||||
class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator):
|
||||
|
||||
def random_type(self):
|
||||
# override default since django only uses type I (see note in class)
|
||||
return "I"
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(1, 3, 2, 1)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,480 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils.compat import u
|
||||
from passlib.tests.utils import TestCase, HandlerCase
|
||||
from passlib.tests.test_handlers import UPASS_WAV
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# ldap_pbkdf2_{digest}
|
||||
#=============================================================================
|
||||
# NOTE: since these are all wrappers for the pbkdf2_{digest} hasehs,
|
||||
# they don't extensive separate testing.
|
||||
|
||||
class ldap_pbkdf2_test(TestCase):
|
||||
|
||||
def test_wrappers(self):
|
||||
"""test ldap pbkdf2 wrappers"""
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha1.verify(
|
||||
"password",
|
||||
'{PBKDF2}1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI',
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha256.verify(
|
||||
"password",
|
||||
'{PBKDF2-SHA256}1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg'
|
||||
'.fJPeq1h/gXXY7acBp9/6c.tmQ'
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha512.verify(
|
||||
"password",
|
||||
'{PBKDF2-SHA512}1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
|
||||
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
|
||||
)
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# pbkdf2 hashes
|
||||
#=============================================================================
|
||||
class atlassian_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.atlassian_pbkdf2_sha1
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# generated using Jira
|
||||
#
|
||||
("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'),
|
||||
(UPASS_WAV,
|
||||
"{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# bad char ---\/
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy!0IPksHChwoTAVYFrhsgoq8/p'
|
||||
|
||||
# bad size, missing padding
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/'
|
||||
|
||||
# bad size, with correct padding
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/='
|
||||
]
|
||||
|
||||
class pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# zero padded rounds field
|
||||
'$pbkdf2$01212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
|
||||
|
||||
# empty rounds field
|
||||
'$pbkdf2$$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
|
||||
|
||||
# too many field
|
||||
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc$',
|
||||
]
|
||||
|
||||
class pbkdf2_sha256_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha256
|
||||
known_correct_hashes = [
|
||||
("password",
|
||||
'$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ'
|
||||
),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI'
|
||||
),
|
||||
]
|
||||
|
||||
class pbkdf2_sha512_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha512
|
||||
known_correct_hashes = [
|
||||
("password",
|
||||
'$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
|
||||
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
|
||||
),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt'
|
||||
'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q'
|
||||
),
|
||||
]
|
||||
|
||||
class cta_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.cta_pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors from original implementation
|
||||
#
|
||||
(u("hashy the \N{SNOWMAN}"), '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="),
|
||||
(UPASS_WAV,
|
||||
"$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="),
|
||||
]
|
||||
|
||||
class dlitz_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.dlitz_pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors from original implementation
|
||||
#
|
||||
('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'),
|
||||
('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'),
|
||||
('dcl', '$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL'),
|
||||
('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'),
|
||||
(UPASS_WAV,
|
||||
'$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'),
|
||||
]
|
||||
|
||||
class grub_pbkdf2_sha512_test(HandlerCase):
|
||||
handler = hash.grub_pbkdf2_sha512
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors generated from cmd line tool
|
||||
#
|
||||
|
||||
# salt=32 bytes
|
||||
(UPASS_WAV,
|
||||
'grub.pbkdf2.sha512.10000.BCAC1CEC5E4341C8C511C529'
|
||||
'7FA877BE91C2817B32A35A3ECF5CA6B8B257F751.6968526A'
|
||||
'2A5B1AEEE0A29A9E057336B48D388FFB3F600233237223C21'
|
||||
'04DE1752CEC35B0DD1ED49563398A282C0F471099C2803FBA'
|
||||
'47C7919CABC43192C68F60'),
|
||||
|
||||
# salt=64 bytes
|
||||
('toomanysecrets',
|
||||
'grub.pbkdf2.sha512.10000.9B436BB6978682363D5C449B'
|
||||
'BEAB322676946C632208BC1294D51F47174A9A3B04A7E4785'
|
||||
'986CD4EA7470FAB8FE9F6BD522D1FC6C51109A8596FB7AD48'
|
||||
'7C4493.0FE5EF169AFFCB67D86E2581B1E251D88C777B98BA'
|
||||
'2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4'
|
||||
'634083F9A20F4B6F339A52FBD6BED618E527B'),
|
||||
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scram hash
|
||||
#=============================================================================
|
||||
class scram_test(HandlerCase):
|
||||
handler = hash.scram
|
||||
|
||||
# TODO: need a bunch more reference vectors from some real
|
||||
# SCRAM transactions.
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# taken from example in SCRAM specification (rfc 5802)
|
||||
#
|
||||
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# same as 5802 example hash, but with sha-256 & sha-512 added.
|
||||
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
|
||||
|
||||
# test unicode passwords & saslprep (all the passwords below
|
||||
# should normalize to the same value: 'IX \xE0')
|
||||
(u('IX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
(u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
(u('\u00ADIX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# zero-padding in rounds
|
||||
'$scram$04096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# non-digit in rounds
|
||||
'$scram$409A$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# bad char in salt ---\/
|
||||
'$scram$4096$QSXCR.Q6sek8bf9-$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# bad char in digest ---\/
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX3-',
|
||||
|
||||
# missing sections
|
||||
'$scram$4096$QSXCR.Q6sek8bf92',
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$',
|
||||
|
||||
# too many sections
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30$',
|
||||
|
||||
# missing separator
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY',
|
||||
|
||||
# too many chars in alg name
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'shaxxx-190=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# missing sha-1 alg
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# non-iana name
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(scram_test, self).setUp()
|
||||
|
||||
# some platforms lack stringprep (e.g. Jython, IronPython)
|
||||
self.require_stringprep()
|
||||
|
||||
# silence norm_hash_name() warning
|
||||
warnings.filterwarnings("ignore", r"norm_hash_name\(\): unknown hash")
|
||||
|
||||
def test_90_algs(self):
|
||||
"""test parsing of 'algs' setting"""
|
||||
defaults = dict(salt=b'A'*10, rounds=1000)
|
||||
def parse(algs, **kwds):
|
||||
for k in defaults:
|
||||
kwds.setdefault(k, defaults[k])
|
||||
return self.handler(algs=algs, **kwds).algs
|
||||
|
||||
# None -> default list
|
||||
self.assertEqual(parse(None, use_defaults=True), hash.scram.default_algs)
|
||||
self.assertRaises(TypeError, parse, None)
|
||||
|
||||
# strings should be parsed
|
||||
self.assertEqual(parse("sha1"), ["sha-1"])
|
||||
self.assertEqual(parse("sha1, sha256, md5"), ["md5","sha-1","sha-256"])
|
||||
|
||||
# lists should be normalized
|
||||
self.assertEqual(parse(["sha-1","sha256"]), ["sha-1","sha-256"])
|
||||
|
||||
# sha-1 required
|
||||
self.assertRaises(ValueError, parse, ["sha-256"])
|
||||
self.assertRaises(ValueError, parse, algs=[], use_defaults=True)
|
||||
|
||||
# alg names must be < 10 chars
|
||||
self.assertRaises(ValueError, parse, ["sha-1","shaxxx-190"])
|
||||
|
||||
# alg & checksum mutually exclusive.
|
||||
self.assertRaises(RuntimeError, parse, ['sha-1'],
|
||||
checksum={"sha-1": b"\x00"*20})
|
||||
|
||||
def test_90_checksums(self):
|
||||
"""test internal parsing of 'checksum' keyword"""
|
||||
# check non-bytes checksum values are rejected
|
||||
self.assertRaises(TypeError, self.handler, use_defaults=True,
|
||||
checksum={'sha-1': u('X')*20})
|
||||
|
||||
# check sha-1 is required
|
||||
self.assertRaises(ValueError, self.handler, use_defaults=True,
|
||||
checksum={'sha-256': b'X'*32})
|
||||
|
||||
# XXX: anything else that's not tested by the other code already?
|
||||
|
||||
def test_91_extract_digest_info(self):
|
||||
"""test scram.extract_digest_info()"""
|
||||
edi = self.handler.extract_digest_info
|
||||
|
||||
# return appropriate value or throw KeyError
|
||||
h = "$scram$10$AAAAAA$sha-1=AQ,bbb=Ag,ccc=Aw"
|
||||
s = b'\x00'*4
|
||||
self.assertEqual(edi(h,"SHA1"), (s,10, b'\x01'))
|
||||
self.assertEqual(edi(h,"bbb"), (s,10, b'\x02'))
|
||||
self.assertEqual(edi(h,"ccc"), (s,10, b'\x03'))
|
||||
self.assertRaises(KeyError, edi, h, "ddd")
|
||||
|
||||
# config strings should cause value error.
|
||||
c = "$scram$10$....$sha-1,bbb,ccc"
|
||||
self.assertRaises(ValueError, edi, c, "sha-1")
|
||||
self.assertRaises(ValueError, edi, c, "bbb")
|
||||
self.assertRaises(ValueError, edi, c, "ddd")
|
||||
|
||||
def test_92_extract_digest_algs(self):
|
||||
"""test scram.extract_digest_algs()"""
|
||||
eda = self.handler.extract_digest_algs
|
||||
|
||||
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), ["sha-1"])
|
||||
|
||||
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', format="hashlib"),
|
||||
["sha1"])
|
||||
|
||||
self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
|
||||
["sha-1","sha-256","sha-512"])
|
||||
|
||||
def test_93_derive_digest(self):
|
||||
"""test scram.derive_digest()"""
|
||||
# NOTE: this just does a light test, since derive_digest
|
||||
# is used by hash / verify, and is tested pretty well via those.
|
||||
hash = self.handler.derive_digest
|
||||
|
||||
# check various encodings of password work.
|
||||
s1 = b'\x01\x02\x03'
|
||||
d1 = b'\xb2\xfb\xab\x82[tNuPnI\x8aZZ\x19\x87\xcen\xe9\xd3'
|
||||
self.assertEqual(hash(u("\u2168"), s1, 1000, 'sha-1'), d1)
|
||||
self.assertEqual(hash(b"\xe2\x85\xa8", s1, 1000, 'SHA-1'), d1)
|
||||
self.assertEqual(hash(u("IX"), s1, 1000, 'sha1'), d1)
|
||||
self.assertEqual(hash(b"IX", s1, 1000, 'SHA1'), d1)
|
||||
|
||||
# check algs
|
||||
self.assertEqual(hash("IX", s1, 1000, 'md5'),
|
||||
b'3\x19\x18\xc0\x1c/\xa8\xbf\xe4\xa3\xc2\x8eM\xe8od')
|
||||
self.assertRaises(ValueError, hash, "IX", s1, 1000, 'sha-666')
|
||||
|
||||
# check rounds
|
||||
self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1')
|
||||
|
||||
# unicode salts accepted as of passlib 1.7 (previous caused TypeError)
|
||||
self.assertEqual(hash(u("IX"), s1.decode("latin-1"), 1000, 'sha1'), d1)
|
||||
|
||||
def test_94_saslprep(self):
|
||||
"""test hash/verify use saslprep"""
|
||||
# NOTE: this just does a light test that saslprep() is being
|
||||
# called in various places, relying in saslpreps()'s tests
|
||||
# to verify full normalization behavior.
|
||||
|
||||
# hash unnormalized
|
||||
h = self.do_encrypt(u("I\u00ADX"))
|
||||
self.assertTrue(self.do_verify(u("IX"), h))
|
||||
self.assertTrue(self.do_verify(u("\u2168"), h))
|
||||
|
||||
# hash normalized
|
||||
h = self.do_encrypt(u("\xF3"))
|
||||
self.assertTrue(self.do_verify(u("o\u0301"), h))
|
||||
self.assertTrue(self.do_verify(u("\u200Do\u0301"), h))
|
||||
|
||||
# throws error if forbidden char provided
|
||||
self.assertRaises(ValueError, self.do_encrypt, u("\uFDD0"))
|
||||
self.assertRaises(ValueError, self.do_verify, u("\uFDD0"), h)
|
||||
|
||||
def test_94_using_w_default_algs(self, param="default_algs"):
|
||||
"""using() -- 'default_algs' parameter"""
|
||||
# create subclass
|
||||
handler = self.handler
|
||||
orig = list(handler.default_algs) # in case it's modified in place
|
||||
subcls = handler.using(**{param: "sha1,md5"})
|
||||
|
||||
# shouldn't have changed handler
|
||||
self.assertEqual(handler.default_algs, orig)
|
||||
|
||||
# should have own set
|
||||
self.assertEqual(subcls.default_algs, ["md5", "sha-1"])
|
||||
|
||||
# test hash output
|
||||
h1 = subcls.hash("dummy")
|
||||
self.assertEqual(handler.extract_digest_algs(h1), ["md5", "sha-1"])
|
||||
|
||||
def test_94_using_w_algs(self):
|
||||
"""using() -- 'algs' parameter"""
|
||||
self.test_94_using_w_default_algs(param="algs")
|
||||
|
||||
def test_94_needs_update_algs(self):
|
||||
"""needs_update() -- algs setting"""
|
||||
handler1 = self.handler.using(algs="sha1,md5")
|
||||
|
||||
# shouldn't need update, has same algs
|
||||
h1 = handler1.hash("dummy")
|
||||
self.assertFalse(handler1.needs_update(h1))
|
||||
|
||||
# *currently* shouldn't need update, has superset of algs required by handler2
|
||||
# (may change this policy)
|
||||
handler2 = handler1.using(algs="sha1")
|
||||
self.assertFalse(handler2.needs_update(h1))
|
||||
|
||||
# should need update, doesn't have all algs required by handler3
|
||||
handler3 = handler1.using(algs="sha1,sha256")
|
||||
self.assertTrue(handler3.needs_update(h1))
|
||||
|
||||
def test_95_context_algs(self):
|
||||
"""test handling of 'algs' in context object"""
|
||||
handler = self.handler
|
||||
from passlib.context import CryptContext
|
||||
c1 = CryptContext(["scram"], scram__algs="sha1,md5")
|
||||
|
||||
h = c1.hash("dummy")
|
||||
self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
|
||||
self.assertFalse(c1.needs_update(h))
|
||||
|
||||
c2 = c1.copy(scram__algs="sha1")
|
||||
self.assertFalse(c2.needs_update(h))
|
||||
|
||||
c2 = c1.copy(scram__algs="sha1,sha256")
|
||||
self.assertTrue(c2.needs_update(h))
|
||||
|
||||
def test_96_full_verify(self):
|
||||
"""test verify(full=True) flag"""
|
||||
def vpart(s, h):
|
||||
return self.handler.verify(s, h)
|
||||
def vfull(s, h):
|
||||
return self.handler.verify(s, h, full=True)
|
||||
|
||||
# reference
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertTrue(vfull('pencil', h))
|
||||
self.assertFalse(vfull('tape', h))
|
||||
|
||||
# catch truncated digests.
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhV,' # -1 char
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertRaises(ValueError, vfull, 'pencil', h)
|
||||
|
||||
# catch padded digests.
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVYa,' # +1 char
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertRaises(ValueError, vfull, 'pencil', h)
|
||||
|
||||
# catch hash containing digests belonging to diff passwords.
|
||||
# proper behavior for quick-verify (the default) is undefined,
|
||||
# but full-verify should throw error.
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' # 'pencil'
|
||||
'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc,' # 'tape'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' # 'pencil'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertTrue(vpart('tape', h))
|
||||
self.assertFalse(vpart('pencil', h))
|
||||
self.assertRaises(ValueError, vfull, 'pencil', h)
|
||||
self.assertRaises(ValueError, vfull, 'tape', h)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,111 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# scrypt hash
|
||||
#=============================================================================
|
||||
class _scrypt_test(HandlerCase):
|
||||
handler = hash.scrypt
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# excepted from test vectors from scrypt whitepaper
|
||||
# (http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b),
|
||||
# and encoded using passlib's custom format
|
||||
#
|
||||
|
||||
# salt=b""
|
||||
("", "$scrypt$ln=4,r=1,p=1$$d9ZXYjhleyA7GcpCwYoEl/FrSETjB0ro39/6P+3iFEI"),
|
||||
|
||||
# salt=b"NaCl"
|
||||
("password", "$scrypt$ln=10,r=8,p=16$TmFDbA$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWI"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# simple test
|
||||
("test", '$scrypt$ln=8,r=8,p=1$wlhLyXmP8b53bm1NKYVQqg$mTpvG8lzuuDk+DWz8HZIB6Vum6erDuUm0As5yU+VxWA'),
|
||||
|
||||
# different block value
|
||||
("password", '$scrypt$ln=8,r=2,p=1$dO6d0xoDoLT2PofQGoNQag$g/Wf2A0vhHhaJM+addK61QPBthSmYB6uVTtQzh8CM3o'),
|
||||
|
||||
# different rounds
|
||||
(UPASS_TABLE, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
|
||||
|
||||
# alt encoding
|
||||
(PASS_TABLE_UTF8, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
|
||||
|
||||
# diff block & parallel counts as well
|
||||
("nacl", '$scrypt$ln=1,r=4,p=2$yhnD+J+Tci4lZCwFgHCuVQ$fAsEWmxSHuC0cHKMwKVFPzrQukgvK09Sj+NueTSxKds')
|
||||
]
|
||||
|
||||
if TEST_MODE("full"):
|
||||
# add some hashes with larger rounds value.
|
||||
known_correct_hashes.extend([
|
||||
#
|
||||
# from scrypt whitepaper
|
||||
#
|
||||
|
||||
# salt=b"SodiumChloride"
|
||||
("pleaseletmein", "$scrypt$ln=14,r=8,p=1$U29kaXVtQ2hsb3JpZGU"
|
||||
"$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+qbVD9lRdofI"),
|
||||
|
||||
#
|
||||
# openwall format (https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt)
|
||||
#
|
||||
("pleaseletmein",
|
||||
"$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D"),
|
||||
|
||||
])
|
||||
|
||||
known_malformed_hashes = [
|
||||
# missing 'p' value
|
||||
'$scrypt$ln=10,r=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# rounds too low
|
||||
'$scrypt$ln=0,r=1,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# invalid block size
|
||||
'$scrypt$ln=10,r=A,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# r*p too large
|
||||
'$scrypt$ln=10,r=134217728,p=8$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(_scrypt_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# builtin is still just way too slow.
|
||||
if self.backend == "builtin":
|
||||
kwds.setdefault("rounds", 6)
|
||||
super(_scrypt_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(4, 10, 6, 1)
|
||||
|
||||
# create test cases for specific backends
|
||||
scrypt_stdlib_test = _scrypt_test.create_backend_case("stdlib")
|
||||
scrypt_scrypt_test = _scrypt_test.create_backend_case("scrypt")
|
||||
scrypt_builtin_test = _scrypt_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
97
backend/venv/Lib/site-packages/passlib/tests/test_hosts.py
Normal file
97
backend/venv/Lib/site-packages/passlib/tests/test_hosts.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""test passlib.hosts"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hosts, hash as hashmod
|
||||
from passlib.utils import unix_crypt_schemes
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# test predefined app contexts
|
||||
#=============================================================================
|
||||
class HostsTest(TestCase):
|
||||
"""perform general tests to make sure contexts work"""
|
||||
# NOTE: these tests are not really comprehensive,
|
||||
# since they would do little but duplicate
|
||||
# the presets in apps.py
|
||||
#
|
||||
# they mainly try to ensure no typos
|
||||
# or dynamic behavior foul-ups.
|
||||
|
||||
def check_unix_disabled(self, ctx):
|
||||
for hash in [
|
||||
"",
|
||||
"!",
|
||||
"*",
|
||||
"!$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0",
|
||||
]:
|
||||
self.assertEqual(ctx.identify(hash), 'unix_disabled')
|
||||
self.assertFalse(ctx.verify('test', hash))
|
||||
|
||||
def test_linux_context(self):
|
||||
ctx = hosts.linux_context
|
||||
for hash in [
|
||||
('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'),
|
||||
('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17'),
|
||||
'$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0',
|
||||
'kAJJz.Rwp0A/I',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
self.check_unix_disabled(ctx)
|
||||
|
||||
def test_bsd_contexts(self):
|
||||
for ctx in [
|
||||
hosts.freebsd_context,
|
||||
hosts.openbsd_context,
|
||||
hosts.netbsd_context,
|
||||
]:
|
||||
for hash in [
|
||||
'$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0',
|
||||
'kAJJz.Rwp0A/I',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
h1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
if hashmod.bcrypt.has_backend():
|
||||
self.assertTrue(ctx.verify("test", h1))
|
||||
else:
|
||||
self.assertEqual(ctx.identify(h1), "bcrypt")
|
||||
self.check_unix_disabled(ctx)
|
||||
|
||||
def test_host_context(self):
|
||||
ctx = getattr(hosts, "host_context", None)
|
||||
if not ctx:
|
||||
return self.skipTest("host_context not available on this platform")
|
||||
|
||||
# validate schemes is non-empty,
|
||||
# and contains unix_disabled + at least one real scheme
|
||||
schemes = list(ctx.schemes())
|
||||
self.assertTrue(schemes, "appears to be unix system, but no known schemes supported by crypt")
|
||||
self.assertTrue('unix_disabled' in schemes)
|
||||
schemes.remove("unix_disabled")
|
||||
self.assertTrue(schemes, "should have schemes beside fallback scheme")
|
||||
self.assertTrue(set(unix_crypt_schemes).issuperset(schemes))
|
||||
|
||||
# check for hash support
|
||||
self.check_unix_disabled(ctx)
|
||||
for scheme, hash in [
|
||||
("sha512_crypt", ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751')),
|
||||
("sha256_crypt", ('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17')),
|
||||
("md5_crypt", '$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0'),
|
||||
("des_crypt", 'kAJJz.Rwp0A/I'),
|
||||
]:
|
||||
if scheme in schemes:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
205
backend/venv/Lib/site-packages/passlib/tests/test_pwd.py
Normal file
205
backend/venv/Lib/site-packages/passlib/tests/test_pwd.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""passlib.tests -- tests for passlib.pwd"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import itertools
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.tests.utils import TestCase
|
||||
# local
|
||||
__all__ = [
|
||||
"UtilsTest",
|
||||
"GenerateTest",
|
||||
"StrengthTest",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class UtilsTest(TestCase):
|
||||
"""test internal utilities"""
|
||||
descriptionPrefix = "passlib.pwd"
|
||||
|
||||
def test_self_info_rate(self):
|
||||
"""_self_info_rate()"""
|
||||
from passlib.pwd import _self_info_rate
|
||||
|
||||
self.assertEqual(_self_info_rate(""), 0)
|
||||
|
||||
self.assertEqual(_self_info_rate("a" * 8), 0)
|
||||
|
||||
self.assertEqual(_self_info_rate("ab"), 1)
|
||||
self.assertEqual(_self_info_rate("ab" * 8), 1)
|
||||
|
||||
self.assertEqual(_self_info_rate("abcd"), 2)
|
||||
self.assertEqual(_self_info_rate("abcd" * 8), 2)
|
||||
self.assertAlmostEqual(_self_info_rate("abcdaaaa"), 1.5488, places=4)
|
||||
|
||||
# def test_total_self_info(self):
|
||||
# """_total_self_info()"""
|
||||
# from passlib.pwd import _total_self_info
|
||||
#
|
||||
# self.assertEqual(_total_self_info(""), 0)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("a" * 8), 0)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("ab"), 2)
|
||||
# self.assertEqual(_total_self_info("ab" * 8), 16)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("abcd"), 8)
|
||||
# self.assertEqual(_total_self_info("abcd" * 8), 64)
|
||||
# self.assertAlmostEqual(_total_self_info("abcdaaaa"), 12.3904, places=4)
|
||||
|
||||
#=============================================================================
|
||||
# word generation
|
||||
#=============================================================================
|
||||
|
||||
# import subject
|
||||
from passlib.pwd import genword, default_charsets
|
||||
ascii_62 = default_charsets['ascii_62']
|
||||
hex = default_charsets['hex']
|
||||
|
||||
class WordGeneratorTest(TestCase):
|
||||
"""test generation routines"""
|
||||
descriptionPrefix = "passlib.pwd.genword()"
|
||||
|
||||
def setUp(self):
|
||||
super(WordGeneratorTest, self).setUp()
|
||||
|
||||
# patch some RNG references so they're reproducible.
|
||||
from passlib.pwd import SequenceGenerator
|
||||
self.patchAttr(SequenceGenerator, "rng",
|
||||
self.getRandom("pwd generator"))
|
||||
|
||||
def assertResultContents(self, results, count, chars, unique=True):
|
||||
"""check result list matches expected count & charset"""
|
||||
self.assertEqual(len(results), count)
|
||||
if unique:
|
||||
if unique is True:
|
||||
unique = count
|
||||
self.assertEqual(len(set(results)), unique)
|
||||
self.assertEqual(set("".join(results)), set(chars))
|
||||
|
||||
def test_general(self):
|
||||
"""general behavior"""
|
||||
|
||||
# basic usage
|
||||
result = genword()
|
||||
self.assertEqual(len(result), 9)
|
||||
|
||||
# malformed keyword should have useful error.
|
||||
self.assertRaisesRegex(TypeError, "(?i)unexpected keyword.*badkwd", genword, badkwd=True)
|
||||
|
||||
def test_returns(self):
|
||||
"""'returns' keyword"""
|
||||
# returns=int option
|
||||
results = genword(returns=5000)
|
||||
self.assertResultContents(results, 5000, ascii_62)
|
||||
|
||||
# returns=iter option
|
||||
gen = genword(returns=iter)
|
||||
results = [next(gen) for _ in range(5000)]
|
||||
self.assertResultContents(results, 5000, ascii_62)
|
||||
|
||||
# invalid returns option
|
||||
self.assertRaises(TypeError, genword, returns='invalid-type')
|
||||
|
||||
def test_charset(self):
|
||||
"""'charset' & 'chars' options"""
|
||||
# charset option
|
||||
results = genword(charset="hex", returns=5000)
|
||||
self.assertResultContents(results, 5000, hex)
|
||||
|
||||
# chars option
|
||||
# there are 3**3=27 possible combinations
|
||||
results = genword(length=3, chars="abc", returns=5000)
|
||||
self.assertResultContents(results, 5000, "abc", unique=27)
|
||||
|
||||
# chars + charset
|
||||
self.assertRaises(TypeError, genword, chars='abc', charset='hex')
|
||||
|
||||
# TODO: test rng option
|
||||
|
||||
#=============================================================================
|
||||
# phrase generation
|
||||
#=============================================================================
|
||||
|
||||
# import subject
|
||||
from passlib.pwd import genphrase
|
||||
simple_words = ["alpha", "beta", "gamma"]
|
||||
|
||||
class PhraseGeneratorTest(TestCase):
|
||||
"""test generation routines"""
|
||||
descriptionPrefix = "passlib.pwd.genphrase()"
|
||||
|
||||
def assertResultContents(self, results, count, words, unique=True, sep=" "):
|
||||
"""check result list matches expected count & charset"""
|
||||
self.assertEqual(len(results), count)
|
||||
if unique:
|
||||
if unique is True:
|
||||
unique = count
|
||||
self.assertEqual(len(set(results)), unique)
|
||||
out = set(itertools.chain.from_iterable(elem.split(sep) for elem in results))
|
||||
self.assertEqual(out, set(words))
|
||||
|
||||
def test_general(self):
|
||||
"""general behavior"""
|
||||
|
||||
# basic usage
|
||||
result = genphrase()
|
||||
self.assertEqual(len(result.split(" ")), 4) # 48 / log(7776, 2) ~= 3.7 -> 4
|
||||
|
||||
# malformed keyword should have useful error.
|
||||
self.assertRaisesRegex(TypeError, "(?i)unexpected keyword.*badkwd", genphrase, badkwd=True)
|
||||
|
||||
def test_entropy(self):
|
||||
"""'length' & 'entropy' keywords"""
|
||||
|
||||
# custom entropy
|
||||
result = genphrase(entropy=70)
|
||||
self.assertEqual(len(result.split(" ")), 6) # 70 / log(7776, 2) ~= 5.4 -> 6
|
||||
|
||||
# custom length
|
||||
result = genphrase(length=3)
|
||||
self.assertEqual(len(result.split(" ")), 3)
|
||||
|
||||
# custom length < entropy
|
||||
result = genphrase(length=3, entropy=48)
|
||||
self.assertEqual(len(result.split(" ")), 4)
|
||||
|
||||
# custom length > entropy
|
||||
result = genphrase(length=4, entropy=12)
|
||||
self.assertEqual(len(result.split(" ")), 4)
|
||||
|
||||
def test_returns(self):
|
||||
"""'returns' keyword"""
|
||||
# returns=int option
|
||||
results = genphrase(returns=1000, words=simple_words)
|
||||
self.assertResultContents(results, 1000, simple_words)
|
||||
|
||||
# returns=iter option
|
||||
gen = genphrase(returns=iter, words=simple_words)
|
||||
results = [next(gen) for _ in range(1000)]
|
||||
self.assertResultContents(results, 1000, simple_words)
|
||||
|
||||
# invalid returns option
|
||||
self.assertRaises(TypeError, genphrase, returns='invalid-type')
|
||||
|
||||
def test_wordset(self):
|
||||
"""'wordset' & 'words' options"""
|
||||
# wordset option
|
||||
results = genphrase(words=simple_words, returns=5000)
|
||||
self.assertResultContents(results, 5000, simple_words)
|
||||
|
||||
# words option
|
||||
results = genphrase(length=3, words=simple_words, returns=5000)
|
||||
self.assertResultContents(results, 5000, simple_words, unique=3**3)
|
||||
|
||||
# words + wordset
|
||||
self.assertRaises(TypeError, genphrase, words=simple_words, wordset='bip39')
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
228
backend/venv/Lib/site-packages/passlib/tests/test_registry.py
Normal file
228
backend/venv/Lib/site-packages/passlib/tests/test_registry.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import warnings
|
||||
import sys
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash, registry, exc
|
||||
from passlib.registry import register_crypt_handler, register_crypt_handler_path, \
|
||||
get_crypt_handler, list_crypt_handlers, _unload_handler_name as unload_handler_name
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
# dummy handlers
|
||||
#
|
||||
# NOTE: these are defined outside of test case
|
||||
# since they're used by test_register_crypt_handler_path(),
|
||||
# which needs them to be available as module globals.
|
||||
#=============================================================================
|
||||
class dummy_0(uh.StaticHandler):
|
||||
name = "dummy_0"
|
||||
|
||||
class alt_dummy_0(uh.StaticHandler):
|
||||
name = "dummy_0"
|
||||
|
||||
dummy_x = 1
|
||||
|
||||
#=============================================================================
|
||||
# test registry
|
||||
#=============================================================================
|
||||
class RegistryTest(TestCase):
|
||||
|
||||
descriptionPrefix = "passlib.registry"
|
||||
|
||||
def setUp(self):
|
||||
super(RegistryTest, self).setUp()
|
||||
|
||||
# backup registry state & restore it after test.
|
||||
locations = dict(registry._locations)
|
||||
handlers = dict(registry._handlers)
|
||||
def restore():
|
||||
registry._locations.clear()
|
||||
registry._locations.update(locations)
|
||||
registry._handlers.clear()
|
||||
registry._handlers.update(handlers)
|
||||
self.addCleanup(restore)
|
||||
|
||||
def test_hash_proxy(self):
|
||||
"""test passlib.hash proxy object"""
|
||||
# check dir works
|
||||
dir(hash)
|
||||
|
||||
# check repr works
|
||||
repr(hash)
|
||||
|
||||
# check non-existent attrs raise error
|
||||
self.assertRaises(AttributeError, getattr, hash, 'fooey')
|
||||
|
||||
# GAE tries to set __loader__,
|
||||
# make sure that doesn't call register_crypt_handler.
|
||||
old = getattr(hash, "__loader__", None)
|
||||
test = object()
|
||||
hash.__loader__ = test
|
||||
self.assertIs(hash.__loader__, test)
|
||||
if old is None:
|
||||
del hash.__loader__
|
||||
self.assertFalse(hasattr(hash, "__loader__"))
|
||||
else:
|
||||
hash.__loader__ = old
|
||||
self.assertIs(hash.__loader__, old)
|
||||
|
||||
# check storing attr calls register_crypt_handler
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
hash.dummy_1 = dummy_1
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
# check storing under wrong name results in error
|
||||
self.assertRaises(ValueError, setattr, hash, "dummy_1x", dummy_1)
|
||||
|
||||
def test_register_crypt_handler_path(self):
|
||||
"""test register_crypt_handler_path()"""
|
||||
# NOTE: this messes w/ internals of registry, shouldn't be used publically.
|
||||
paths = registry._locations
|
||||
|
||||
# check namespace is clear
|
||||
self.assertTrue('dummy_0' not in paths)
|
||||
self.assertFalse(hasattr(hash, 'dummy_0'))
|
||||
|
||||
# check invalid names are rejected
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", ".test_registry")
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", __name__ + ":dummy_0:xxx")
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", __name__ + ":dummy_0.xxx")
|
||||
|
||||
# try lazy load
|
||||
register_crypt_handler_path('dummy_0', __name__)
|
||||
self.assertTrue('dummy_0' in list_crypt_handlers())
|
||||
self.assertTrue('dummy_0' not in list_crypt_handlers(loaded_only=True))
|
||||
self.assertIs(hash.dummy_0, dummy_0)
|
||||
self.assertTrue('dummy_0' in list_crypt_handlers(loaded_only=True))
|
||||
unload_handler_name('dummy_0')
|
||||
|
||||
# try lazy load w/ alt
|
||||
register_crypt_handler_path('dummy_0', __name__ + ':alt_dummy_0')
|
||||
self.assertIs(hash.dummy_0, alt_dummy_0)
|
||||
unload_handler_name('dummy_0')
|
||||
|
||||
# check lazy load w/ wrong type fails
|
||||
register_crypt_handler_path('dummy_x', __name__)
|
||||
self.assertRaises(TypeError, get_crypt_handler, 'dummy_x')
|
||||
|
||||
# check lazy load w/ wrong name fails
|
||||
register_crypt_handler_path('alt_dummy_0', __name__)
|
||||
self.assertRaises(ValueError, get_crypt_handler, "alt_dummy_0")
|
||||
unload_handler_name("alt_dummy_0")
|
||||
|
||||
# TODO: check lazy load which calls register_crypt_handler (warning should be issued)
|
||||
sys.modules.pop("passlib.tests._test_bad_register", None)
|
||||
register_crypt_handler_path("dummy_bad", "passlib.tests._test_bad_register")
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", "xxxxxxxxxx", DeprecationWarning)
|
||||
h = get_crypt_handler("dummy_bad")
|
||||
from passlib.tests import _test_bad_register as tbr
|
||||
self.assertIs(h, tbr.alt_dummy_bad)
|
||||
|
||||
def test_register_crypt_handler(self):
|
||||
"""test register_crypt_handler()"""
|
||||
|
||||
self.assertRaises(TypeError, register_crypt_handler, {})
|
||||
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name=None)))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="AB_CD")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="ab-cd")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="ab__cd")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="default")))
|
||||
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
class dummy_1b(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
self.assertTrue('dummy_1' not in list_crypt_handlers())
|
||||
|
||||
register_crypt_handler(dummy_1)
|
||||
register_crypt_handler(dummy_1)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
self.assertRaises(KeyError, register_crypt_handler, dummy_1b)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
register_crypt_handler(dummy_1b, force=True)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1b)
|
||||
|
||||
self.assertTrue('dummy_1' in list_crypt_handlers())
|
||||
|
||||
def test_get_crypt_handler(self):
|
||||
"""test get_crypt_handler()"""
|
||||
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
# without available handler
|
||||
self.assertRaises(KeyError, get_crypt_handler, "dummy_1")
|
||||
self.assertIs(get_crypt_handler("dummy_1", None), None)
|
||||
|
||||
# already loaded handler
|
||||
register_crypt_handler(dummy_1)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning)
|
||||
|
||||
# already loaded handler, using incorrect name
|
||||
self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1)
|
||||
|
||||
# lazy load of unloaded handler, using incorrect name
|
||||
register_crypt_handler_path('dummy_0', __name__)
|
||||
self.assertIs(get_crypt_handler("DUMMY-0"), dummy_0)
|
||||
|
||||
# check system & private names aren't returned
|
||||
from passlib import hash
|
||||
hash.__dict__["_fake"] = "dummy"
|
||||
for name in ["_fake", "__package__"]:
|
||||
self.assertRaises(KeyError, get_crypt_handler, name)
|
||||
self.assertIs(get_crypt_handler(name, None), None)
|
||||
|
||||
def test_list_crypt_handlers(self):
|
||||
"""test list_crypt_handlers()"""
|
||||
from passlib.registry import list_crypt_handlers
|
||||
|
||||
# check system & private names aren't returned
|
||||
hash.__dict__["_fake"] = "dummy"
|
||||
for name in list_crypt_handlers():
|
||||
self.assertFalse(name.startswith("_"), "%r: " % name)
|
||||
unload_handler_name("_fake")
|
||||
|
||||
def test_handlers(self):
|
||||
"""verify we have tests for all builtin handlers"""
|
||||
from passlib.registry import list_crypt_handlers
|
||||
from passlib.tests.test_handlers import get_handler_case, conditionally_available_hashes
|
||||
for name in list_crypt_handlers():
|
||||
# skip some wrappers that don't need independant testing
|
||||
if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
|
||||
continue
|
||||
if name in ["roundup_plaintext"]:
|
||||
continue
|
||||
# check the remaining ones all have a handler
|
||||
try:
|
||||
self.assertTrue(get_handler_case(name))
|
||||
except exc.MissingBackendError:
|
||||
if name in conditionally_available_hashes: # expected to fail on some setups
|
||||
continue
|
||||
raise
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
1604
backend/venv/Lib/site-packages/passlib/tests/test_totp.py
Normal file
1604
backend/venv/Lib/site-packages/passlib/tests/test_totp.py
Normal file
File diff suppressed because it is too large
Load Diff
1171
backend/venv/Lib/site-packages/passlib/tests/test_utils.py
Normal file
1171
backend/venv/Lib/site-packages/passlib/tests/test_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,870 @@
|
||||
"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import re
|
||||
import hashlib
|
||||
from logging import getLogger
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib.hash import ldap_md5, sha256_crypt
|
||||
from passlib.exc import MissingBackendError, PasslibHashWarning
|
||||
from passlib.utils.compat import str_to_uascii, \
|
||||
uascii_to_str, unicode
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.tests.utils import HandlerCase, TestCase
|
||||
from passlib.utils.compat import u
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
# utils
|
||||
#=============================================================================
|
||||
def _makelang(alphabet, size):
|
||||
"""generate all strings of given size using alphabet"""
|
||||
def helper(size):
|
||||
if size < 2:
|
||||
for char in alphabet:
|
||||
yield char
|
||||
else:
|
||||
for char in alphabet:
|
||||
for tail in helper(size-1):
|
||||
yield char+tail
|
||||
return set(helper(size))
|
||||
|
||||
#=============================================================================
|
||||
# test GenericHandler & associates mixin classes
|
||||
#=============================================================================
|
||||
class SkeletonTest(TestCase):
|
||||
"""test hash support classes"""
|
||||
|
||||
#===================================================================
|
||||
# StaticHandler
|
||||
#===================================================================
|
||||
def test_00_static_handler(self):
|
||||
"""test StaticHandler class"""
|
||||
|
||||
class d1(uh.StaticHandler):
|
||||
name = "d1"
|
||||
context_kwds = ("flag",)
|
||||
_hash_prefix = u("_")
|
||||
checksum_chars = u("ab")
|
||||
checksum_size = 1
|
||||
|
||||
def __init__(self, flag=False, **kwds):
|
||||
super(d1, self).__init__(**kwds)
|
||||
self.flag = flag
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
return u('b') if self.flag else u('a')
|
||||
|
||||
# check default identify method
|
||||
self.assertTrue(d1.identify(u('_a')))
|
||||
self.assertTrue(d1.identify(b'_a'))
|
||||
self.assertTrue(d1.identify(u('_b')))
|
||||
|
||||
self.assertFalse(d1.identify(u('_c')))
|
||||
self.assertFalse(d1.identify(b'_c'))
|
||||
self.assertFalse(d1.identify(u('a')))
|
||||
self.assertFalse(d1.identify(u('b')))
|
||||
self.assertFalse(d1.identify(u('c')))
|
||||
self.assertRaises(TypeError, d1.identify, None)
|
||||
self.assertRaises(TypeError, d1.identify, 1)
|
||||
|
||||
# check default genconfig method
|
||||
self.assertEqual(d1.genconfig(), d1.hash(""))
|
||||
|
||||
# check default verify method
|
||||
self.assertTrue(d1.verify('s', b'_a'))
|
||||
self.assertTrue(d1.verify('s',u('_a')))
|
||||
self.assertFalse(d1.verify('s', b'_b'))
|
||||
self.assertFalse(d1.verify('s',u('_b')))
|
||||
self.assertTrue(d1.verify('s', b'_b', flag=True))
|
||||
self.assertRaises(ValueError, d1.verify, 's', b'_c')
|
||||
self.assertRaises(ValueError, d1.verify, 's', u('_c'))
|
||||
|
||||
# check default hash method
|
||||
self.assertEqual(d1.hash('s'), '_a')
|
||||
self.assertEqual(d1.hash('s', flag=True), '_b')
|
||||
|
||||
def test_01_calc_checksum_hack(self):
|
||||
"""test StaticHandler legacy attr"""
|
||||
# release 1.5 StaticHandler required genhash(),
|
||||
# not _calc_checksum, be implemented. we have backward compat wrapper,
|
||||
# this tests that it works.
|
||||
|
||||
class d1(uh.StaticHandler):
|
||||
name = "d1"
|
||||
|
||||
@classmethod
|
||||
def identify(cls, hash):
|
||||
if not hash or len(hash) != 40:
|
||||
return False
|
||||
try:
|
||||
int(hash, 16)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def genhash(cls, secret, hash):
|
||||
if secret is None:
|
||||
raise TypeError("no secret provided")
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
# NOTE: have to support hash=None since this is test of legacy 1.5 api
|
||||
if hash is not None and not cls.identify(hash):
|
||||
raise ValueError("invalid hash")
|
||||
return hashlib.sha1(b"xyz" + secret).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def verify(cls, secret, hash):
|
||||
if hash is None:
|
||||
raise ValueError("no hash specified")
|
||||
return cls.genhash(secret, hash) == hash.lower()
|
||||
|
||||
# hash should issue api warnings, but everything else should be fine.
|
||||
with self.assertWarningList("d1.*should be updated.*_calc_checksum"):
|
||||
hash = d1.hash("test")
|
||||
self.assertEqual(hash, '7c622762588a0e5cc786ad0a143156f9fd38eea3')
|
||||
|
||||
self.assertTrue(d1.verify("test", hash))
|
||||
self.assertFalse(d1.verify("xtest", hash))
|
||||
|
||||
# not defining genhash either, however, should cause NotImplementedError
|
||||
del d1.genhash
|
||||
self.assertRaises(NotImplementedError, d1.hash, 'test')
|
||||
|
||||
#===================================================================
|
||||
# GenericHandler & mixins
|
||||
#===================================================================
|
||||
def test_10_identify(self):
|
||||
"""test GenericHandler.identify()"""
|
||||
class d1(uh.GenericHandler):
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
if isinstance(hash, bytes):
|
||||
hash = hash.decode("ascii")
|
||||
if hash == u('a'):
|
||||
return cls(checksum=hash)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# check fallback
|
||||
self.assertRaises(TypeError, d1.identify, None)
|
||||
self.assertRaises(TypeError, d1.identify, 1)
|
||||
self.assertFalse(d1.identify(''))
|
||||
self.assertTrue(d1.identify('a'))
|
||||
self.assertFalse(d1.identify('b'))
|
||||
|
||||
# check regexp
|
||||
d1._hash_regex = re.compile(u('@.'))
|
||||
self.assertRaises(TypeError, d1.identify, None)
|
||||
self.assertRaises(TypeError, d1.identify, 1)
|
||||
self.assertTrue(d1.identify('@a'))
|
||||
self.assertFalse(d1.identify('a'))
|
||||
del d1._hash_regex
|
||||
|
||||
# check ident-based
|
||||
d1.ident = u('!')
|
||||
self.assertRaises(TypeError, d1.identify, None)
|
||||
self.assertRaises(TypeError, d1.identify, 1)
|
||||
self.assertTrue(d1.identify('!a'))
|
||||
self.assertFalse(d1.identify('a'))
|
||||
del d1.ident
|
||||
|
||||
def test_11_norm_checksum(self):
|
||||
"""test GenericHandler checksum handling"""
|
||||
# setup helpers
|
||||
class d1(uh.GenericHandler):
|
||||
name = 'd1'
|
||||
checksum_size = 4
|
||||
checksum_chars = u('xz')
|
||||
|
||||
def norm_checksum(checksum=None, **k):
|
||||
return d1(checksum=checksum, **k).checksum
|
||||
|
||||
# too small
|
||||
self.assertRaises(ValueError, norm_checksum, u('xxx'))
|
||||
|
||||
# right size
|
||||
self.assertEqual(norm_checksum(u('xxxx')), u('xxxx'))
|
||||
self.assertEqual(norm_checksum(u('xzxz')), u('xzxz'))
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, norm_checksum, u('xxxxx'))
|
||||
|
||||
# wrong chars
|
||||
self.assertRaises(ValueError, norm_checksum, u('xxyx'))
|
||||
|
||||
# wrong type
|
||||
self.assertRaises(TypeError, norm_checksum, b'xxyx')
|
||||
|
||||
# relaxed
|
||||
# NOTE: this could be turned back on if we test _norm_checksum() directly...
|
||||
#with self.assertWarningList("checksum should be unicode"):
|
||||
# self.assertEqual(norm_checksum(b'xxzx', relaxed=True), u('xxzx'))
|
||||
#self.assertRaises(TypeError, norm_checksum, 1, relaxed=True)
|
||||
|
||||
# test _stub_checksum behavior
|
||||
self.assertEqual(d1()._stub_checksum, u('xxxx'))
|
||||
|
||||
def test_12_norm_checksum_raw(self):
|
||||
"""test GenericHandler + HasRawChecksum mixin"""
|
||||
class d1(uh.HasRawChecksum, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
checksum_size = 4
|
||||
|
||||
def norm_checksum(*a, **k):
|
||||
return d1(*a, **k).checksum
|
||||
|
||||
# test bytes
|
||||
self.assertEqual(norm_checksum(b'1234'), b'1234')
|
||||
|
||||
# test unicode
|
||||
self.assertRaises(TypeError, norm_checksum, u('xxyx'))
|
||||
|
||||
# NOTE: this could be turned back on if we test _norm_checksum() directly...
|
||||
# self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True)
|
||||
|
||||
# test _stub_checksum behavior
|
||||
self.assertEqual(d1()._stub_checksum, b'\x00'*4)
|
||||
|
||||
def test_20_norm_salt(self):
|
||||
"""test GenericHandler + HasSalt mixin"""
|
||||
# setup helpers
|
||||
class d1(uh.HasSalt, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
setting_kwds = ('salt',)
|
||||
min_salt_size = 2
|
||||
max_salt_size = 4
|
||||
default_salt_size = 3
|
||||
salt_chars = 'ab'
|
||||
|
||||
def norm_salt(**k):
|
||||
return d1(**k).salt
|
||||
|
||||
def gen_salt(sz, **k):
|
||||
return d1.using(salt_size=sz, **k)(use_defaults=True).salt
|
||||
|
||||
salts2 = _makelang('ab', 2)
|
||||
salts3 = _makelang('ab', 3)
|
||||
salts4 = _makelang('ab', 4)
|
||||
|
||||
# check salt=None
|
||||
self.assertRaises(TypeError, norm_salt)
|
||||
self.assertRaises(TypeError, norm_salt, salt=None)
|
||||
self.assertIn(norm_salt(use_defaults=True), salts3)
|
||||
|
||||
# check explicit salts
|
||||
with warnings.catch_warnings(record=True) as wlog:
|
||||
|
||||
# check too-small salts
|
||||
self.assertRaises(ValueError, norm_salt, salt='')
|
||||
self.assertRaises(ValueError, norm_salt, salt='a')
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check correct salts
|
||||
self.assertEqual(norm_salt(salt='ab'), 'ab')
|
||||
self.assertEqual(norm_salt(salt='aba'), 'aba')
|
||||
self.assertEqual(norm_salt(salt='abba'), 'abba')
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check too-large salts
|
||||
self.assertRaises(ValueError, norm_salt, salt='aaaabb')
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check generated salts
|
||||
with warnings.catch_warnings(record=True) as wlog:
|
||||
|
||||
# check too-small salt size
|
||||
self.assertRaises(ValueError, gen_salt, 0)
|
||||
self.assertRaises(ValueError, gen_salt, 1)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check correct salt size
|
||||
self.assertIn(gen_salt(2), salts2)
|
||||
self.assertIn(gen_salt(3), salts3)
|
||||
self.assertIn(gen_salt(4), salts4)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check too-large salt size
|
||||
self.assertRaises(ValueError, gen_salt, 5)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
self.assertIn(gen_salt(5, relaxed=True), salts4)
|
||||
self.consumeWarningList(wlog, ["salt_size.*above max_salt_size"])
|
||||
|
||||
# test with max_salt_size=None
|
||||
del d1.max_salt_size
|
||||
with self.assertWarningList([]):
|
||||
self.assertEqual(len(gen_salt(None)), 3)
|
||||
self.assertEqual(len(gen_salt(5)), 5)
|
||||
|
||||
# TODO: test HasRawSalt mixin
|
||||
|
||||
def test_30_init_rounds(self):
|
||||
"""test GenericHandler + HasRounds mixin"""
|
||||
# setup helpers
|
||||
class d1(uh.HasRounds, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
setting_kwds = ('rounds',)
|
||||
min_rounds = 1
|
||||
max_rounds = 3
|
||||
default_rounds = 2
|
||||
|
||||
# NOTE: really is testing _init_rounds(), could dup to test _norm_rounds() via .replace
|
||||
def norm_rounds(**k):
|
||||
return d1(**k).rounds
|
||||
|
||||
# check rounds=None
|
||||
self.assertRaises(TypeError, norm_rounds)
|
||||
self.assertRaises(TypeError, norm_rounds, rounds=None)
|
||||
self.assertEqual(norm_rounds(use_defaults=True), 2)
|
||||
|
||||
# check rounds=non int
|
||||
self.assertRaises(TypeError, norm_rounds, rounds=1.5)
|
||||
|
||||
# check explicit rounds
|
||||
with warnings.catch_warnings(record=True) as wlog:
|
||||
# too small
|
||||
self.assertRaises(ValueError, norm_rounds, rounds=0)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# just right
|
||||
self.assertEqual(norm_rounds(rounds=1), 1)
|
||||
self.assertEqual(norm_rounds(rounds=2), 2)
|
||||
self.assertEqual(norm_rounds(rounds=3), 3)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, norm_rounds, rounds=4)
|
||||
self.consumeWarningList(wlog)
|
||||
|
||||
# check no default rounds
|
||||
d1.default_rounds = None
|
||||
self.assertRaises(TypeError, norm_rounds, use_defaults=True)
|
||||
|
||||
def test_40_backends(self):
|
||||
"""test GenericHandler + HasManyBackends mixin"""
|
||||
class d1(uh.HasManyBackends, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
setting_kwds = ()
|
||||
|
||||
backends = ("a", "b")
|
||||
|
||||
_enable_a = False
|
||||
_enable_b = False
|
||||
|
||||
@classmethod
|
||||
def _load_backend_a(cls):
|
||||
if cls._enable_a:
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_a)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _load_backend_b(cls):
|
||||
if cls._enable_b:
|
||||
cls._set_calc_checksum_backend(cls._calc_checksum_b)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _calc_checksum_a(self, secret):
|
||||
return 'a'
|
||||
|
||||
def _calc_checksum_b(self, secret):
|
||||
return 'b'
|
||||
|
||||
# test no backends
|
||||
self.assertRaises(MissingBackendError, d1.get_backend)
|
||||
self.assertRaises(MissingBackendError, d1.set_backend)
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'any')
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'default')
|
||||
self.assertFalse(d1.has_backend())
|
||||
|
||||
# enable 'b' backend
|
||||
d1._enable_b = True
|
||||
|
||||
# test lazy load
|
||||
obj = d1()
|
||||
self.assertEqual(obj._calc_checksum('s'), 'b')
|
||||
|
||||
# test repeat load
|
||||
d1.set_backend('b')
|
||||
d1.set_backend('any')
|
||||
self.assertEqual(obj._calc_checksum('s'), 'b')
|
||||
|
||||
# test unavailable
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'a')
|
||||
self.assertTrue(d1.has_backend('b'))
|
||||
self.assertFalse(d1.has_backend('a'))
|
||||
|
||||
# enable 'a' backend also
|
||||
d1._enable_a = True
|
||||
|
||||
# test explicit
|
||||
self.assertTrue(d1.has_backend())
|
||||
d1.set_backend('a')
|
||||
self.assertEqual(obj._calc_checksum('s'), 'a')
|
||||
|
||||
# test unknown backend
|
||||
self.assertRaises(ValueError, d1.set_backend, 'c')
|
||||
self.assertRaises(ValueError, d1.has_backend, 'c')
|
||||
|
||||
# test error thrown if _has & _load are mixed
|
||||
d1.set_backend("b") # switch away from 'a' so next call actually checks loader
|
||||
class d2(d1):
|
||||
_has_backend_a = True
|
||||
self.assertRaises(AssertionError, d2.has_backend, "a")
|
||||
|
||||
def test_41_backends(self):
|
||||
"""test GenericHandler + HasManyBackends mixin (deprecated api)"""
|
||||
warnings.filterwarnings("ignore",
|
||||
category=DeprecationWarning,
|
||||
message=r".* support for \._has_backend_.* is deprecated.*",
|
||||
)
|
||||
|
||||
class d1(uh.HasManyBackends, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
setting_kwds = ()
|
||||
|
||||
backends = ("a", "b")
|
||||
|
||||
_has_backend_a = False
|
||||
_has_backend_b = False
|
||||
|
||||
def _calc_checksum_a(self, secret):
|
||||
return 'a'
|
||||
|
||||
def _calc_checksum_b(self, secret):
|
||||
return 'b'
|
||||
|
||||
# test no backends
|
||||
self.assertRaises(MissingBackendError, d1.get_backend)
|
||||
self.assertRaises(MissingBackendError, d1.set_backend)
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'any')
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'default')
|
||||
self.assertFalse(d1.has_backend())
|
||||
|
||||
# enable 'b' backend
|
||||
d1._has_backend_b = True
|
||||
|
||||
# test lazy load
|
||||
obj = d1()
|
||||
self.assertEqual(obj._calc_checksum('s'), 'b')
|
||||
|
||||
# test repeat load
|
||||
d1.set_backend('b')
|
||||
d1.set_backend('any')
|
||||
self.assertEqual(obj._calc_checksum('s'), 'b')
|
||||
|
||||
# test unavailable
|
||||
self.assertRaises(MissingBackendError, d1.set_backend, 'a')
|
||||
self.assertTrue(d1.has_backend('b'))
|
||||
self.assertFalse(d1.has_backend('a'))
|
||||
|
||||
# enable 'a' backend also
|
||||
d1._has_backend_a = True
|
||||
|
||||
# test explicit
|
||||
self.assertTrue(d1.has_backend())
|
||||
d1.set_backend('a')
|
||||
self.assertEqual(obj._calc_checksum('s'), 'a')
|
||||
|
||||
# test unknown backend
|
||||
self.assertRaises(ValueError, d1.set_backend, 'c')
|
||||
self.assertRaises(ValueError, d1.has_backend, 'c')
|
||||
|
||||
def test_50_norm_ident(self):
|
||||
"""test GenericHandler + HasManyIdents"""
|
||||
# setup helpers
|
||||
class d1(uh.HasManyIdents, uh.GenericHandler):
|
||||
name = 'd1'
|
||||
setting_kwds = ('ident',)
|
||||
default_ident = u("!A")
|
||||
ident_values = (u("!A"), u("!B"))
|
||||
ident_aliases = { u("A"): u("!A")}
|
||||
|
||||
def norm_ident(**k):
|
||||
return d1(**k).ident
|
||||
|
||||
# check ident=None
|
||||
self.assertRaises(TypeError, norm_ident)
|
||||
self.assertRaises(TypeError, norm_ident, ident=None)
|
||||
self.assertEqual(norm_ident(use_defaults=True), u('!A'))
|
||||
|
||||
# check valid idents
|
||||
self.assertEqual(norm_ident(ident=u('!A')), u('!A'))
|
||||
self.assertEqual(norm_ident(ident=u('!B')), u('!B'))
|
||||
self.assertRaises(ValueError, norm_ident, ident=u('!C'))
|
||||
|
||||
# check aliases
|
||||
self.assertEqual(norm_ident(ident=u('A')), u('!A'))
|
||||
|
||||
# check invalid idents
|
||||
self.assertRaises(ValueError, norm_ident, ident=u('B'))
|
||||
|
||||
# check identify is honoring ident system
|
||||
self.assertTrue(d1.identify(u("!Axxx")))
|
||||
self.assertTrue(d1.identify(u("!Bxxx")))
|
||||
self.assertFalse(d1.identify(u("!Cxxx")))
|
||||
self.assertFalse(d1.identify(u("A")))
|
||||
self.assertFalse(d1.identify(u("")))
|
||||
self.assertRaises(TypeError, d1.identify, None)
|
||||
self.assertRaises(TypeError, d1.identify, 1)
|
||||
|
||||
# check default_ident missing is detected.
|
||||
d1.default_ident = None
|
||||
self.assertRaises(AssertionError, norm_ident, use_defaults=True)
|
||||
|
||||
#===================================================================
|
||||
# experimental - the following methods are not finished or tested,
|
||||
# but way work correctly for some hashes
|
||||
#===================================================================
|
||||
def test_91_parsehash(self):
|
||||
"""test parsehash()"""
|
||||
# NOTE: this just tests some existing GenericHandler classes
|
||||
from passlib import hash
|
||||
|
||||
#
|
||||
# parsehash()
|
||||
#
|
||||
|
||||
# simple hash w/ salt
|
||||
result = hash.des_crypt.parsehash("OgAwTx2l6NADI")
|
||||
self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')})
|
||||
|
||||
# parse rounds and extra implicit_rounds flag
|
||||
h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9'
|
||||
s = u('LKO/Ute40T3FNF95')
|
||||
c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9')
|
||||
result = hash.sha256_crypt.parsehash(h)
|
||||
self.assertEqual(result, dict(salt=s, rounds=5000,
|
||||
implicit_rounds=True, checksum=c))
|
||||
|
||||
# omit checksum
|
||||
result = hash.sha256_crypt.parsehash(h, checksum=False)
|
||||
self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True))
|
||||
|
||||
# sanitize
|
||||
result = hash.sha256_crypt.parsehash(h, sanitize=True)
|
||||
self.assertEqual(result, dict(rounds=5000, implicit_rounds=True,
|
||||
salt=u('LK**************'),
|
||||
checksum=u('U0pr***************************************')))
|
||||
|
||||
# parse w/o implicit rounds flag
|
||||
result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3')
|
||||
self.assertEqual(result, dict(
|
||||
checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'),
|
||||
salt=u('uy/jIAhCetNCTtb0'),
|
||||
rounds=10428,
|
||||
))
|
||||
|
||||
# parsing of raw checksums & salts
|
||||
h1 = '$pbkdf2$60000$DoEwpvQeA8B4T.k951yLUQ$O26Y3/NJEiLCVaOVPxGXshyjW8k'
|
||||
result = hash.pbkdf2_sha1.parsehash(h1)
|
||||
self.assertEqual(result, dict(
|
||||
checksum=b';n\x98\xdf\xf3I\x12"\xc2U\xa3\x95?\x11\x97\xb2\x1c\xa3[\xc9',
|
||||
rounds=60000,
|
||||
salt=b'\x0e\x810\xa6\xf4\x1e\x03\xc0xO\xe9=\xe7\\\x8bQ',
|
||||
))
|
||||
|
||||
# sanitizing of raw checksums & salts
|
||||
result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True)
|
||||
self.assertEqual(result, dict(
|
||||
checksum=u('O26************************'),
|
||||
rounds=60000,
|
||||
salt=u('Do********************'),
|
||||
))
|
||||
|
||||
def test_92_bitsize(self):
|
||||
"""test bitsize()"""
|
||||
# NOTE: this just tests some existing GenericHandler classes
|
||||
from passlib import hash
|
||||
|
||||
# no rounds
|
||||
self.assertEqual(hash.des_crypt.bitsize(),
|
||||
{'checksum': 66, 'salt': 12})
|
||||
|
||||
# log2 rounds
|
||||
self.assertEqual(hash.bcrypt.bitsize(),
|
||||
{'checksum': 186, 'salt': 132})
|
||||
|
||||
# linear rounds
|
||||
# NOTE: +3 comes from int(math.log(.1,2)),
|
||||
# where 0.1 = 10% = default allowed variation in rounds
|
||||
self.patchAttr(hash.sha256_crypt, "default_rounds", 1 << (14 + 3))
|
||||
self.assertEqual(hash.sha256_crypt.bitsize(),
|
||||
{'checksum': 258, 'rounds': 14, 'salt': 96})
|
||||
|
||||
# raw checksum
|
||||
self.patchAttr(hash.pbkdf2_sha1, "default_rounds", 1 << (13 + 3))
|
||||
self.assertEqual(hash.pbkdf2_sha1.bitsize(),
|
||||
{'checksum': 160, 'rounds': 13, 'salt': 128})
|
||||
|
||||
# TODO: handle fshp correctly, and other glitches noted in code.
|
||||
##self.assertEqual(hash.fshp.bitsize(variant=1),
|
||||
## {'checksum': 256, 'rounds': 13, 'salt': 128})
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# PrefixWrapper
|
||||
#=============================================================================
|
||||
class dummy_handler_in_registry(object):
|
||||
"""context manager that inserts dummy handler in registry"""
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.dummy = type('dummy_' + name, (uh.GenericHandler,), dict(
|
||||
name=name,
|
||||
setting_kwds=(),
|
||||
))
|
||||
|
||||
def __enter__(self):
|
||||
from passlib import registry
|
||||
registry._unload_handler_name(self.name, locations=False)
|
||||
registry.register_crypt_handler(self.dummy)
|
||||
assert registry.get_crypt_handler(self.name) is self.dummy
|
||||
return self.dummy
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
from passlib import registry
|
||||
registry._unload_handler_name(self.name, locations=False)
|
||||
|
||||
class PrefixWrapperTest(TestCase):
|
||||
"""test PrefixWrapper class"""
|
||||
|
||||
def test_00_lazy_loading(self):
|
||||
"""test PrefixWrapper lazy loading of handler"""
|
||||
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}", lazy=True)
|
||||
|
||||
# check base state
|
||||
self.assertEqual(d1._wrapped_name, "ldap_md5")
|
||||
self.assertIs(d1._wrapped_handler, None)
|
||||
|
||||
# check loading works
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
self.assertIs(d1._wrapped_handler, ldap_md5)
|
||||
|
||||
# replace w/ wrong handler, make sure doesn't reload w/ dummy
|
||||
with dummy_handler_in_registry("ldap_md5") as dummy:
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
|
||||
def test_01_active_loading(self):
|
||||
"""test PrefixWrapper active loading of handler"""
|
||||
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
|
||||
|
||||
# check base state
|
||||
self.assertEqual(d1._wrapped_name, "ldap_md5")
|
||||
self.assertIs(d1._wrapped_handler, ldap_md5)
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
|
||||
# replace w/ wrong handler, make sure doesn't reload w/ dummy
|
||||
with dummy_handler_in_registry("ldap_md5") as dummy:
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
|
||||
def test_02_explicit(self):
|
||||
"""test PrefixWrapper with explicitly specified handler"""
|
||||
|
||||
d1 = uh.PrefixWrapper("d1", ldap_md5, "{XXX}", "{MD5}")
|
||||
|
||||
# check base state
|
||||
self.assertEqual(d1._wrapped_name, None)
|
||||
self.assertIs(d1._wrapped_handler, ldap_md5)
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
|
||||
# replace w/ wrong handler, make sure doesn't reload w/ dummy
|
||||
with dummy_handler_in_registry("ldap_md5") as dummy:
|
||||
self.assertIs(d1.wrapped, ldap_md5)
|
||||
|
||||
def test_10_wrapped_attributes(self):
|
||||
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
|
||||
self.assertEqual(d1.name, "d1")
|
||||
self.assertIs(d1.setting_kwds, ldap_md5.setting_kwds)
|
||||
self.assertFalse('max_rounds' in dir(d1))
|
||||
|
||||
d2 = uh.PrefixWrapper("d2", "sha256_crypt", "{XXX}")
|
||||
self.assertIs(d2.setting_kwds, sha256_crypt.setting_kwds)
|
||||
self.assertTrue('max_rounds' in dir(d2))
|
||||
|
||||
def test_11_wrapped_methods(self):
|
||||
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
|
||||
dph = "{XXX}X03MO1qnZdYdgyfeuILPmQ=="
|
||||
lph = "{MD5}X03MO1qnZdYdgyfeuILPmQ=="
|
||||
|
||||
# genconfig
|
||||
self.assertEqual(d1.genconfig(), '{XXX}1B2M2Y8AsgTpgAmY7PhCfg==')
|
||||
|
||||
# genhash
|
||||
self.assertRaises(TypeError, d1.genhash, "password", None)
|
||||
self.assertEqual(d1.genhash("password", dph), dph)
|
||||
self.assertRaises(ValueError, d1.genhash, "password", lph)
|
||||
|
||||
# hash
|
||||
self.assertEqual(d1.hash("password"), dph)
|
||||
|
||||
# identify
|
||||
self.assertTrue(d1.identify(dph))
|
||||
self.assertFalse(d1.identify(lph))
|
||||
|
||||
# verify
|
||||
self.assertRaises(ValueError, d1.verify, "password", lph)
|
||||
self.assertTrue(d1.verify("password", dph))
|
||||
|
||||
def test_12_ident(self):
|
||||
# test ident is proxied
|
||||
h = uh.PrefixWrapper("h2", "ldap_md5", "{XXX}")
|
||||
self.assertEqual(h.ident, u("{XXX}{MD5}"))
|
||||
self.assertIs(h.ident_values, None)
|
||||
|
||||
# test lack of ident means no proxy
|
||||
h = uh.PrefixWrapper("h2", "des_crypt", "{XXX}")
|
||||
self.assertIs(h.ident, None)
|
||||
self.assertIs(h.ident_values, None)
|
||||
|
||||
# test orig_prefix disabled ident proxy
|
||||
h = uh.PrefixWrapper("h1", "ldap_md5", "{XXX}", "{MD5}")
|
||||
self.assertIs(h.ident, None)
|
||||
self.assertIs(h.ident_values, None)
|
||||
|
||||
# test custom ident overrides default
|
||||
h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{X")
|
||||
self.assertEqual(h.ident, u("{X"))
|
||||
self.assertIs(h.ident_values, None)
|
||||
|
||||
# test custom ident must match
|
||||
h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{XXX}A")
|
||||
self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5",
|
||||
"{XXX}", ident="{XY")
|
||||
self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5",
|
||||
"{XXX}", ident="{XXXX")
|
||||
|
||||
# test ident_values is proxied
|
||||
h = uh.PrefixWrapper("h4", "phpass", "{XXX}")
|
||||
self.assertIs(h.ident, None)
|
||||
self.assertEqual(h.ident_values, (u("{XXX}$P$"), u("{XXX}$H$")))
|
||||
|
||||
# test ident=True means use prefix even if hash has no ident.
|
||||
h = uh.PrefixWrapper("h5", "des_crypt", "{XXX}", ident=True)
|
||||
self.assertEqual(h.ident, u("{XXX}"))
|
||||
self.assertIs(h.ident_values, None)
|
||||
|
||||
# ... but requires prefix
|
||||
self.assertRaises(ValueError, uh.PrefixWrapper, "h6", "des_crypt", ident=True)
|
||||
|
||||
# orig_prefix + HasManyIdent - warning
|
||||
with self.assertWarningList("orig_prefix.*may not work correctly"):
|
||||
h = uh.PrefixWrapper("h7", "phpass", orig_prefix="$", prefix="?")
|
||||
self.assertEqual(h.ident_values, None) # TODO: should output (u("?P$"), u("?H$")))
|
||||
self.assertEqual(h.ident, None)
|
||||
|
||||
def test_13_repr(self):
|
||||
"""test repr()"""
|
||||
h = uh.PrefixWrapper("h2", "md5_crypt", "{XXX}", orig_prefix="$1$")
|
||||
self.assertRegex(repr(h),
|
||||
r"""(?x)^PrefixWrapper\(
|
||||
['"]h2['"],\s+
|
||||
['"]md5_crypt['"],\s+
|
||||
prefix=u?["']{XXX}['"],\s+
|
||||
orig_prefix=u?["']\$1\$['"]
|
||||
\)$""")
|
||||
|
||||
def test_14_bad_hash(self):
|
||||
"""test orig_prefix sanity check"""
|
||||
# shoudl throw InvalidHashError if wrapped hash doesn't begin
|
||||
# with orig_prefix.
|
||||
h = uh.PrefixWrapper("h2", "md5_crypt", orig_prefix="$6$")
|
||||
self.assertRaises(ValueError, h.hash, 'test')
|
||||
|
||||
#=============================================================================
|
||||
# sample algorithms - these serve as known quantities
|
||||
# to test the unittests themselves, as well as other
|
||||
# parts of passlib. they shouldn't be used as actual password schemes.
|
||||
#=============================================================================
|
||||
class UnsaltedHash(uh.StaticHandler):
|
||||
"""test algorithm which lacks a salt"""
|
||||
name = "unsalted_test_hash"
|
||||
checksum_chars = uh.LOWER_HEX_CHARS
|
||||
checksum_size = 40
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
data = b"boblious" + secret
|
||||
return str_to_uascii(hashlib.sha1(data).hexdigest())
|
||||
|
||||
class SaltedHash(uh.HasSalt, uh.GenericHandler):
|
||||
"""test algorithm with a salt"""
|
||||
name = "salted_test_hash"
|
||||
setting_kwds = ("salt",)
|
||||
|
||||
min_salt_size = 2
|
||||
max_salt_size = 4
|
||||
checksum_size = 40
|
||||
salt_chars = checksum_chars = uh.LOWER_HEX_CHARS
|
||||
|
||||
_hash_regex = re.compile(u("^@salt[0-9a-f]{42,44}$"))
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
if not cls.identify(hash):
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
if isinstance(hash, bytes):
|
||||
hash = hash.decode("ascii")
|
||||
return cls(salt=hash[5:-40], checksum=hash[-40:])
|
||||
|
||||
def to_string(self):
|
||||
hash = u("@salt%s%s") % (self.salt, self.checksum)
|
||||
return uascii_to_str(hash)
|
||||
|
||||
def _calc_checksum(self, secret):
|
||||
if isinstance(secret, unicode):
|
||||
secret = secret.encode("utf-8")
|
||||
data = self.salt.encode("ascii") + secret + self.salt.encode("ascii")
|
||||
return str_to_uascii(hashlib.sha1(data).hexdigest())
|
||||
|
||||
#=============================================================================
|
||||
# test sample algorithms - really a self-test of HandlerCase
|
||||
#=============================================================================
|
||||
|
||||
# TODO: provide data samples for algorithms
|
||||
# (positive knowns, negative knowns, invalid identify)
|
||||
|
||||
UPASS_TEMP = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2')
|
||||
|
||||
class UnsaltedHashTest(HandlerCase):
|
||||
handler = UnsaltedHash
|
||||
|
||||
known_correct_hashes = [
|
||||
("password", "61cfd32684c47de231f1f982c214e884133762c0"),
|
||||
(UPASS_TEMP, '96b329d120b97ff81ada770042e44ba87343ad2b'),
|
||||
]
|
||||
|
||||
def test_bad_kwds(self):
|
||||
self.assertRaises(TypeError, UnsaltedHash, salt='x')
|
||||
self.assertRaises(TypeError, UnsaltedHash.genconfig, rounds=1)
|
||||
|
||||
class SaltedHashTest(HandlerCase):
|
||||
handler = SaltedHash
|
||||
|
||||
known_correct_hashes = [
|
||||
("password", '@salt77d71f8fe74f314dac946766c1ac4a2a58365482c0'),
|
||||
(UPASS_TEMP, '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'),
|
||||
]
|
||||
|
||||
def test_bad_kwds(self):
|
||||
stub = SaltedHash(use_defaults=True)._stub_checksum
|
||||
self.assertRaises(TypeError, SaltedHash, checksum=stub, salt=None)
|
||||
self.assertRaises(ValueError, SaltedHash, checksum=stub, salt='xxx')
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
passlib.tests -- tests for passlib.utils.md4
|
||||
|
||||
.. warning::
|
||||
|
||||
This module & it's functions have been deprecated, and superceded
|
||||
by the functions in passlib.crypto. This file is being maintained
|
||||
until the deprecated functions are removed, and is only present prevent
|
||||
historical regressions up to that point. New and more thorough testing
|
||||
is being done by the replacement tests in ``test_utils_crypto_builtin_md4``.
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.tests.test_crypto_builtin_md4 import _Common_MD4_Test
|
||||
# local
|
||||
__all__ = [
|
||||
"Legacy_MD4_Test",
|
||||
]
|
||||
#=============================================================================
|
||||
# test pure-python MD4 implementation
|
||||
#=============================================================================
|
||||
class Legacy_MD4_Test(_Common_MD4_Test):
|
||||
descriptionPrefix = "passlib.utils.md4.md4()"
|
||||
|
||||
def setUp(self):
|
||||
super(Legacy_MD4_Test, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*passlib.utils.md4.*deprecated", DeprecationWarning)
|
||||
|
||||
def get_md4_const(self):
|
||||
from passlib.utils.md4 import md4
|
||||
return md4
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,323 @@
|
||||
"""
|
||||
passlib.tests -- tests for passlib.utils.pbkdf2
|
||||
|
||||
.. warning::
|
||||
|
||||
This module & it's functions have been deprecated, and superceded
|
||||
by the functions in passlib.crypto. This file is being maintained
|
||||
until the deprecated functions are removed, and is only present prevent
|
||||
historical regressions up to that point. New and more thorough testing
|
||||
is being done by the replacement tests in ``test_utils_crypto.py``.
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import hashlib
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.utils.compat import u, JYTHON
|
||||
from passlib.tests.utils import TestCase, hb
|
||||
|
||||
#=============================================================================
|
||||
# test assorted crypto helpers
|
||||
#=============================================================================
|
||||
class UtilsTest(TestCase):
|
||||
"""test various utils functions"""
|
||||
descriptionPrefix = "passlib.utils.pbkdf2"
|
||||
|
||||
ndn_formats = ["hashlib", "iana"]
|
||||
ndn_values = [
|
||||
# (iana name, hashlib name, ... other unnormalized names)
|
||||
("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"),
|
||||
("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"),
|
||||
("sha256", "sha-256", "SHA_256", "sha2-256"),
|
||||
("ripemd160", "ripemd-160", "SCRAM-RIPEMD-160", "RIPEmd160",
|
||||
# NOTE: there was an older "RIPEMD" & "RIPEMD-128", but python treates "RIPEMD"
|
||||
# as alias for "RIPEMD-160"
|
||||
"ripemd", "SCRAM-RIPEMD"),
|
||||
("test128", "test-128", "TEST128"),
|
||||
("test2", "test2", "TEST-2"),
|
||||
("test3_128", "test3-128", "TEST-3-128"),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(UtilsTest, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
|
||||
|
||||
def test_norm_hash_name(self):
|
||||
"""norm_hash_name()"""
|
||||
from itertools import chain
|
||||
from passlib.utils.pbkdf2 import norm_hash_name
|
||||
from passlib.crypto.digest import _known_hash_names
|
||||
|
||||
# test formats
|
||||
for format in self.ndn_formats:
|
||||
norm_hash_name("md4", format)
|
||||
self.assertRaises(ValueError, norm_hash_name, "md4", None)
|
||||
self.assertRaises(ValueError, norm_hash_name, "md4", "fake")
|
||||
|
||||
# test types
|
||||
self.assertEqual(norm_hash_name(u("MD4")), "md4")
|
||||
self.assertEqual(norm_hash_name(b"MD4"), "md4")
|
||||
self.assertRaises(TypeError, norm_hash_name, None)
|
||||
|
||||
# test selected results
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", '.*unknown hash')
|
||||
for row in chain(_known_hash_names, self.ndn_values):
|
||||
for idx, format in enumerate(self.ndn_formats):
|
||||
correct = row[idx]
|
||||
for value in row:
|
||||
result = norm_hash_name(value, format)
|
||||
self.assertEqual(result, correct,
|
||||
"name=%r, format=%r:" % (value,
|
||||
format))
|
||||
|
||||
#=============================================================================
|
||||
# test PBKDF1 support
|
||||
#=============================================================================
|
||||
class Pbkdf1_Test(TestCase):
|
||||
"""test kdf helpers"""
|
||||
descriptionPrefix = "passlib.utils.pbkdf2.pbkdf1()"
|
||||
|
||||
pbkdf1_tests = [
|
||||
# (password, salt, rounds, keylen, hash, result)
|
||||
|
||||
#
|
||||
# from http://www.di-mgt.com.au/cryptoKDFs.html
|
||||
#
|
||||
(b'password', hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
(b'password', b'salt', 1000, 0, 'md5', b''),
|
||||
(b'password', b'salt', 1000, 1, 'md5', hb('84')),
|
||||
(b'password', b'salt', 1000, 8, 'md5', hb('8475c6a8531a5d27')),
|
||||
(b'password', b'salt', 1000, 16, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
|
||||
(b'password', b'salt', 1000, None, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
|
||||
(b'password', b'salt', 1000, None, 'sha1', hb('4a8fd48e426ed081b535be5769892fa396293efb')),
|
||||
]
|
||||
if not JYTHON:
|
||||
pbkdf1_tests.append(
|
||||
(b'password', b'salt', 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453'))
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(Pbkdf1_Test, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
from passlib.utils.pbkdf2 import pbkdf1
|
||||
for secret, salt, rounds, keylen, digest, correct in self.pbkdf1_tests:
|
||||
result = pbkdf1(secret, salt, rounds, keylen, digest)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
from passlib.utils.pbkdf2 import pbkdf1
|
||||
def helper(secret=b'secret', salt=b'salt', rounds=1, keylen=1, hash='md5'):
|
||||
return pbkdf1(secret, salt, rounds, keylen, hash)
|
||||
helper()
|
||||
|
||||
# salt/secret wrong type
|
||||
self.assertRaises(TypeError, helper, secret=1)
|
||||
self.assertRaises(TypeError, helper, salt=1)
|
||||
|
||||
# non-existent hashes
|
||||
self.assertRaises(ValueError, helper, hash='missing')
|
||||
|
||||
# rounds < 1 and wrong type
|
||||
self.assertRaises(ValueError, helper, rounds=0)
|
||||
self.assertRaises(TypeError, helper, rounds='1')
|
||||
|
||||
# keylen < 0, keylen > block_size, and wrong type
|
||||
self.assertRaises(ValueError, helper, keylen=-1)
|
||||
self.assertRaises(ValueError, helper, keylen=17, hash='md5')
|
||||
self.assertRaises(TypeError, helper, keylen='1')
|
||||
|
||||
#=============================================================================
|
||||
# test PBKDF2 support
|
||||
#=============================================================================
|
||||
class Pbkdf2_Test(TestCase):
|
||||
"""test pbkdf2() support"""
|
||||
descriptionPrefix = "passlib.utils.pbkdf2.pbkdf2()"
|
||||
|
||||
pbkdf2_test_vectors = [
|
||||
# (result, secret, salt, rounds, keylen, prf="sha1")
|
||||
|
||||
#
|
||||
# from rfc 3962
|
||||
#
|
||||
|
||||
# test case 1 / 128 bit
|
||||
(
|
||||
hb("cdedb5281bb2f801565a1122b2563515"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 1, 16
|
||||
),
|
||||
|
||||
# test case 2 / 128 bit
|
||||
(
|
||||
hb("01dbee7f4a9e243e988b62c73cda935d"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 2, 16
|
||||
),
|
||||
|
||||
# test case 2 / 256 bit
|
||||
(
|
||||
hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 2, 32
|
||||
),
|
||||
|
||||
# test case 3 / 256 bit
|
||||
(
|
||||
hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"),
|
||||
b"password", b"ATHENA.MIT.EDUraeburn", 1200, 32
|
||||
),
|
||||
|
||||
# test case 4 / 256 bit
|
||||
(
|
||||
hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"),
|
||||
b"password", b'\x12\x34\x56\x78\x78\x56\x34\x12', 5, 32
|
||||
),
|
||||
|
||||
# test case 5 / 256 bit
|
||||
(
|
||||
hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"),
|
||||
b"X"*64, b"pass phrase equals block size", 1200, 32
|
||||
),
|
||||
|
||||
# test case 6 / 256 bit
|
||||
(
|
||||
hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"),
|
||||
b"X"*65, b"pass phrase exceeds block size", 1200, 32
|
||||
),
|
||||
|
||||
#
|
||||
# from rfc 6070
|
||||
#
|
||||
(
|
||||
hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
|
||||
b"password", b"salt", 1, 20,
|
||||
),
|
||||
|
||||
(
|
||||
hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
|
||||
b"password", b"salt", 2, 20,
|
||||
),
|
||||
|
||||
(
|
||||
hb("4b007901b765489abead49d926f721d065a429c1"),
|
||||
b"password", b"salt", 4096, 20,
|
||||
),
|
||||
|
||||
# just runs too long - could enable if ALL option is set
|
||||
##(
|
||||
##
|
||||
## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
|
||||
## "password", "salt", 16777216, 20,
|
||||
##),
|
||||
|
||||
(
|
||||
hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
|
||||
b"passwordPASSWORDpassword",
|
||||
b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
|
||||
4096, 25,
|
||||
),
|
||||
|
||||
(
|
||||
hb("56fa6aa75548099dcc37d7f03425e0c3"),
|
||||
b"pass\00word", b"sa\00lt", 4096, 16,
|
||||
),
|
||||
|
||||
#
|
||||
# from example in http://grub.enbug.org/Authentication
|
||||
#
|
||||
(
|
||||
hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED"
|
||||
"97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC"
|
||||
"6C29E293F0A0"),
|
||||
b"hello",
|
||||
hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71"
|
||||
"784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073"
|
||||
"994D79080136"),
|
||||
10000, 64, "hmac-sha512"
|
||||
),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc222812'),
|
||||
b"secret", b"salt", 10, 16, "hmac-sha1",
|
||||
),
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc2228127872da6d'),
|
||||
b"secret", b"salt", 10, None, "hmac-sha1",
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(Pbkdf2_Test, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
from passlib.utils.pbkdf2 import pbkdf2
|
||||
for row in self.pbkdf2_test_vectors:
|
||||
correct, secret, salt, rounds, keylen = row[:5]
|
||||
prf = row[5] if len(row) == 6 else "hmac-sha1"
|
||||
result = pbkdf2(secret, salt, rounds, keylen, prf)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
from passlib.utils.pbkdf2 import pbkdf2
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"):
|
||||
return pbkdf2(secret, salt, rounds, keylen, prf)
|
||||
helper()
|
||||
|
||||
# invalid rounds
|
||||
self.assertRaises(ValueError, helper, rounds=-1)
|
||||
self.assertRaises(ValueError, helper, rounds=0)
|
||||
self.assertRaises(TypeError, helper, rounds='x')
|
||||
|
||||
# invalid keylen
|
||||
self.assertRaises(ValueError, helper, keylen=-1)
|
||||
self.assertRaises(ValueError, helper, keylen=0)
|
||||
helper(keylen=1)
|
||||
self.assertRaises(OverflowError, helper, keylen=20*(2**32-1)+1)
|
||||
self.assertRaises(TypeError, helper, keylen='x')
|
||||
|
||||
# invalid secret/salt type
|
||||
self.assertRaises(TypeError, helper, salt=5)
|
||||
self.assertRaises(TypeError, helper, secret=5)
|
||||
|
||||
# invalid hash
|
||||
self.assertRaises(ValueError, helper, prf='hmac-foo')
|
||||
self.assertRaises(NotImplementedError, helper, prf='foo')
|
||||
self.assertRaises(TypeError, helper, prf=5)
|
||||
|
||||
def test_default_keylen(self):
|
||||
"""test keylen==None"""
|
||||
from passlib.utils.pbkdf2 import pbkdf2
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"):
|
||||
return pbkdf2(secret, salt, rounds, keylen, prf)
|
||||
self.assertEqual(len(helper(prf='hmac-sha1')), 20)
|
||||
self.assertEqual(len(helper(prf='hmac-sha256')), 32)
|
||||
|
||||
def test_custom_prf(self):
|
||||
"""test custom prf function"""
|
||||
from passlib.utils.pbkdf2 import pbkdf2
|
||||
def prf(key, msg):
|
||||
return hashlib.md5(key+msg+b'fooey').digest()
|
||||
self.assertRaises(NotImplementedError, pbkdf2, b'secret', b'salt', 1000, 20, prf)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
50
backend/venv/Lib/site-packages/passlib/tests/test_win32.py
Normal file
50
backend/venv/Lib/site-packages/passlib/tests/test_win32.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""tests for passlib.win32 -- (c) Assurance Technologies 2003-2009"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
from passlib.utils.compat import u
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class UtilTest(TestCase):
|
||||
"""test util funcs in passlib.win32"""
|
||||
|
||||
##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx
|
||||
## among other places
|
||||
|
||||
def setUp(self):
|
||||
super(UtilTest, self).setUp()
|
||||
warnings.filterwarnings("ignore",
|
||||
"the 'passlib.win32' module is deprecated")
|
||||
|
||||
def test_lmhash(self):
|
||||
from passlib.win32 import raw_lmhash
|
||||
for secret, hash in [
|
||||
("OLDPASSWORD", u("c9b81d939d6fd80cd408e6b105741864")),
|
||||
("NEWPASSWORD", u('09eeab5aa415d6e4d408e6b105741864')),
|
||||
("welcome", u("c23413a8a1e7665faad3b435b51404ee")),
|
||||
]:
|
||||
result = raw_lmhash(secret, hex=True)
|
||||
self.assertEqual(result, hash)
|
||||
|
||||
def test_nthash(self):
|
||||
warnings.filterwarnings("ignore",
|
||||
r"nthash\.raw_nthash\(\) is deprecated")
|
||||
from passlib.win32 import raw_nthash
|
||||
for secret, hash in [
|
||||
("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")),
|
||||
("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")),
|
||||
]:
|
||||
result = raw_nthash(secret, hex=True)
|
||||
self.assertEqual(result, hash)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
83
backend/venv/Lib/site-packages/passlib/tests/tox_support.py
Normal file
83
backend/venv/Lib/site-packages/passlib/tests/tox_support.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""passlib.tests.tox_support - helper script for tox tests"""
|
||||
#=============================================================================
|
||||
# init script env
|
||||
#=============================================================================
|
||||
import os, sys
|
||||
root_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
|
||||
sys.path.insert(0, root_dir)
|
||||
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import print_
|
||||
# local
|
||||
__all__ = [
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# main
|
||||
#=============================================================================
|
||||
TH_PATH = "passlib.tests.test_handlers"
|
||||
|
||||
def do_hash_tests(*args):
|
||||
"""return list of hash algorithm tests that match regexes"""
|
||||
if not args:
|
||||
print(TH_PATH)
|
||||
return
|
||||
suffix = ''
|
||||
args = list(args)
|
||||
while True:
|
||||
if args[0] == "--method":
|
||||
suffix = '.' + args[1]
|
||||
del args[:2]
|
||||
else:
|
||||
break
|
||||
from passlib.tests import test_handlers
|
||||
names = [TH_PATH + ":" + name + suffix for name in dir(test_handlers)
|
||||
if not name.startswith("_") and any(re.match(arg,name) for arg in args)]
|
||||
print_("\n".join(names))
|
||||
return not names
|
||||
|
||||
def do_preset_tests(name):
|
||||
"""return list of preset test names"""
|
||||
if name == "django" or name == "django-hashes":
|
||||
do_hash_tests("django_.*_test", "hex_md5_test")
|
||||
if name == "django":
|
||||
print_("passlib.tests.test_ext_django")
|
||||
else:
|
||||
raise ValueError("unknown name: %r" % name)
|
||||
|
||||
def do_setup_gae(path, runtime):
|
||||
"""write fake GAE ``app.yaml`` to current directory so nosegae will work"""
|
||||
from passlib.tests.utils import set_file
|
||||
set_file(os.path.join(path, "app.yaml"), """\
|
||||
application: fake-app
|
||||
version: 2
|
||||
runtime: %s
|
||||
api_version: 1
|
||||
threadsafe: no
|
||||
|
||||
handlers:
|
||||
- url: /.*
|
||||
script: dummy.py
|
||||
|
||||
libraries:
|
||||
- name: django
|
||||
version: "latest"
|
||||
""" % runtime)
|
||||
|
||||
def main(cmd, *args):
|
||||
return globals()["do_" + cmd](*args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(main(*sys.argv[1:]) or 0)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
3621
backend/venv/Lib/site-packages/passlib/tests/utils.py
Normal file
3621
backend/venv/Lib/site-packages/passlib/tests/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
1908
backend/venv/Lib/site-packages/passlib/totp.py
Normal file
1908
backend/venv/Lib/site-packages/passlib/totp.py
Normal file
File diff suppressed because it is too large
Load Diff
1220
backend/venv/Lib/site-packages/passlib/utils/__init__.py
Normal file
1220
backend/venv/Lib/site-packages/passlib/utils/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
884
backend/venv/Lib/site-packages/passlib/utils/binary.py
Normal file
884
backend/venv/Lib/site-packages/passlib/utils/binary.py
Normal file
@@ -0,0 +1,884 @@
|
||||
"""
|
||||
passlib.utils.binary - binary data encoding/decoding/manipulation
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from base64 import (
|
||||
b64encode,
|
||||
b64decode,
|
||||
b32decode as _b32decode,
|
||||
b32encode as _b32encode,
|
||||
)
|
||||
from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils.compat import (
|
||||
PY3, bascii_to_str,
|
||||
irange, imap, iter_byte_chars, join_byte_values, join_byte_elems,
|
||||
nextgetter, suppress_cause,
|
||||
u, unicode, unicode_or_bytes_types,
|
||||
)
|
||||
from passlib.utils.decor import memoized_property
|
||||
# from passlib.utils import BASE64_CHARS, HASH64_CHARS
|
||||
# local
|
||||
__all__ = [
|
||||
# constants
|
||||
"BASE64_CHARS", "PADDED_BASE64_CHARS",
|
||||
"AB64_CHARS",
|
||||
"HASH64_CHARS",
|
||||
"BCRYPT_CHARS",
|
||||
"HEX_CHARS", "LOWER_HEX_CHARS", "UPPER_HEX_CHARS",
|
||||
|
||||
"ALL_BYTE_VALUES",
|
||||
|
||||
# misc
|
||||
"compile_byte_translation",
|
||||
|
||||
# base64
|
||||
'ab64_encode', 'ab64_decode',
|
||||
'b64s_encode', 'b64s_decode',
|
||||
|
||||
# base32
|
||||
"b32encode", "b32decode",
|
||||
|
||||
# custom encodings
|
||||
'Base64Engine',
|
||||
'LazyBase64Engine',
|
||||
'h64',
|
||||
'h64big',
|
||||
'bcrypt64',
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# constant strings
|
||||
#=============================================================================
|
||||
|
||||
#-------------------------------------------------------------
|
||||
# common salt_chars & checksum_chars values
|
||||
#-------------------------------------------------------------
|
||||
|
||||
#: standard base64 charmap
|
||||
BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
|
||||
|
||||
#: alt base64 charmap -- "." instead of "+"
|
||||
AB64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./")
|
||||
|
||||
#: charmap used by HASH64 encoding.
|
||||
HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
#: charmap used by BCrypt
|
||||
BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
||||
|
||||
#: std base64 chars + padding char
|
||||
PADDED_BASE64_CHARS = BASE64_CHARS + u("=")
|
||||
|
||||
#: all hex chars
|
||||
HEX_CHARS = u("0123456789abcdefABCDEF")
|
||||
|
||||
#: upper case hex chars
|
||||
UPPER_HEX_CHARS = u("0123456789ABCDEF")
|
||||
|
||||
#: lower case hex chars
|
||||
LOWER_HEX_CHARS = u("0123456789abcdef")
|
||||
|
||||
#-------------------------------------------------------------
|
||||
# byte strings
|
||||
#-------------------------------------------------------------
|
||||
|
||||
#: special byte string containing all possible byte values
|
||||
#: NOTE: for efficiency, this is treated as singleton by some of the code
|
||||
ALL_BYTE_VALUES = join_byte_values(irange(256))
|
||||
|
||||
#: some string constants we reuse
|
||||
B_EMPTY = b''
|
||||
B_NULL = b'\x00'
|
||||
B_EQUAL = b'='
|
||||
|
||||
#=============================================================================
|
||||
# byte translation
|
||||
#=============================================================================
|
||||
|
||||
#: base list used to compile byte translations
|
||||
_TRANSLATE_SOURCE = list(iter_byte_chars(ALL_BYTE_VALUES))
|
||||
|
||||
def compile_byte_translation(mapping, source=None):
|
||||
"""
|
||||
return a 256-byte string for translating bytes using specified mapping.
|
||||
bytes not specified by mapping will be left alone.
|
||||
|
||||
:param mapping:
|
||||
dict mapping input byte (str or int) -> output byte (str or int).
|
||||
|
||||
:param source:
|
||||
optional existing byte translation string to use as base.
|
||||
(must be 255-length byte string). defaults to identity mapping.
|
||||
|
||||
:returns:
|
||||
255-length byte string for passing to bytes().translate.
|
||||
"""
|
||||
if source is None:
|
||||
target = _TRANSLATE_SOURCE[:]
|
||||
else:
|
||||
assert isinstance(source, bytes) and len(source) == 255
|
||||
target = list(iter_byte_chars(source))
|
||||
for k, v in mapping.items():
|
||||
if isinstance(k, unicode_or_bytes_types):
|
||||
k = ord(k)
|
||||
assert isinstance(k, int) and 0 <= k < 256
|
||||
if isinstance(v, unicode):
|
||||
v = v.encode("ascii")
|
||||
assert isinstance(v, bytes) and len(v) == 1
|
||||
target[k] = v
|
||||
return B_EMPTY.join(target)
|
||||
|
||||
#=============================================================================
|
||||
# unpadding / stripped base64 encoding
|
||||
#=============================================================================
|
||||
def b64s_encode(data):
|
||||
"""
|
||||
encode using shortened base64 format which omits padding & whitespace.
|
||||
uses default ``+/`` altchars.
|
||||
"""
|
||||
return b2a_base64(data).rstrip(_BASE64_STRIP)
|
||||
|
||||
def b64s_decode(data):
|
||||
"""
|
||||
decode from shortened base64 format which omits padding & whitespace.
|
||||
uses default ``+/`` altchars.
|
||||
"""
|
||||
if isinstance(data, unicode):
|
||||
# needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
|
||||
try:
|
||||
data = data.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
|
||||
off = len(data) & 3
|
||||
if off == 0:
|
||||
pass
|
||||
elif off == 2:
|
||||
data += _BASE64_PAD2
|
||||
elif off == 3:
|
||||
data += _BASE64_PAD1
|
||||
else: # off == 1
|
||||
raise ValueError("invalid base64 input")
|
||||
try:
|
||||
return a2b_base64(data)
|
||||
except _BinAsciiError as err:
|
||||
raise suppress_cause(TypeError(err))
|
||||
|
||||
#=============================================================================
|
||||
# adapted-base64 encoding
|
||||
#=============================================================================
|
||||
_BASE64_STRIP = b"=\n"
|
||||
_BASE64_PAD1 = b"="
|
||||
_BASE64_PAD2 = b"=="
|
||||
|
||||
# XXX: Passlib 1.8/1.9 -- deprecate everything that's using ab64_encode(),
|
||||
# have it start outputing b64s_encode() instead? can use a64_decode() to retain backwards compat.
|
||||
|
||||
def ab64_encode(data):
|
||||
"""
|
||||
encode using shortened base64 format which omits padding & whitespace.
|
||||
uses custom ``./`` altchars.
|
||||
|
||||
it is primarily used by Passlib's custom pbkdf2 hashes.
|
||||
"""
|
||||
return b64s_encode(data).replace(b"+", b".")
|
||||
|
||||
def ab64_decode(data):
|
||||
"""
|
||||
decode from shortened base64 format which omits padding & whitespace.
|
||||
uses custom ``./`` altchars, but supports decoding normal ``+/`` altchars as well.
|
||||
|
||||
it is primarily used by Passlib's custom pbkdf2 hashes.
|
||||
"""
|
||||
if isinstance(data, unicode):
|
||||
# needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
|
||||
try:
|
||||
data = data.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
|
||||
return b64s_decode(data.replace(b".", b"+"))
|
||||
|
||||
#=============================================================================
|
||||
# base32 codec
|
||||
#=============================================================================
|
||||
|
||||
def b32encode(source):
|
||||
"""
|
||||
wrapper around :func:`base64.b32encode` which strips padding,
|
||||
and returns a native string.
|
||||
"""
|
||||
# NOTE: using upper case by default here, since 'I & L' are less
|
||||
# visually ambiguous than 'i & l'
|
||||
return bascii_to_str(_b32encode(source).rstrip(B_EQUAL))
|
||||
|
||||
#: byte translation map to replace common mistyped base32 chars.
|
||||
#: XXX: could correct '1' -> 'I', but could be a mistyped lower-case 'l', so leaving it alone.
|
||||
_b32_translate = compile_byte_translation({"8": "B", "0": "O"})
|
||||
|
||||
#: helper to add padding
|
||||
_b32_decode_pad = B_EQUAL * 8
|
||||
|
||||
def b32decode(source):
|
||||
"""
|
||||
wrapper around :func:`base64.b32decode`
|
||||
which handles common mistyped chars.
|
||||
padding optional, ignored if present.
|
||||
"""
|
||||
# encode & correct for typos
|
||||
if isinstance(source, unicode):
|
||||
source = source.encode("ascii")
|
||||
source = source.translate(_b32_translate)
|
||||
|
||||
# pad things so final string is multiple of 8
|
||||
remainder = len(source) & 0x7
|
||||
if remainder:
|
||||
source += _b32_decode_pad[:-remainder]
|
||||
|
||||
# XXX: py27 stdlib's version of this has some inefficiencies,
|
||||
# could look into using optimized version.
|
||||
return _b32decode(source, True)
|
||||
|
||||
#=============================================================================
|
||||
# base64-variant encoding
|
||||
#=============================================================================
|
||||
|
||||
class Base64Engine(object):
|
||||
"""Provides routines for encoding/decoding base64 data using
|
||||
arbitrary character mappings, selectable endianness, etc.
|
||||
|
||||
:arg charmap:
|
||||
A string of 64 unique characters,
|
||||
which will be used to encode successive 6-bit chunks of data.
|
||||
A character's position within the string should correspond
|
||||
to its 6-bit value.
|
||||
|
||||
:param big:
|
||||
Whether the encoding should be big-endian (default False).
|
||||
|
||||
.. note::
|
||||
This class does not currently handle base64's padding characters
|
||||
in any way what so ever.
|
||||
|
||||
Raw Bytes <-> Encoded Bytes
|
||||
===========================
|
||||
The following methods convert between raw bytes,
|
||||
and strings encoded using the engine's specific base64 variant:
|
||||
|
||||
.. automethod:: encode_bytes
|
||||
.. automethod:: decode_bytes
|
||||
.. automethod:: encode_transposed_bytes
|
||||
.. automethod:: decode_transposed_bytes
|
||||
|
||||
..
|
||||
.. automethod:: check_repair_unused
|
||||
.. automethod:: repair_unused
|
||||
|
||||
Integers <-> Encoded Bytes
|
||||
==========================
|
||||
The following methods allow encoding and decoding
|
||||
unsigned integers to and from the engine's specific base64 variant.
|
||||
Endianess is determined by the engine's ``big`` constructor keyword.
|
||||
|
||||
.. automethod:: encode_int6
|
||||
.. automethod:: decode_int6
|
||||
|
||||
.. automethod:: encode_int12
|
||||
.. automethod:: decode_int12
|
||||
|
||||
.. automethod:: encode_int24
|
||||
.. automethod:: decode_int24
|
||||
|
||||
.. automethod:: encode_int64
|
||||
.. automethod:: decode_int64
|
||||
|
||||
Informational Attributes
|
||||
========================
|
||||
.. attribute:: charmap
|
||||
|
||||
unicode string containing list of characters used in encoding;
|
||||
position in string matches 6bit value of character.
|
||||
|
||||
.. attribute:: bytemap
|
||||
|
||||
bytes version of :attr:`charmap`
|
||||
|
||||
.. attribute:: big
|
||||
|
||||
boolean flag indicating this using big-endian encoding.
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
# public config
|
||||
bytemap = None # charmap as bytes
|
||||
big = None # little or big endian
|
||||
|
||||
# filled in by init based on charmap.
|
||||
# (byte elem: single byte under py2, 8bit int under py3)
|
||||
_encode64 = None # maps 6bit value -> byte elem
|
||||
_decode64 = None # maps byte elem -> 6bit value
|
||||
|
||||
# helpers filled in by init based on endianness
|
||||
_encode_bytes = None # throws IndexError if bad value (shouldn't happen)
|
||||
_decode_bytes = None # throws KeyError if bad char.
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, charmap, big=False):
|
||||
# validate charmap, generate encode64/decode64 helper functions.
|
||||
if isinstance(charmap, unicode):
|
||||
charmap = charmap.encode("latin-1")
|
||||
elif not isinstance(charmap, bytes):
|
||||
raise exc.ExpectedStringError(charmap, "charmap")
|
||||
if len(charmap) != 64:
|
||||
raise ValueError("charmap must be 64 characters in length")
|
||||
if len(set(charmap)) != 64:
|
||||
raise ValueError("charmap must not contain duplicate characters")
|
||||
self.bytemap = charmap
|
||||
self._encode64 = charmap.__getitem__
|
||||
lookup = dict((value, idx) for idx, value in enumerate(charmap))
|
||||
self._decode64 = lookup.__getitem__
|
||||
|
||||
# validate big, set appropriate helper functions.
|
||||
self.big = big
|
||||
if big:
|
||||
self._encode_bytes = self._encode_bytes_big
|
||||
self._decode_bytes = self._decode_bytes_big
|
||||
else:
|
||||
self._encode_bytes = self._encode_bytes_little
|
||||
self._decode_bytes = self._decode_bytes_little
|
||||
|
||||
# TODO: support padding character
|
||||
##if padding is not None:
|
||||
## if isinstance(padding, unicode):
|
||||
## padding = padding.encode("latin-1")
|
||||
## elif not isinstance(padding, bytes):
|
||||
## raise TypeError("padding char must be unicode or bytes")
|
||||
## if len(padding) != 1:
|
||||
## raise ValueError("padding must be single character")
|
||||
##self.padding = padding
|
||||
|
||||
@property
|
||||
def charmap(self):
|
||||
"""charmap as unicode"""
|
||||
return self.bytemap.decode("latin-1")
|
||||
|
||||
#===================================================================
|
||||
# encoding byte strings
|
||||
#===================================================================
|
||||
def encode_bytes(self, source):
|
||||
"""encode bytes to base64 string.
|
||||
|
||||
:arg source: byte string to encode.
|
||||
:returns: byte string containing encoded data.
|
||||
"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
chunks, tail = divmod(len(source), 3)
|
||||
if PY3:
|
||||
next_value = nextgetter(iter(source))
|
||||
else:
|
||||
next_value = nextgetter(ord(elem) for elem in source)
|
||||
gen = self._encode_bytes(next_value, chunks, tail)
|
||||
out = join_byte_elems(imap(self._encode64, gen))
|
||||
##if tail:
|
||||
## padding = self.padding
|
||||
## if padding:
|
||||
## out += padding * (3-tail)
|
||||
return out
|
||||
|
||||
def _encode_bytes_little(self, next_value, chunks, tail):
|
||||
"""helper used by encode_bytes() to handle little-endian encoding"""
|
||||
#
|
||||
# output bit layout:
|
||||
#
|
||||
# first byte: v1 543210
|
||||
#
|
||||
# second byte: v1 ....76
|
||||
# +v2 3210..
|
||||
#
|
||||
# third byte: v2 ..7654
|
||||
# +v3 10....
|
||||
#
|
||||
# fourth byte: v3 765432
|
||||
#
|
||||
idx = 0
|
||||
while idx < chunks:
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
v3 = next_value()
|
||||
yield v1 & 0x3f
|
||||
yield ((v2 & 0x0f)<<2)|(v1>>6)
|
||||
yield ((v3 & 0x03)<<4)|(v2>>4)
|
||||
yield v3>>2
|
||||
idx += 1
|
||||
if tail:
|
||||
v1 = next_value()
|
||||
if tail == 1:
|
||||
# note: 4 msb of last byte are padding
|
||||
yield v1 & 0x3f
|
||||
yield v1>>6
|
||||
else:
|
||||
assert tail == 2
|
||||
# note: 2 msb of last byte are padding
|
||||
v2 = next_value()
|
||||
yield v1 & 0x3f
|
||||
yield ((v2 & 0x0f)<<2)|(v1>>6)
|
||||
yield v2>>4
|
||||
|
||||
def _encode_bytes_big(self, next_value, chunks, tail):
|
||||
"""helper used by encode_bytes() to handle big-endian encoding"""
|
||||
#
|
||||
# output bit layout:
|
||||
#
|
||||
# first byte: v1 765432
|
||||
#
|
||||
# second byte: v1 10....
|
||||
# +v2 ..7654
|
||||
#
|
||||
# third byte: v2 3210..
|
||||
# +v3 ....76
|
||||
#
|
||||
# fourth byte: v3 543210
|
||||
#
|
||||
idx = 0
|
||||
while idx < chunks:
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
v3 = next_value()
|
||||
yield v1>>2
|
||||
yield ((v1&0x03)<<4)|(v2>>4)
|
||||
yield ((v2&0x0f)<<2)|(v3>>6)
|
||||
yield v3 & 0x3f
|
||||
idx += 1
|
||||
if tail:
|
||||
v1 = next_value()
|
||||
if tail == 1:
|
||||
# note: 4 lsb of last byte are padding
|
||||
yield v1>>2
|
||||
yield (v1&0x03)<<4
|
||||
else:
|
||||
assert tail == 2
|
||||
# note: 2 lsb of last byte are padding
|
||||
v2 = next_value()
|
||||
yield v1>>2
|
||||
yield ((v1&0x03)<<4)|(v2>>4)
|
||||
yield ((v2&0x0f)<<2)
|
||||
|
||||
#===================================================================
|
||||
# decoding byte strings
|
||||
#===================================================================
|
||||
|
||||
def decode_bytes(self, source):
|
||||
"""decode bytes from base64 string.
|
||||
|
||||
:arg source: byte string to decode.
|
||||
:returns: byte string containing decoded data.
|
||||
"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
##padding = self.padding
|
||||
##if padding:
|
||||
## # TODO: add padding size check?
|
||||
## source = source.rstrip(padding)
|
||||
chunks, tail = divmod(len(source), 4)
|
||||
if tail == 1:
|
||||
# only 6 bits left, can't encode a whole byte!
|
||||
raise ValueError("input string length cannot be == 1 mod 4")
|
||||
next_value = nextgetter(imap(self._decode64, source))
|
||||
try:
|
||||
return join_byte_values(self._decode_bytes(next_value, chunks, tail))
|
||||
except KeyError as err:
|
||||
raise ValueError("invalid character: %r" % (err.args[0],))
|
||||
|
||||
def _decode_bytes_little(self, next_value, chunks, tail):
|
||||
"""helper used by decode_bytes() to handle little-endian encoding"""
|
||||
#
|
||||
# input bit layout:
|
||||
#
|
||||
# first byte: v1 ..543210
|
||||
# +v2 10......
|
||||
#
|
||||
# second byte: v2 ....5432
|
||||
# +v3 3210....
|
||||
#
|
||||
# third byte: v3 ......54
|
||||
# +v4 543210..
|
||||
#
|
||||
idx = 0
|
||||
while idx < chunks:
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
v3 = next_value()
|
||||
v4 = next_value()
|
||||
yield v1 | ((v2 & 0x3) << 6)
|
||||
yield (v2>>2) | ((v3 & 0xF) << 4)
|
||||
yield (v3>>4) | (v4<<2)
|
||||
idx += 1
|
||||
if tail:
|
||||
# tail is 2 or 3
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
yield v1 | ((v2 & 0x3) << 6)
|
||||
# NOTE: if tail == 2, 4 msb of v2 are ignored (should be 0)
|
||||
if tail == 3:
|
||||
# NOTE: 2 msb of v3 are ignored (should be 0)
|
||||
v3 = next_value()
|
||||
yield (v2>>2) | ((v3 & 0xF) << 4)
|
||||
|
||||
def _decode_bytes_big(self, next_value, chunks, tail):
|
||||
"""helper used by decode_bytes() to handle big-endian encoding"""
|
||||
#
|
||||
# input bit layout:
|
||||
#
|
||||
# first byte: v1 543210..
|
||||
# +v2 ......54
|
||||
#
|
||||
# second byte: v2 3210....
|
||||
# +v3 ....5432
|
||||
#
|
||||
# third byte: v3 10......
|
||||
# +v4 ..543210
|
||||
#
|
||||
idx = 0
|
||||
while idx < chunks:
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
v3 = next_value()
|
||||
v4 = next_value()
|
||||
yield (v1<<2) | (v2>>4)
|
||||
yield ((v2&0xF)<<4) | (v3>>2)
|
||||
yield ((v3&0x3)<<6) | v4
|
||||
idx += 1
|
||||
if tail:
|
||||
# tail is 2 or 3
|
||||
v1 = next_value()
|
||||
v2 = next_value()
|
||||
yield (v1<<2) | (v2>>4)
|
||||
# NOTE: if tail == 2, 4 lsb of v2 are ignored (should be 0)
|
||||
if tail == 3:
|
||||
# NOTE: 2 lsb of v3 are ignored (should be 0)
|
||||
v3 = next_value()
|
||||
yield ((v2&0xF)<<4) | (v3>>2)
|
||||
|
||||
#===================================================================
|
||||
# encode/decode helpers
|
||||
#===================================================================
|
||||
|
||||
# padmap2/3 - dict mapping last char of string ->
|
||||
# equivalent char with no padding bits set.
|
||||
|
||||
def __make_padset(self, bits):
|
||||
"""helper to generate set of valid last chars & bytes"""
|
||||
pset = set(c for i,c in enumerate(self.bytemap) if not i & bits)
|
||||
pset.update(c for i,c in enumerate(self.charmap) if not i & bits)
|
||||
return frozenset(pset)
|
||||
|
||||
@memoized_property
|
||||
def _padinfo2(self):
|
||||
"""mask to clear padding bits, and valid last bytes (for strings 2 % 4)"""
|
||||
# 4 bits of last char unused (lsb for big, msb for little)
|
||||
bits = 15 if self.big else (15<<2)
|
||||
return ~bits, self.__make_padset(bits)
|
||||
|
||||
@memoized_property
|
||||
def _padinfo3(self):
|
||||
"""mask to clear padding bits, and valid last bytes (for strings 3 % 4)"""
|
||||
# 2 bits of last char unused (lsb for big, msb for little)
|
||||
bits = 3 if self.big else (3<<4)
|
||||
return ~bits, self.__make_padset(bits)
|
||||
|
||||
def check_repair_unused(self, source):
|
||||
"""helper to detect & clear invalid unused bits in last character.
|
||||
|
||||
:arg source:
|
||||
encoded data (as ascii bytes or unicode).
|
||||
|
||||
:returns:
|
||||
`(True, result)` if the string was repaired,
|
||||
`(False, source)` if the string was ok as-is.
|
||||
"""
|
||||
# figure out how many padding bits there are in last char.
|
||||
tail = len(source) & 3
|
||||
if tail == 2:
|
||||
mask, padset = self._padinfo2
|
||||
elif tail == 3:
|
||||
mask, padset = self._padinfo3
|
||||
elif not tail:
|
||||
return False, source
|
||||
else:
|
||||
raise ValueError("source length must != 1 mod 4")
|
||||
|
||||
# check if last char is ok (padset contains bytes & unicode versions)
|
||||
last = source[-1]
|
||||
if last in padset:
|
||||
return False, source
|
||||
|
||||
# we have dirty bits - repair the string by decoding last char,
|
||||
# clearing the padding bits via <mask>, and encoding new char.
|
||||
if isinstance(source, unicode):
|
||||
cm = self.charmap
|
||||
last = cm[cm.index(last) & mask]
|
||||
assert last in padset, "failed to generate valid padding char"
|
||||
else:
|
||||
# NOTE: this assumes ascii-compat encoding, and that
|
||||
# all chars used by encoding are 7-bit ascii.
|
||||
last = self._encode64(self._decode64(last) & mask)
|
||||
assert last in padset, "failed to generate valid padding char"
|
||||
if PY3:
|
||||
last = bytes([last])
|
||||
return True, source[:-1] + last
|
||||
|
||||
def repair_unused(self, source):
|
||||
return self.check_repair_unused(source)[1]
|
||||
|
||||
##def transcode(self, source, other):
|
||||
## return ''.join(
|
||||
## other.charmap[self.charmap.index(char)]
|
||||
## for char in source
|
||||
## )
|
||||
|
||||
##def random_encoded_bytes(self, size, random=None, unicode=False):
|
||||
## "return random encoded string of given size"
|
||||
## data = getrandstr(random or rng,
|
||||
## self.charmap if unicode else self.bytemap, size)
|
||||
## return self.repair_unused(data)
|
||||
|
||||
#===================================================================
|
||||
# transposed encoding/decoding
|
||||
#===================================================================
|
||||
def encode_transposed_bytes(self, source, offsets):
|
||||
"""encode byte string, first transposing source using offset list"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
tmp = join_byte_elems(source[off] for off in offsets)
|
||||
return self.encode_bytes(tmp)
|
||||
|
||||
def decode_transposed_bytes(self, source, offsets):
|
||||
"""decode byte string, then reverse transposition described by offset list"""
|
||||
# NOTE: if transposition does not use all bytes of source,
|
||||
# the original can't be recovered... and join_byte_elems() will throw
|
||||
# an error because 1+ values in <buf> will be None.
|
||||
tmp = self.decode_bytes(source)
|
||||
buf = [None] * len(offsets)
|
||||
for off, char in zip(offsets, tmp):
|
||||
buf[off] = char
|
||||
return join_byte_elems(buf)
|
||||
|
||||
#===================================================================
|
||||
# integer decoding helpers - mainly used by des_crypt family
|
||||
#===================================================================
|
||||
def _decode_int(self, source, bits):
|
||||
"""decode base64 string -> integer
|
||||
|
||||
:arg source: base64 string to decode.
|
||||
:arg bits: number of bits in resulting integer.
|
||||
|
||||
:raises ValueError:
|
||||
* if the string contains invalid base64 characters.
|
||||
* if the string is not long enough - it must be at least
|
||||
``int(ceil(bits/6))`` in length.
|
||||
|
||||
:returns:
|
||||
a integer in the range ``0 <= n < 2**bits``
|
||||
"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
big = self.big
|
||||
pad = -bits % 6
|
||||
chars = (bits+pad)/6
|
||||
if len(source) != chars:
|
||||
raise ValueError("source must be %d chars" % (chars,))
|
||||
decode = self._decode64
|
||||
out = 0
|
||||
try:
|
||||
for c in source if big else reversed(source):
|
||||
out = (out<<6) + decode(c)
|
||||
except KeyError:
|
||||
raise ValueError("invalid character in string: %r" % (c,))
|
||||
if pad:
|
||||
# strip padding bits
|
||||
if big:
|
||||
out >>= pad
|
||||
else:
|
||||
out &= (1<<bits)-1
|
||||
return out
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# optimized versions for common integer sizes
|
||||
#---------------------------------------------------------------
|
||||
|
||||
def decode_int6(self, source):
|
||||
"""decode single character -> 6 bit integer"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
if len(source) != 1:
|
||||
raise ValueError("source must be exactly 1 byte")
|
||||
if PY3:
|
||||
# convert to 8bit int before doing lookup
|
||||
source = source[0]
|
||||
try:
|
||||
return self._decode64(source)
|
||||
except KeyError:
|
||||
raise ValueError("invalid character")
|
||||
|
||||
def decode_int12(self, source):
|
||||
"""decodes 2 char string -> 12-bit integer"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
if len(source) != 2:
|
||||
raise ValueError("source must be exactly 2 bytes")
|
||||
decode = self._decode64
|
||||
try:
|
||||
if self.big:
|
||||
return decode(source[1]) + (decode(source[0])<<6)
|
||||
else:
|
||||
return decode(source[0]) + (decode(source[1])<<6)
|
||||
except KeyError:
|
||||
raise ValueError("invalid character")
|
||||
|
||||
def decode_int24(self, source):
|
||||
"""decodes 4 char string -> 24-bit integer"""
|
||||
if not isinstance(source, bytes):
|
||||
raise TypeError("source must be bytes, not %s" % (type(source),))
|
||||
if len(source) != 4:
|
||||
raise ValueError("source must be exactly 4 bytes")
|
||||
decode = self._decode64
|
||||
try:
|
||||
if self.big:
|
||||
return decode(source[3]) + (decode(source[2])<<6)+ \
|
||||
(decode(source[1])<<12) + (decode(source[0])<<18)
|
||||
else:
|
||||
return decode(source[0]) + (decode(source[1])<<6)+ \
|
||||
(decode(source[2])<<12) + (decode(source[3])<<18)
|
||||
except KeyError:
|
||||
raise ValueError("invalid character")
|
||||
|
||||
def decode_int30(self, source):
|
||||
"""decode 5 char string -> 30 bit integer"""
|
||||
return self._decode_int(source, 30)
|
||||
|
||||
def decode_int64(self, source):
|
||||
"""decode 11 char base64 string -> 64-bit integer
|
||||
|
||||
this format is used primarily by des-crypt & variants to encode
|
||||
the DES output value used as a checksum.
|
||||
"""
|
||||
return self._decode_int(source, 64)
|
||||
|
||||
#===================================================================
|
||||
# integer encoding helpers - mainly used by des_crypt family
|
||||
#===================================================================
|
||||
def _encode_int(self, value, bits):
|
||||
"""encode integer into base64 format
|
||||
|
||||
:arg value: non-negative integer to encode
|
||||
:arg bits: number of bits to encode
|
||||
|
||||
:returns:
|
||||
a string of length ``int(ceil(bits/6.0))``.
|
||||
"""
|
||||
assert value >= 0, "caller did not sanitize input"
|
||||
pad = -bits % 6
|
||||
bits += pad
|
||||
if self.big:
|
||||
itr = irange(bits-6, -6, -6)
|
||||
# shift to add lsb padding.
|
||||
value <<= pad
|
||||
else:
|
||||
itr = irange(0, bits, 6)
|
||||
# padding is msb, so no change needed.
|
||||
return join_byte_elems(imap(self._encode64,
|
||||
((value>>off) & 0x3f for off in itr)))
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# optimized versions for common integer sizes
|
||||
#---------------------------------------------------------------
|
||||
|
||||
def encode_int6(self, value):
|
||||
"""encodes 6-bit integer -> single hash64 character"""
|
||||
if value < 0 or value > 63:
|
||||
raise ValueError("value out of range")
|
||||
if PY3:
|
||||
return self.bytemap[value:value+1]
|
||||
else:
|
||||
return self._encode64(value)
|
||||
|
||||
def encode_int12(self, value):
|
||||
"""encodes 12-bit integer -> 2 char string"""
|
||||
if value < 0 or value > 0xFFF:
|
||||
raise ValueError("value out of range")
|
||||
raw = [value & 0x3f, (value>>6) & 0x3f]
|
||||
if self.big:
|
||||
raw = reversed(raw)
|
||||
return join_byte_elems(imap(self._encode64, raw))
|
||||
|
||||
def encode_int24(self, value):
|
||||
"""encodes 24-bit integer -> 4 char string"""
|
||||
if value < 0 or value > 0xFFFFFF:
|
||||
raise ValueError("value out of range")
|
||||
raw = [value & 0x3f, (value>>6) & 0x3f,
|
||||
(value>>12) & 0x3f, (value>>18) & 0x3f]
|
||||
if self.big:
|
||||
raw = reversed(raw)
|
||||
return join_byte_elems(imap(self._encode64, raw))
|
||||
|
||||
def encode_int30(self, value):
|
||||
"""decode 5 char string -> 30 bit integer"""
|
||||
if value < 0 or value > 0x3fffffff:
|
||||
raise ValueError("value out of range")
|
||||
return self._encode_int(value, 30)
|
||||
|
||||
def encode_int64(self, value):
|
||||
"""encode 64-bit integer -> 11 char hash64 string
|
||||
|
||||
this format is used primarily by des-crypt & variants to encode
|
||||
the DES output value used as a checksum.
|
||||
"""
|
||||
if value < 0 or value > 0xffffffffffffffff:
|
||||
raise ValueError("value out of range")
|
||||
return self._encode_int(value, 64)
|
||||
|
||||
#===================================================================
|
||||
# eof
|
||||
#===================================================================
|
||||
|
||||
class LazyBase64Engine(Base64Engine):
|
||||
"""Base64Engine which delays initialization until it's accessed"""
|
||||
_lazy_opts = None
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self._lazy_opts = (args, kwds)
|
||||
|
||||
def _lazy_init(self):
|
||||
args, kwds = self._lazy_opts
|
||||
super(LazyBase64Engine, self).__init__(*args, **kwds)
|
||||
del self._lazy_opts
|
||||
self.__class__ = Base64Engine
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
if not attr.startswith("_"):
|
||||
self._lazy_init()
|
||||
return object.__getattribute__(self, attr)
|
||||
|
||||
#-------------------------------------------------------------
|
||||
# common variants
|
||||
#-------------------------------------------------------------
|
||||
|
||||
h64 = LazyBase64Engine(HASH64_CHARS)
|
||||
h64big = LazyBase64Engine(HASH64_CHARS, big=True)
|
||||
bcrypt64 = LazyBase64Engine(BCRYPT_CHARS, big=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
474
backend/venv/Lib/site-packages/passlib/utils/compat/__init__.py
Normal file
474
backend/venv/Lib/site-packages/passlib/utils/compat/__init__.py
Normal file
@@ -0,0 +1,474 @@
|
||||
"""passlib.utils.compat - python 2/3 compatibility helpers"""
|
||||
#=============================================================================
|
||||
# figure out what we're running
|
||||
#=============================================================================
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# python version
|
||||
#------------------------------------------------------------------------
|
||||
import sys
|
||||
PY2 = sys.version_info < (3,0)
|
||||
PY3 = sys.version_info >= (3,0)
|
||||
|
||||
# make sure it's not an unsupported version, even if we somehow got this far
|
||||
if sys.version_info < (2,6) or (3,0) <= sys.version_info < (3,2):
|
||||
raise RuntimeError("Passlib requires Python 2.6, 2.7, or >= 3.2 (as of passlib 1.7)")
|
||||
|
||||
PY26 = sys.version_info < (2,7)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# python implementation
|
||||
#------------------------------------------------------------------------
|
||||
JYTHON = sys.platform.startswith('java')
|
||||
|
||||
PYPY = hasattr(sys, "pypy_version_info")
|
||||
|
||||
if PYPY and sys.pypy_version_info < (2,0):
|
||||
raise RuntimeError("passlib requires pypy >= 2.0 (as of passlib 1.7)")
|
||||
|
||||
# e.g. '2.7.7\n[Pyston 0.5.1]'
|
||||
# NOTE: deprecated support 2019-11
|
||||
PYSTON = "Pyston" in sys.version
|
||||
|
||||
#=============================================================================
|
||||
# common imports
|
||||
#=============================================================================
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
if PY3:
|
||||
import builtins
|
||||
else:
|
||||
import __builtin__ as builtins
|
||||
|
||||
def add_doc(obj, doc):
|
||||
"""add docstring to an object"""
|
||||
obj.__doc__ = doc
|
||||
|
||||
#=============================================================================
|
||||
# the default exported vars
|
||||
#=============================================================================
|
||||
__all__ = [
|
||||
# python versions
|
||||
'PY2', 'PY3', 'PY26',
|
||||
|
||||
# io
|
||||
'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser',
|
||||
'print_',
|
||||
|
||||
# type detection
|
||||
## 'is_mapping',
|
||||
'int_types',
|
||||
'num_types',
|
||||
'unicode_or_bytes_types',
|
||||
'native_string_types',
|
||||
|
||||
# unicode/bytes types & helpers
|
||||
'u',
|
||||
'unicode',
|
||||
'uascii_to_str', 'bascii_to_str',
|
||||
'str_to_uascii', 'str_to_bascii',
|
||||
'join_unicode', 'join_bytes',
|
||||
'join_byte_values', 'join_byte_elems',
|
||||
'byte_elem_value',
|
||||
'iter_byte_values',
|
||||
|
||||
# iteration helpers
|
||||
'irange', #'lrange',
|
||||
'imap', 'lmap',
|
||||
'iteritems', 'itervalues',
|
||||
'next',
|
||||
|
||||
# collections
|
||||
'OrderedDict',
|
||||
|
||||
# context helpers
|
||||
'nullcontext',
|
||||
|
||||
# introspection
|
||||
'get_method_function', 'add_doc',
|
||||
]
|
||||
|
||||
# begin accumulating mapping of lazy-loaded attrs,
|
||||
# 'merged' into module at bottom
|
||||
_lazy_attrs = dict()
|
||||
|
||||
#=============================================================================
|
||||
# unicode & bytes types
|
||||
#=============================================================================
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
# TODO: once we drop python 3.2 support, can use u'' again!
|
||||
def u(s):
|
||||
assert isinstance(s, str)
|
||||
return s
|
||||
|
||||
unicode_or_bytes_types = (str, bytes)
|
||||
native_string_types = (unicode,)
|
||||
|
||||
else:
|
||||
unicode = builtins.unicode
|
||||
|
||||
def u(s):
|
||||
assert isinstance(s, str)
|
||||
return s.decode("unicode_escape")
|
||||
|
||||
unicode_or_bytes_types = (basestring,)
|
||||
native_string_types = (basestring,)
|
||||
|
||||
# shorter preferred aliases
|
||||
unicode_or_bytes = unicode_or_bytes_types
|
||||
unicode_or_str = native_string_types
|
||||
|
||||
# unicode -- unicode type, regardless of python version
|
||||
# bytes -- bytes type, regardless of python version
|
||||
# unicode_or_bytes_types -- types that text can occur in, whether encoded or not
|
||||
# native_string_types -- types that native python strings (dict keys etc) can occur in.
|
||||
|
||||
#=============================================================================
|
||||
# unicode & bytes helpers
|
||||
#=============================================================================
|
||||
# function to join list of unicode strings
|
||||
join_unicode = u('').join
|
||||
|
||||
# function to join list of byte strings
|
||||
join_bytes = b''.join
|
||||
|
||||
if PY3:
|
||||
def uascii_to_str(s):
|
||||
assert isinstance(s, unicode)
|
||||
return s
|
||||
|
||||
def bascii_to_str(s):
|
||||
assert isinstance(s, bytes)
|
||||
return s.decode("ascii")
|
||||
|
||||
def str_to_uascii(s):
|
||||
assert isinstance(s, str)
|
||||
return s
|
||||
|
||||
def str_to_bascii(s):
|
||||
assert isinstance(s, str)
|
||||
return s.encode("ascii")
|
||||
|
||||
join_byte_values = join_byte_elems = bytes
|
||||
|
||||
def byte_elem_value(elem):
|
||||
assert isinstance(elem, int)
|
||||
return elem
|
||||
|
||||
def iter_byte_values(s):
|
||||
assert isinstance(s, bytes)
|
||||
return s
|
||||
|
||||
def iter_byte_chars(s):
|
||||
assert isinstance(s, bytes)
|
||||
# FIXME: there has to be a better way to do this
|
||||
return (bytes([c]) for c in s)
|
||||
|
||||
else:
|
||||
def uascii_to_str(s):
|
||||
assert isinstance(s, unicode)
|
||||
return s.encode("ascii")
|
||||
|
||||
def bascii_to_str(s):
|
||||
assert isinstance(s, bytes)
|
||||
return s
|
||||
|
||||
def str_to_uascii(s):
|
||||
assert isinstance(s, str)
|
||||
return s.decode("ascii")
|
||||
|
||||
def str_to_bascii(s):
|
||||
assert isinstance(s, str)
|
||||
return s
|
||||
|
||||
def join_byte_values(values):
|
||||
return join_bytes(chr(v) for v in values)
|
||||
|
||||
join_byte_elems = join_bytes
|
||||
|
||||
byte_elem_value = ord
|
||||
|
||||
def iter_byte_values(s):
|
||||
assert isinstance(s, bytes)
|
||||
return (ord(c) for c in s)
|
||||
|
||||
def iter_byte_chars(s):
|
||||
assert isinstance(s, bytes)
|
||||
return s
|
||||
|
||||
add_doc(uascii_to_str, "helper to convert ascii unicode -> native str")
|
||||
add_doc(bascii_to_str, "helper to convert ascii bytes -> native str")
|
||||
add_doc(str_to_uascii, "helper to convert ascii native str -> unicode")
|
||||
add_doc(str_to_bascii, "helper to convert ascii native str -> bytes")
|
||||
|
||||
# join_byte_values -- function to convert list of ordinal integers to byte string.
|
||||
|
||||
# join_byte_elems -- function to convert list of byte elements to byte string;
|
||||
# i.e. what's returned by ``b('a')[0]``...
|
||||
# this is b('a') under PY2, but 97 under PY3.
|
||||
|
||||
# byte_elem_value -- function to convert byte element to integer -- a noop under PY3
|
||||
|
||||
add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255")
|
||||
add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings")
|
||||
|
||||
#=============================================================================
|
||||
# numeric
|
||||
#=============================================================================
|
||||
if PY3:
|
||||
int_types = (int,)
|
||||
num_types = (int, float)
|
||||
else:
|
||||
int_types = (int, long)
|
||||
num_types = (int, long, float)
|
||||
|
||||
#=============================================================================
|
||||
# iteration helpers
|
||||
#
|
||||
# irange - range iterable / view (xrange under py2, range under py3)
|
||||
# lrange - range list (range under py2, list(range()) under py3)
|
||||
#
|
||||
# imap - map to iterator
|
||||
# lmap - map to list
|
||||
#=============================================================================
|
||||
if PY3:
|
||||
irange = range
|
||||
##def lrange(*a,**k):
|
||||
## return list(range(*a,**k))
|
||||
|
||||
def lmap(*a, **k):
|
||||
return list(map(*a,**k))
|
||||
imap = map
|
||||
|
||||
def iteritems(d):
|
||||
return d.items()
|
||||
def itervalues(d):
|
||||
return d.values()
|
||||
|
||||
def nextgetter(obj):
|
||||
return obj.__next__
|
||||
|
||||
izip = zip
|
||||
|
||||
else:
|
||||
irange = xrange
|
||||
##lrange = range
|
||||
|
||||
lmap = map
|
||||
from itertools import imap, izip
|
||||
|
||||
def iteritems(d):
|
||||
return d.iteritems()
|
||||
def itervalues(d):
|
||||
return d.itervalues()
|
||||
|
||||
def nextgetter(obj):
|
||||
return obj.next
|
||||
|
||||
add_doc(nextgetter, "return function that yields successive values from iterable")
|
||||
|
||||
#=============================================================================
|
||||
# typing
|
||||
#=============================================================================
|
||||
##def is_mapping(obj):
|
||||
## # non-exhaustive check, enough to distinguish from lists, etc
|
||||
## return hasattr(obj, "items")
|
||||
|
||||
#=============================================================================
|
||||
# introspection
|
||||
#=============================================================================
|
||||
if PY3:
|
||||
method_function_attr = "__func__"
|
||||
else:
|
||||
method_function_attr = "im_func"
|
||||
|
||||
def get_method_function(func):
|
||||
"""given (potential) method, return underlying function"""
|
||||
return getattr(func, method_function_attr, func)
|
||||
|
||||
def get_unbound_method_function(func):
|
||||
"""given unbound method, return underlying function"""
|
||||
return func if PY3 else func.__func__
|
||||
|
||||
def error_from(exc, # *,
|
||||
cause=None):
|
||||
"""
|
||||
backward compat hack to suppress exception cause in python3.3+
|
||||
|
||||
one python < 3.3 support is dropped, can replace all uses with "raise exc from None"
|
||||
"""
|
||||
exc.__cause__ = cause
|
||||
exc.__suppress_context__ = True
|
||||
return exc
|
||||
|
||||
# legacy alias
|
||||
suppress_cause = error_from
|
||||
|
||||
#=============================================================================
|
||||
# input/output
|
||||
#=============================================================================
|
||||
if PY3:
|
||||
_lazy_attrs = dict(
|
||||
BytesIO="io.BytesIO",
|
||||
UnicodeIO="io.StringIO",
|
||||
NativeStringIO="io.StringIO",
|
||||
SafeConfigParser="configparser.ConfigParser",
|
||||
)
|
||||
|
||||
print_ = getattr(builtins, "print")
|
||||
|
||||
else:
|
||||
_lazy_attrs = dict(
|
||||
BytesIO="cStringIO.StringIO",
|
||||
UnicodeIO="StringIO.StringIO",
|
||||
NativeStringIO="cStringIO.StringIO",
|
||||
SafeConfigParser="ConfigParser.SafeConfigParser",
|
||||
)
|
||||
|
||||
def print_(*args, **kwds):
|
||||
"""The new-style print function."""
|
||||
# extract kwd args
|
||||
fp = kwds.pop("file", sys.stdout)
|
||||
sep = kwds.pop("sep", None)
|
||||
end = kwds.pop("end", None)
|
||||
if kwds:
|
||||
raise TypeError("invalid keyword arguments")
|
||||
|
||||
# short-circuit if no target
|
||||
if fp is None:
|
||||
return
|
||||
|
||||
# use unicode or bytes ?
|
||||
want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \
|
||||
any(isinstance(arg, unicode) for arg in args)
|
||||
|
||||
# pick default end sequence
|
||||
if end is None:
|
||||
end = u("\n") if want_unicode else "\n"
|
||||
elif not isinstance(end, unicode_or_bytes_types):
|
||||
raise TypeError("end must be None or a string")
|
||||
|
||||
# pick default separator
|
||||
if sep is None:
|
||||
sep = u(" ") if want_unicode else " "
|
||||
elif not isinstance(sep, unicode_or_bytes_types):
|
||||
raise TypeError("sep must be None or a string")
|
||||
|
||||
# write to buffer
|
||||
first = True
|
||||
write = fp.write
|
||||
for arg in args:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
write(sep)
|
||||
if not isinstance(arg, basestring):
|
||||
arg = str(arg)
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
#=============================================================================
|
||||
# collections
|
||||
#=============================================================================
|
||||
if PY26:
|
||||
_lazy_attrs['OrderedDict'] = 'passlib.utils.compat._ordered_dict.OrderedDict'
|
||||
else:
|
||||
_lazy_attrs['OrderedDict'] = 'collections.OrderedDict'
|
||||
|
||||
#=============================================================================
|
||||
# context managers
|
||||
#=============================================================================
|
||||
|
||||
try:
|
||||
# new in py37
|
||||
from contextlib import nullcontext
|
||||
except ImportError:
|
||||
|
||||
class nullcontext(object):
|
||||
"""
|
||||
Context manager that does no additional processing.
|
||||
"""
|
||||
def __init__(self, enter_result=None):
|
||||
self.enter_result = enter_result
|
||||
|
||||
def __enter__(self):
|
||||
return self.enter_result
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
pass
|
||||
|
||||
#=============================================================================
|
||||
# lazy overlay module
|
||||
#=============================================================================
|
||||
from types import ModuleType
|
||||
|
||||
def _import_object(source):
|
||||
"""helper to import object from module; accept format `path.to.object`"""
|
||||
modname, modattr = source.rsplit(".",1)
|
||||
mod = __import__(modname, fromlist=[modattr], level=0)
|
||||
return getattr(mod, modattr)
|
||||
|
||||
class _LazyOverlayModule(ModuleType):
|
||||
"""proxy module which overlays original module,
|
||||
and lazily imports specified attributes.
|
||||
|
||||
this is mainly used to prevent importing of resources
|
||||
that are only needed by certain password hashes,
|
||||
yet allow them to be imported from a single location.
|
||||
|
||||
used by :mod:`passlib.utils`, :mod:`passlib.crypto`,
|
||||
and :mod:`passlib.utils.compat`.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def replace_module(cls, name, attrmap):
|
||||
orig = sys.modules[name]
|
||||
self = cls(name, attrmap, orig)
|
||||
sys.modules[name] = self
|
||||
return self
|
||||
|
||||
def __init__(self, name, attrmap, proxy=None):
|
||||
ModuleType.__init__(self, name)
|
||||
self.__attrmap = attrmap
|
||||
self.__proxy = proxy
|
||||
self.__log = logging.getLogger(name)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
proxy = self.__proxy
|
||||
if proxy and hasattr(proxy, attr):
|
||||
return getattr(proxy, attr)
|
||||
attrmap = self.__attrmap
|
||||
if attr in attrmap:
|
||||
source = attrmap[attr]
|
||||
if callable(source):
|
||||
value = source()
|
||||
else:
|
||||
value = _import_object(source)
|
||||
setattr(self, attr, value)
|
||||
self.__log.debug("loaded lazy attr %r: %r", attr, value)
|
||||
return value
|
||||
raise AttributeError("'module' object has no attribute '%s'" % (attr,))
|
||||
|
||||
def __repr__(self):
|
||||
proxy = self.__proxy
|
||||
if proxy:
|
||||
return repr(proxy)
|
||||
else:
|
||||
return ModuleType.__repr__(self)
|
||||
|
||||
def __dir__(self):
|
||||
attrs = set(dir(self.__class__))
|
||||
attrs.update(self.__dict__)
|
||||
attrs.update(self.__attrmap)
|
||||
proxy = self.__proxy
|
||||
if proxy is not None:
|
||||
attrs.update(dir(proxy))
|
||||
return list(attrs)
|
||||
|
||||
# replace this module with overlay that will lazily import attributes.
|
||||
_LazyOverlayModule.replace_module(__name__, _lazy_attrs)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
@@ -0,0 +1,242 @@
|
||||
"""passlib.utils.compat._ordered_dict -- backport of collections.OrderedDict for py26
|
||||
|
||||
taken from stdlib-suggested recipe at http://code.activestate.com/recipes/576693/
|
||||
|
||||
this should be imported from passlib.utils.compat.OrderedDict, not here.
|
||||
"""
|
||||
|
||||
try:
|
||||
from thread import get_ident as _get_ident
|
||||
except ImportError:
|
||||
from dummy_thread import get_ident as _get_ident
|
||||
|
||||
class OrderedDict(dict):
|
||||
"""Dictionary that remembers insertion order"""
|
||||
# An inherited dict maps keys to values.
|
||||
# The inherited dict provides __getitem__, __len__, __contains__, and get.
|
||||
# The remaining methods are order-aware.
|
||||
# Big-O running times for all methods are the same as for regular dictionaries.
|
||||
|
||||
# The internal self.__map dictionary maps keys to links in a doubly linked list.
|
||||
# The circular doubly linked list starts and ends with a sentinel element.
|
||||
# The sentinel element never gets deleted (this simplifies the algorithm).
|
||||
# Each link is stored as a list of length three: [PREV, NEXT, KEY].
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
'''Initialize an ordered dictionary. Signature is the same as for
|
||||
regular dictionaries, but keyword arguments are not recommended
|
||||
because their insertion order is arbitrary.
|
||||
|
||||
'''
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__root
|
||||
except AttributeError:
|
||||
self.__root = root = [] # sentinel node
|
||||
root[:] = [root, root, None]
|
||||
self.__map = {}
|
||||
self.__update(*args, **kwds)
|
||||
|
||||
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
|
||||
'od.__setitem__(i, y) <==> od[i]=y'
|
||||
# Setting a new item creates a new link which goes at the end of the linked
|
||||
# list, and the inherited dictionary is updated with the new key/value pair.
|
||||
if key not in self:
|
||||
root = self.__root
|
||||
last = root[0]
|
||||
last[1] = root[0] = self.__map[key] = [last, root, key]
|
||||
dict_setitem(self, key, value)
|
||||
|
||||
def __delitem__(self, key, dict_delitem=dict.__delitem__):
|
||||
'od.__delitem__(y) <==> del od[y]'
|
||||
# Deleting an existing item uses self.__map to find the link which is
|
||||
# then removed by updating the links in the predecessor and successor nodes.
|
||||
dict_delitem(self, key)
|
||||
link_prev, link_next, key = self.__map.pop(key)
|
||||
link_prev[1] = link_next
|
||||
link_next[0] = link_prev
|
||||
|
||||
def __iter__(self):
|
||||
'od.__iter__() <==> iter(od)'
|
||||
root = self.__root
|
||||
curr = root[1]
|
||||
while curr is not root:
|
||||
yield curr[2]
|
||||
curr = curr[1]
|
||||
|
||||
def __reversed__(self):
|
||||
'od.__reversed__() <==> reversed(od)'
|
||||
root = self.__root
|
||||
curr = root[0]
|
||||
while curr is not root:
|
||||
yield curr[2]
|
||||
curr = curr[0]
|
||||
|
||||
def clear(self):
|
||||
'od.clear() -> None. Remove all items from od.'
|
||||
try:
|
||||
for node in self.__map.itervalues():
|
||||
del node[:]
|
||||
root = self.__root
|
||||
root[:] = [root, root, None]
|
||||
self.__map.clear()
|
||||
except AttributeError:
|
||||
pass
|
||||
dict.clear(self)
|
||||
|
||||
def popitem(self, last=True):
|
||||
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
|
||||
Pairs are returned in LIFO order if last is true or FIFO order if false.
|
||||
|
||||
'''
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
root = self.__root
|
||||
if last:
|
||||
link = root[0]
|
||||
link_prev = link[0]
|
||||
link_prev[1] = root
|
||||
root[0] = link_prev
|
||||
else:
|
||||
link = root[1]
|
||||
link_next = link[1]
|
||||
root[1] = link_next
|
||||
link_next[0] = root
|
||||
key = link[2]
|
||||
del self.__map[key]
|
||||
value = dict.pop(self, key)
|
||||
return key, value
|
||||
|
||||
# -- the following methods do not depend on the internal structure --
|
||||
|
||||
def keys(self):
|
||||
'od.keys() -> list of keys in od'
|
||||
return list(self)
|
||||
|
||||
def values(self):
|
||||
'od.values() -> list of values in od'
|
||||
return [self[key] for key in self]
|
||||
|
||||
def items(self):
|
||||
'od.items() -> list of (key, value) pairs in od'
|
||||
return [(key, self[key]) for key in self]
|
||||
|
||||
def iterkeys(self):
|
||||
'od.iterkeys() -> an iterator over the keys in od'
|
||||
return iter(self)
|
||||
|
||||
def itervalues(self):
|
||||
'od.itervalues -> an iterator over the values in od'
|
||||
for k in self:
|
||||
yield self[k]
|
||||
|
||||
def iteritems(self):
|
||||
'od.iteritems -> an iterator over the (key, value) items in od'
|
||||
for k in self:
|
||||
yield (k, self[k])
|
||||
|
||||
def update(*args, **kwds):
|
||||
'''od.update(E, **F) -> None. Update od from dict/iterable E and F.
|
||||
|
||||
If E is a dict instance, does: for k in E: od[k] = E[k]
|
||||
If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
|
||||
Or if E is an iterable of items, does: for k, v in E: od[k] = v
|
||||
In either case, this is followed by: for k, v in F.items(): od[k] = v
|
||||
|
||||
'''
|
||||
if len(args) > 2:
|
||||
raise TypeError('update() takes at most 2 positional '
|
||||
'arguments (%d given)' % (len(args),))
|
||||
elif not args:
|
||||
raise TypeError('update() takes at least 1 argument (0 given)')
|
||||
self = args[0]
|
||||
# Make progressively weaker assumptions about "other"
|
||||
other = ()
|
||||
if len(args) == 2:
|
||||
other = args[1]
|
||||
if isinstance(other, dict):
|
||||
for key in other:
|
||||
self[key] = other[key]
|
||||
elif hasattr(other, 'keys'):
|
||||
for key in other.keys():
|
||||
self[key] = other[key]
|
||||
else:
|
||||
for key, value in other:
|
||||
self[key] = value
|
||||
for key, value in kwds.items():
|
||||
self[key] = value
|
||||
|
||||
__update = update # let subclasses override update without breaking __init__
|
||||
|
||||
__marker = object()
|
||||
|
||||
def pop(self, key, default=__marker):
|
||||
'''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
||||
If key is not found, d is returned if given, otherwise KeyError is raised.
|
||||
|
||||
'''
|
||||
if key in self:
|
||||
result = self[key]
|
||||
del self[key]
|
||||
return result
|
||||
if default is self.__marker:
|
||||
raise KeyError(key)
|
||||
return default
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
|
||||
if key in self:
|
||||
return self[key]
|
||||
self[key] = default
|
||||
return default
|
||||
|
||||
def __repr__(self, _repr_running={}):
|
||||
'od.__repr__() <==> repr(od)'
|
||||
call_key = id(self), _get_ident()
|
||||
if call_key in _repr_running:
|
||||
return '...'
|
||||
_repr_running[call_key] = 1
|
||||
try:
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
finally:
|
||||
del _repr_running[call_key]
|
||||
|
||||
def __reduce__(self):
|
||||
'Return state information for pickling'
|
||||
items = [[k, self[k]] for k in self]
|
||||
inst_dict = vars(self).copy()
|
||||
for k in vars(OrderedDict()):
|
||||
inst_dict.pop(k, None)
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def copy(self):
|
||||
'od.copy() -> a shallow copy of od'
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
'''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
|
||||
and values equal to v (which defaults to None).
|
||||
|
||||
'''
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
|
||||
while comparison to a regular mapping is order-insensitive.
|
||||
|
||||
'''
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and self.items() == other.items()
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
233
backend/venv/Lib/site-packages/passlib/utils/decor.py
Normal file
233
backend/venv/Lib/site-packages/passlib/utils/decor.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""
|
||||
passlib.utils.decor -- helper decorators & properties
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
from functools import wraps, update_wrapper
|
||||
import types
|
||||
from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import PY3
|
||||
# local
|
||||
__all__ = [
|
||||
"classproperty",
|
||||
"hybrid_method",
|
||||
|
||||
"memoize_single_value",
|
||||
"memoized_property",
|
||||
|
||||
"deprecated_function",
|
||||
"deprecated_method",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# class-level decorators
|
||||
#=============================================================================
|
||||
class classproperty(object):
|
||||
"""Function decorator which acts like a combination of classmethod+property (limited to read-only properties)"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.im_func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.im_func(cls)
|
||||
|
||||
@property
|
||||
def __func__(self):
|
||||
"""py3 compatible alias"""
|
||||
return self.im_func
|
||||
|
||||
class hybrid_method(object):
|
||||
"""
|
||||
decorator which invokes function with class if called as class method,
|
||||
and with object if called at instance level.
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
update_wrapper(self, func)
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
obj = cls
|
||||
if PY3:
|
||||
return types.MethodType(self.func, obj)
|
||||
else:
|
||||
return types.MethodType(self.func, obj, cls)
|
||||
|
||||
#=============================================================================
|
||||
# memoization
|
||||
#=============================================================================
|
||||
|
||||
def memoize_single_value(func):
|
||||
"""
|
||||
decorator for function which takes no args,
|
||||
and memoizes result. exposes a ``.clear_cache`` method
|
||||
to clear the cached value.
|
||||
"""
|
||||
cache = {}
|
||||
|
||||
@wraps(func)
|
||||
def wrapper():
|
||||
try:
|
||||
return cache[True]
|
||||
except KeyError:
|
||||
pass
|
||||
value = cache[True] = func()
|
||||
return value
|
||||
|
||||
def clear_cache():
|
||||
cache.pop(True, None)
|
||||
wrapper.clear_cache = clear_cache
|
||||
|
||||
return wrapper
|
||||
|
||||
class memoized_property(object):
|
||||
"""
|
||||
decorator which invokes method once, then replaces attr with result
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.__func__ = func
|
||||
self.__name__ = func.__name__
|
||||
self.__doc__ = func.__doc__
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
value = self.__func__(obj)
|
||||
setattr(obj, self.__name__, value)
|
||||
return value
|
||||
|
||||
if not PY3:
|
||||
|
||||
@property
|
||||
def im_func(self):
|
||||
"""py2 alias"""
|
||||
return self.__func__
|
||||
|
||||
def clear_cache(self, obj):
|
||||
"""
|
||||
class-level helper to clear stored value (if any).
|
||||
|
||||
usage: :samp:`type(self).{attr}.clear_cache(self)`
|
||||
"""
|
||||
obj.__dict__.pop(self.__name__, None)
|
||||
|
||||
def peek_cache(self, obj, default=None):
|
||||
"""
|
||||
class-level helper to peek at stored value
|
||||
|
||||
usage: :samp:`value = type(self).{attr}.clear_cache(self)`
|
||||
"""
|
||||
return obj.__dict__.get(self.__name__, default)
|
||||
|
||||
# works but not used
|
||||
##class memoized_class_property(object):
|
||||
## """function decorator which calls function as classmethod,
|
||||
## and replaces itself with result for current and all future invocations.
|
||||
## """
|
||||
## def __init__(self, func):
|
||||
## self.im_func = func
|
||||
##
|
||||
## def __get__(self, obj, cls):
|
||||
## func = self.im_func
|
||||
## value = func(cls)
|
||||
## setattr(cls, func.__name__, value)
|
||||
## return value
|
||||
##
|
||||
## @property
|
||||
## def __func__(self):
|
||||
## "py3 compatible alias"
|
||||
|
||||
#=============================================================================
|
||||
# deprecation
|
||||
#=============================================================================
|
||||
def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
|
||||
replacement=None, _is_method=False,
|
||||
func_module=None):
|
||||
"""decorator to deprecate a function.
|
||||
|
||||
:arg msg: optional msg, default chosen if omitted
|
||||
:kwd deprecated: version when function was first deprecated
|
||||
:kwd removed: version when function will be removed
|
||||
:kwd replacement: alternate name / instructions for replacing this function.
|
||||
:kwd updoc: add notice to docstring (default ``True``)
|
||||
"""
|
||||
if msg is None:
|
||||
if _is_method:
|
||||
msg = "the method %(mod)s.%(klass)s.%(name)s() is deprecated"
|
||||
else:
|
||||
msg = "the function %(mod)s.%(name)s() is deprecated"
|
||||
if deprecated:
|
||||
msg += " as of Passlib %(deprecated)s"
|
||||
if removed:
|
||||
msg += ", and will be removed in Passlib %(removed)s"
|
||||
if replacement:
|
||||
msg += ", use %s instead" % replacement
|
||||
msg += "."
|
||||
def build(func):
|
||||
is_classmethod = _is_method and isinstance(func, classmethod)
|
||||
if is_classmethod:
|
||||
# NOTE: PY26 doesn't support "classmethod().__func__" directly...
|
||||
func = func.__get__(None, type).__func__
|
||||
opts = dict(
|
||||
mod=func_module or func.__module__,
|
||||
name=func.__name__,
|
||||
deprecated=deprecated,
|
||||
removed=removed,
|
||||
)
|
||||
if _is_method:
|
||||
def wrapper(*args, **kwds):
|
||||
tmp = opts.copy()
|
||||
klass = args[0] if is_classmethod else args[0].__class__
|
||||
tmp.update(klass=klass.__name__, mod=klass.__module__)
|
||||
warn(msg % tmp, DeprecationWarning, stacklevel=2)
|
||||
return func(*args, **kwds)
|
||||
else:
|
||||
text = msg % opts
|
||||
def wrapper(*args, **kwds):
|
||||
warn(text, DeprecationWarning, stacklevel=2)
|
||||
return func(*args, **kwds)
|
||||
update_wrapper(wrapper, func)
|
||||
if updoc and (deprecated or removed) and \
|
||||
wrapper.__doc__ and ".. deprecated::" not in wrapper.__doc__:
|
||||
txt = deprecated or ''
|
||||
if removed or replacement:
|
||||
txt += "\n "
|
||||
if removed:
|
||||
txt += "and will be removed in version %s" % (removed,)
|
||||
if replacement:
|
||||
if removed:
|
||||
txt += ", "
|
||||
txt += "use %s instead" % replacement
|
||||
txt += "."
|
||||
if not wrapper.__doc__.strip(" ").endswith("\n"):
|
||||
wrapper.__doc__ += "\n"
|
||||
wrapper.__doc__ += "\n.. deprecated:: %s\n" % (txt,)
|
||||
if is_classmethod:
|
||||
wrapper = classmethod(wrapper)
|
||||
return wrapper
|
||||
return build
|
||||
|
||||
def deprecated_method(msg=None, deprecated=None, removed=None, updoc=True,
|
||||
replacement=None):
|
||||
"""decorator to deprecate a method.
|
||||
|
||||
:arg msg: optional msg, default chosen if omitted
|
||||
:kwd deprecated: version when method was first deprecated
|
||||
:kwd removed: version when method will be removed
|
||||
:kwd replacement: alternate name / instructions for replacing this method.
|
||||
:kwd updoc: add notice to docstring (default ``True``)
|
||||
"""
|
||||
return deprecated_function(msg, deprecated, removed, updoc, replacement,
|
||||
_is_method=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
46
backend/venv/Lib/site-packages/passlib/utils/des.py
Normal file
46
backend/venv/Lib/site-packages/passlib/utils/des.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
passlib.utils.des - DEPRECATED LOCATION, WILL BE REMOVED IN 2.0
|
||||
|
||||
This has been moved to :mod:`passlib.crypto.des`.
|
||||
"""
|
||||
#=============================================================================
|
||||
# import from new location
|
||||
#=============================================================================
|
||||
from warnings import warn
|
||||
warn("the 'passlib.utils.des' module has been relocated to 'passlib.crypto.des' "
|
||||
"as of passlib 1.7, and the old location will be removed in passlib 2.0",
|
||||
DeprecationWarning)
|
||||
|
||||
#=============================================================================
|
||||
# relocated functions
|
||||
#=============================================================================
|
||||
from passlib.utils.decor import deprecated_function
|
||||
from passlib.crypto.des import expand_des_key, des_encrypt_block, des_encrypt_int_block
|
||||
|
||||
expand_des_key = deprecated_function(deprecated="1.7", removed="1.8",
|
||||
replacement="passlib.crypto.des.expand_des_key")(expand_des_key)
|
||||
|
||||
des_encrypt_block = deprecated_function(deprecated="1.7", removed="1.8",
|
||||
replacement="passlib.crypto.des.des_encrypt_block")(des_encrypt_block)
|
||||
|
||||
des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="1.8",
|
||||
replacement="passlib.crypto.des.des_encrypt_int_block")(des_encrypt_int_block)
|
||||
|
||||
#=============================================================================
|
||||
# deprecated functions -- not carried over to passlib.crypto.des
|
||||
#=============================================================================
|
||||
import struct
|
||||
_unpack_uint64 = struct.Struct(">Q").unpack
|
||||
|
||||
@deprecated_function(deprecated="1.6", removed="1.8",
|
||||
replacement="passlib.crypto.des.des_encrypt_int_block()")
|
||||
def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover -- deprecated & unused
|
||||
if isinstance(key, bytes):
|
||||
if len(key) == 7:
|
||||
key = expand_des_key(key)
|
||||
key = _unpack_uint64(key)[0]
|
||||
return des_encrypt_int_block(key, input, salt, rounds)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
2711
backend/venv/Lib/site-packages/passlib/utils/handlers.py
Normal file
2711
backend/venv/Lib/site-packages/passlib/utils/handlers.py
Normal file
File diff suppressed because it is too large
Load Diff
29
backend/venv/Lib/site-packages/passlib/utils/md4.py
Normal file
29
backend/venv/Lib/site-packages/passlib/utils/md4.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
passlib.utils.md4 - DEPRECATED MODULE, WILL BE REMOVED IN 2.0
|
||||
|
||||
MD4 should now be looked up through ``passlib.crypto.digest.lookup_hash("md4").const``,
|
||||
which provides unified handling stdlib implementation (if present).
|
||||
"""
|
||||
#=============================================================================
|
||||
# issue deprecation warning for module
|
||||
#=============================================================================
|
||||
from warnings import warn
|
||||
warn("the module 'passlib.utils.md4' is deprecated as of Passlib 1.7, "
|
||||
"and will be removed in Passlib 2.0, please use "
|
||||
"'lookup_hash(\"md4\").const()' from 'passlib.crypto' instead",
|
||||
DeprecationWarning)
|
||||
|
||||
#=============================================================================
|
||||
# backwards compat exports
|
||||
#=============================================================================
|
||||
__all__ = ["md4"]
|
||||
|
||||
# this should use hashlib version if available,
|
||||
# and fall back to builtin version.
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
md4 = lookup_hash("md4").const
|
||||
del lookup_hash
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
193
backend/venv/Lib/site-packages/passlib/utils/pbkdf2.py
Normal file
193
backend/venv/Lib/site-packages/passlib/utils/pbkdf2.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""passlib.pbkdf2 - PBKDF2 support
|
||||
|
||||
this module is getting increasingly poorly named.
|
||||
maybe rename to "kdf" since it's getting more key derivation functions added.
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import division
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.exc import ExpectedTypeError
|
||||
from passlib.utils.decor import deprecated_function
|
||||
from passlib.utils.compat import native_string_types
|
||||
from passlib.crypto.digest import norm_hash_name, lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac
|
||||
# local
|
||||
__all__ = [
|
||||
# hash utils
|
||||
"norm_hash_name",
|
||||
|
||||
# prf utils
|
||||
"get_prf",
|
||||
|
||||
# kdfs
|
||||
"pbkdf1",
|
||||
"pbkdf2",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# issue deprecation warning for module
|
||||
#=============================================================================
|
||||
from warnings import warn
|
||||
|
||||
warn("the module 'passlib.utils.pbkdf2' is deprecated as of Passlib 1.7, "
|
||||
"and will be removed in Passlib 2.0, please use 'passlib.crypto' instead",
|
||||
DeprecationWarning)
|
||||
|
||||
#=============================================================================
|
||||
# hash helpers
|
||||
#=============================================================================
|
||||
|
||||
norm_hash_name = deprecated_function(deprecated="1.7", removed="1.8", func_module=__name__,
|
||||
replacement="passlib.crypto.digest.norm_hash_name")(norm_hash_name)
|
||||
|
||||
#=============================================================================
|
||||
# prf lookup
|
||||
#=============================================================================
|
||||
|
||||
#: cache mapping prf name/func -> (func, digest_size)
|
||||
_prf_cache = {}
|
||||
|
||||
#: list of accepted prefixes
|
||||
_HMAC_PREFIXES = ("hmac_", "hmac-")
|
||||
|
||||
def get_prf(name):
|
||||
"""Lookup pseudo-random family (PRF) by name.
|
||||
|
||||
:arg name:
|
||||
This must be the name of a recognized prf.
|
||||
Currently this only recognizes names with the format
|
||||
:samp:`hmac-{digest}`, where :samp:`{digest}`
|
||||
is the name of a hash function such as
|
||||
``md5``, ``sha256``, etc.
|
||||
|
||||
todo: restore text about callables.
|
||||
|
||||
:raises ValueError: if the name is not known
|
||||
:raises TypeError: if the name is not a callable or string
|
||||
|
||||
:returns:
|
||||
a tuple of :samp:`({prf_func}, {digest_size})`, where:
|
||||
|
||||
* :samp:`{prf_func}` is a function implementing
|
||||
the specified PRF, and has the signature
|
||||
``prf_func(secret, message) -> digest``.
|
||||
|
||||
* :samp:`{digest_size}` is an integer indicating
|
||||
the number of bytes the function returns.
|
||||
|
||||
Usage example::
|
||||
|
||||
>>> from passlib.utils.pbkdf2 import get_prf
|
||||
>>> hmac_sha256, dsize = get_prf("hmac-sha256")
|
||||
>>> hmac_sha256
|
||||
<function hmac_sha256 at 0x1e37c80>
|
||||
>>> dsize
|
||||
32
|
||||
>>> digest = hmac_sha256('password', 'message')
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
This function is deprecated, and will be removed in Passlib 2.0.
|
||||
This only related replacement is :func:`passlib.crypto.digest.compile_hmac`.
|
||||
"""
|
||||
global _prf_cache
|
||||
if name in _prf_cache:
|
||||
return _prf_cache[name]
|
||||
if isinstance(name, native_string_types):
|
||||
if not name.startswith(_HMAC_PREFIXES):
|
||||
raise ValueError("unknown prf algorithm: %r" % (name,))
|
||||
digest = lookup_hash(name[5:]).name
|
||||
def hmac(key, msg):
|
||||
return compile_hmac(digest, key)(msg)
|
||||
record = (hmac, hmac.digest_info.digest_size)
|
||||
elif callable(name):
|
||||
# assume it's a callable, use it directly
|
||||
digest_size = len(name(b'x', b'y'))
|
||||
record = (name, digest_size)
|
||||
else:
|
||||
raise ExpectedTypeError(name, "str or callable", "prf name")
|
||||
_prf_cache[name] = record
|
||||
return record
|
||||
|
||||
#=============================================================================
|
||||
# pbkdf1 support
|
||||
#=============================================================================
|
||||
def pbkdf1(secret, salt, rounds, keylen=None, hash="sha1"):
|
||||
"""pkcs#5 password-based key derivation v1.5
|
||||
|
||||
:arg secret: passphrase to use to generate key
|
||||
:arg salt: salt string to use when generating key
|
||||
:param rounds: number of rounds to use to generate key
|
||||
:arg keylen: number of bytes to generate (if ``None``, uses digest's native size)
|
||||
:param hash:
|
||||
hash function to use. must be name of a hash recognized by hashlib.
|
||||
|
||||
:returns:
|
||||
raw bytes of generated key
|
||||
|
||||
.. note::
|
||||
|
||||
This algorithm has been deprecated, new code should use PBKDF2.
|
||||
Among other limitations, ``keylen`` cannot be larger
|
||||
than the digest size of the specified hash.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
This has been relocated to :func:`passlib.crypto.digest.pbkdf1`,
|
||||
and this version will be removed in Passlib 2.0.
|
||||
*Note the call signature has changed.*
|
||||
"""
|
||||
return _pbkdf1(hash, secret, salt, rounds, keylen)
|
||||
|
||||
#=============================================================================
|
||||
# pbkdf2
|
||||
#=============================================================================
|
||||
def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"):
|
||||
"""pkcs#5 password-based key derivation v2.0
|
||||
|
||||
:arg secret:
|
||||
passphrase to use to generate key
|
||||
|
||||
:arg salt:
|
||||
salt string to use when generating key
|
||||
|
||||
:param rounds:
|
||||
number of rounds to use to generate key
|
||||
|
||||
:arg keylen:
|
||||
number of bytes to generate.
|
||||
if set to ``None``, will use digest size of selected prf.
|
||||
|
||||
:param prf:
|
||||
psuedo-random family to use for key strengthening.
|
||||
this must be a string starting with ``"hmac-"``, followed by the name of a known digest.
|
||||
this defaults to ``"hmac-sha1"`` (the only prf explicitly listed in
|
||||
the PBKDF2 specification)
|
||||
|
||||
.. rst-class:: warning
|
||||
|
||||
.. versionchanged 1.7:
|
||||
|
||||
This argument no longer supports arbitrary PRF callables --
|
||||
These were rarely / never used, and created too many unwanted codepaths.
|
||||
|
||||
:returns:
|
||||
raw bytes of generated key
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`,
|
||||
and will be removed in Passlib 2.0. *Note the call signature has changed.*
|
||||
"""
|
||||
if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)):
|
||||
raise NotImplementedError("non-HMAC prfs are not supported as of Passlib 1.7")
|
||||
digest = prf[5:]
|
||||
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user