Contrib passlib

legacy27 3.6.8.2
Mike Hiretsky 4 years ago
parent c8fbe2da5b
commit c694653856

@ -0,0 +1,3 @@
"""passlib - suite of password hashing & generation routines"""
__version__ = '1.7.2'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1 @@
"""passlib.setup - helpers used by passlib's setup.py script"""

@ -0,0 +1,149 @@
"""update version string during build"""
#=============================================================================
# imports
#=============================================================================
from __future__ import absolute_import, division, print_function
# core
import datetime
from distutils.dist import Distribution
import os
import re
import subprocess
import time
# pkg
# local
__all__ = [
"stamp_source",
"stamp_distutils_output",
"append_hg_revision",
"as_bool",
]
#=============================================================================
# helpers
#=============================================================================
def get_command_class(opts, name):
return opts['cmdclass'].get(name) or Distribution().get_command_class(name)
def get_command_options(opts, command):
return opts.setdefault("options", {}).setdefault(command, {})
def set_command_options(opts, command, **kwds):
get_command_options(opts, command).update(kwds)
def _get_file(path):
with open(path, "r") as fh:
return fh.read()
def _replace_file(path, content, dry_run=False):
if dry_run:
return
if os.path.exists(path):
# sdist likes to use hardlinks, have to remove them first,
# or we modify *source* file
os.unlink(path)
with open(path, "w") as fh:
fh.write(content)
def stamp_source(base_dir, version, dry_run=False):
"""
update version info in passlib source
"""
#
# update version string in toplevel package source
#
path = os.path.join(base_dir, "passlib", "__init__.py")
content = _get_file(path)
content, count = re.subn('(?m)^__version__\s*=.*$',
'__version__ = ' + repr(version),
content)
assert count == 1, "failed to replace version string"
_replace_file(path, content, dry_run=dry_run)
#
# update flag in setup.py
# (not present when called from bdist_wheel, etc)
#
path = os.path.join(base_dir, "setup.py")
if os.path.exists(path):
content = _get_file(path)
content, count = re.subn('(?m)^stamp_build\s*=.*$',
'stamp_build = False', content)
assert count == 1, "failed to update 'stamp_build' flag"
_replace_file(path, content, dry_run=dry_run)
def stamp_distutils_output(opts, version):
# subclass buildpy to update version string in source
_build_py = get_command_class(opts, "build_py")
class build_py(_build_py):
def build_packages(self):
_build_py.build_packages(self)
stamp_source(self.build_lib, version, self.dry_run)
opts['cmdclass']['build_py'] = build_py
# subclass sdist to do same thing
_sdist = get_command_class(opts, "sdist")
class sdist(_sdist):
def make_release_tree(self, base_dir, files):
_sdist.make_release_tree(self, base_dir, files)
stamp_source(base_dir, version, self.dry_run)
opts['cmdclass']['sdist'] = sdist
def as_bool(value):
return (value or "").lower() in "yes y true t 1".split()
def append_hg_revision(version):
# call HG via subprocess
# NOTE: for py26 compat, using Popen() instead of check_output()
try:
proc = subprocess.Popen(["hg", "tip", "--template", "{date(date, '%Y%m%d%H%M%S')}+hg.{node|short}"],
stdout=subprocess.PIPE)
stamp, _ = proc.communicate()
if proc.returncode:
raise subprocess.CalledProcessError(1, [])
stamp = stamp.decode("ascii")
except (OSError, subprocess.CalledProcessError):
# fallback - just use build date
now = int(os.environ.get('SOURCE_DATE_EPOCH') or time.time())
build_date = datetime.datetime.utcfromtimestamp(now)
stamp = build_date.strftime("%Y%m%d%H%M%S")
# modify version
if version.endswith((".dev0", ".post0")):
version = version[:-1] + stamp
else:
version += ".post" + stamp
return version
def install_build_py_exclude(opts):
_build_py = get_command_class(opts, "build_py")
class build_py(_build_py):
user_options = _build_py.user_options + [
("exclude-packages=", None,
"exclude packages from builds"),
]
exclude_packages = None
def finalize_options(self):
_build_py.finalize_options(self)
target = self.packages
for package in self.exclude_packages or []:
if package in target:
target.remove(package)
opts['cmdclass']['build_py'] = build_py
#=============================================================================
# eof
#=============================================================================

File diff suppressed because it is too large Load Diff

@ -0,0 +1,197 @@
"""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
#=============================================================================
_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"],
)
_django14_schemes = ["django_pbkdf2_sha256", "django_pbkdf2_sha1",
"django_bcrypt"] + _django10_schemes
django14_context = LazyCryptContext(
schemes=_django14_schemes,
deprecated=_django10_schemes,
)
_django16_schemes = _django14_schemes[:]
_django16_schemes.insert(1, "django_bcrypt_sha256")
django16_context = LazyCryptContext(
schemes=_django16_schemes,
deprecated=_django10_schemes,
)
django110_context = LazyCryptContext(
schemes=["django_pbkdf2_sha256", "django_pbkdf2_sha1",
"django_argon2", "django_bcrypt", "django_bcrypt_sha256",
"django_disabled"],
)
# this will always point to latest version
django_context = django110_context
#=============================================================================
# ldap
#=============================================================================
std_ldap_schemes = ["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
#=============================================================================

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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -0,0 +1,891 @@
"""passlib.crypto.digest -- crytographic helpers used by the password hashes in passlib
.. versionadded:: 1.7
"""
#=============================================================================
# imports
#=============================================================================
from __future__ import division
# core
import hashlib
import logging; log = logging.getLogger(__name__)
try:
# new in py3.4
from hashlib import pbkdf2_hmac as _stdlib_pbkdf2_hmac
if _stdlib_pbkdf2_hmac.__module__ == "hashlib":
# builtin pure-python backends are slightly faster than stdlib's pure python fallback,
# so only using stdlib's version if it's backed by openssl's pbkdf2_hmac()
log.debug("ignoring pure-python hashlib.pbkdf2_hmac()")
_stdlib_pbkdf2_hmac = None
except ImportError:
_stdlib_pbkdf2_hmac = None
import re
import os
from struct import Struct
from warnings import warn
# site
try:
# https://pypi.python.org/pypi/fastpbkdf2/
from fastpbkdf2 import pbkdf2_hmac as _fast_pbkdf2_hmac
except ImportError:
_fast_pbkdf2_hmac = None
# pkg
from passlib import exc
from passlib.utils import join_bytes, to_native_str, join_byte_values, to_bytes, \
SequenceMixin
from passlib.utils.compat import irange, int_types, unicode_or_bytes_types, PY3
from passlib.utils.decor import memoized_property
# local
__all__ = [
# hash utils
"lookup_hash",
"HashInfo",
"norm_hash_name",
# hmac utils
"compile_hmac",
# kdfs
"pbkdf1",
"pbkdf2_hmac",
]
#=============================================================================
# generic constants
#=============================================================================
#: max 32-bit value
MAX_UINT32 = (1 << 32) - 1
#: max 64-bit value
MAX_UINT64 = (1 << 64) - 1
#=============================================================================
# hash utils
#=============================================================================
#: list of known hash names, used by lookup_hash()'s _norm_hash_name() helper
_known_hash_names = [
# format: (hashlib/ssl name, iana name or standin, other known aliases ...)
# hashes with official IANA-assigned names
# (as of 2012-03 - http://www.iana.org/assignments/hash-function-text-names)
("md2", "md2"),
("md5", "md5"),
("sha1", "sha-1"),
("sha224", "sha-224", "sha2-224"),
("sha256", "sha-256", "sha2-256"),
("sha384", "sha-384", "sha2-384"),
("sha512", "sha-512", "sha2-512"),
# TODO: add sha3 to this table.
# hashlib/ssl-supported hashes without official IANA names,
# (hopefully-) compatible stand-ins have been chosen.
("md4", "md4"),
("sha", "sha-0", "sha0"),
("ripemd", "ripemd"),
("ripemd160", "ripemd-160"),
]
#: cache of hash info instances used by lookup_hash()
_hash_info_cache = {}
def _get_hash_aliases(name):
"""
internal helper used by :func:`lookup_hash` --
normalize arbitrary hash name to hashlib format.
if name not recognized, returns dummy record and issues a warning.
:arg name:
unnormalized name
:returns:
tuple with 2+ elements: ``(hashlib_name, iana_name|None, ... 0+ aliases)``.
"""
# normalize input
orig = name
if not isinstance(name, str):
name = to_native_str(name, 'utf-8', 'hash name')
name = re.sub("[_ /]", "-", name.strip().lower())
if name.startswith("scram-"): # helper for SCRAM protocol (see passlib.handlers.scram)
name = name[6:]
if name.endswith("-plus"):
name = name[:-5]
# look through standard names and known aliases
def check_table(name):
for row in _known_hash_names:
if name in row:
return row
result = check_table(name)
if result:
return result
# try to clean name up some more
m = re.match(r"(?i)^(?P<name>[a-z]+)-?(?P<rev>\d)?-?(?P<size>\d{3,4})?$", name)
if m:
# roughly follows "SHA2-256" style format, normalize representation,
# and checked table.
iana_name, rev, size = m.group("name", "rev", "size")
if rev:
iana_name += rev
hashlib_name = iana_name
if size:
iana_name += "-" + size
if rev:
hashlib_name += "_"
hashlib_name += size
result = check_table(iana_name)
if result:
return result
# not found in table, but roughly recognize format. use names we built up as fallback.
log.info("normalizing unrecognized hash name %r => %r / %r",
orig, hashlib_name, iana_name)
else:
# just can't make sense of it. return something
iana_name = name
hashlib_name = name.replace("-", "_")
log.warning("normalizing unrecognized hash name and format %r => %r / %r",
orig, hashlib_name, iana_name)
return hashlib_name, iana_name
def _get_hash_const(name):
"""
internal helper used by :func:`lookup_hash` --
lookup hash constructor by name
:arg name:
name (normalized to hashlib format, e.g. ``"sha256"``)
:returns:
hash constructor, e.g. ``hashlib.sha256()``;
or None if hash can't be located.
"""
# check hashlib.<attr> for an efficient constructor
if not name.startswith("_") and name not in ("new", "algorithms"):
try:
return getattr(hashlib, name)
except AttributeError:
pass
# check hashlib.new() in case SSL supports the digest
new_ssl_hash = hashlib.new
try:
# new() should throw ValueError if alg is unknown
new_ssl_hash(name, b"")
except ValueError:
pass
else:
# create wrapper function
# XXX: is there a faster way to wrap this?
def const(msg=b""):
return new_ssl_hash(name, msg)
const.__name__ = name
const.__module__ = "hashlib"
const.__doc__ = ("wrapper for hashlib.new(%r),\n"
"generated by passlib.crypto.digest.lookup_hash()") % name
return const
# use builtin md4 as fallback when not supported by hashlib
if name == "md4":
from passlib.crypto._md4 import md4
return md4
# XXX: any other modules / registries we should check?
# TODO: add pysha3 support.
return None
def lookup_hash(digest, return_unknown=False):
"""
Returns a :class:`HashInfo` record containing information about a given hash function.
Can be used to look up a hash constructor by name, normalize hash name representation, etc.
:arg digest:
This can be any of:
* A string containing a :mod:`!hashlib` digest name (e.g. ``"sha256"``),
* A string containing an IANA-assigned hash name,
* A digest constructor function (e.g. ``hashlib.sha256``).
Case is ignored, underscores are converted to hyphens,
and various other cleanups are made.
:param return_unknown:
By default, this function will throw an :exc:`~passlib.exc.UnknownHashError` if no hash constructor
can be found. However, if this flag is False, it will instead return a dummy record
without a constructor function. This is mainly used by :func:`norm_hash_name`.
:returns HashInfo:
:class:`HashInfo` instance containing information about specified digest.
Multiple calls resolving to the same hash should always
return the same :class:`!HashInfo` instance.
"""
# check for cached entry
cache = _hash_info_cache
try:
return cache[digest]
except (KeyError, TypeError):
# NOTE: TypeError is to catch 'TypeError: unhashable type' (e.g. HashInfo)
pass
# resolve ``digest`` to ``const`` & ``name_record``
cache_by_name = True
if isinstance(digest, unicode_or_bytes_types):
# normalize name
name_list = _get_hash_aliases(digest)
name = name_list[0]
assert name
# if name wasn't normalized to hashlib format,
# get info for normalized name and reuse it.
if name != digest:
info = lookup_hash(name, return_unknown=return_unknown)
if info.const is None:
# pass through dummy record
assert return_unknown
return info
cache[digest] = info
return info
# else look up constructor
const = _get_hash_const(name)
if const is None:
if return_unknown:
# return a dummy record (but don't cache it, so normal lookup still returns error)
return HashInfo(None, name_list)
else:
raise exc.UnknownHashError(name)
elif isinstance(digest, HashInfo):
# handle border case where HashInfo is passed in.
return digest
elif callable(digest):
# try to lookup digest based on it's self-reported name
# (which we trust to be the canonical "hashlib" name)
const = digest
name_list = _get_hash_aliases(const().name)
name = name_list[0]
other_const = _get_hash_const(name)
if other_const is None:
# this is probably a third-party digest we don't know about,
# so just pass it on through, and register reverse lookup for it's name.
pass
elif other_const is const:
# if we got back same constructor, this is just a known stdlib constructor,
# which was passed in before we had cached it by name. proceed normally.
pass
else:
# if we got back different object, then ``const`` is something else
# (such as a mock object), in which case we want to skip caching it by name,
# as that would conflict with real hash.
cache_by_name = False
else:
raise exc.ExpectedTypeError(digest, "digest name or constructor", "digest")
# create new instance
info = HashInfo(const, name_list)
# populate cache
cache[const] = info
if cache_by_name:
for name in name_list:
if name: # (skips iana name if it's empty)
assert cache.get(name) in [None, info], "%r already in cache" % name
cache[name] = info
return info
#: UT helper for clearing internal cache
lookup_hash.clear_cache = _hash_info_cache.clear
def norm_hash_name(name, format="hashlib"):
"""Normalize hash function name (convenience wrapper for :func:`lookup_hash`).
:arg name:
Original hash function name.
This name can be a Python :mod:`~hashlib` digest name,
a SCRAM mechanism name, IANA assigned hash name, etc.
Case is ignored, and underscores are converted to hyphens.
:param format:
Naming convention to normalize to.
Possible values are:
* ``"hashlib"`` (the default) - normalizes name to be compatible
with Python's :mod:`!hashlib`.
* ``"iana"`` - normalizes name to IANA-assigned hash function name.
For hashes which IANA hasn't assigned a name for, this issues a warning,
and then uses a heuristic to return a "best guess" name.
:returns:
Hash name, returned as native :class:`!str`.
"""
info = lookup_hash(name, return_unknown=True)
if not info.const:
warn("norm_hash_name(): unknown hash: %r" % (name,), exc.PasslibRuntimeWarning)
if format == "hashlib":
return info.name
elif format == "iana":
return info.iana_name
else:
raise ValueError("unknown format: %r" % (format,))
class HashInfo(SequenceMixin):
"""
Record containing information about a given hash algorithm, as returned :func:`lookup_hash`.
This class exposes the following attributes:
.. autoattribute:: const
.. autoattribute:: digest_size
.. autoattribute:: block_size
.. autoattribute:: name
.. autoattribute:: iana_name
.. autoattribute:: aliases
This object can also be treated a 3-element sequence
containing ``(const, digest_size, block_size)``.
"""
#=========================================================================
# instance attrs
#=========================================================================
#: Canonical / hashlib-compatible name (e.g. ``"sha256"``).
name = None
#: IANA assigned name (e.g. ``"sha-256"``), may be ``None`` if unknown.
iana_name = None
#: Tuple of other known aliases (may be empty)
aliases = ()
#: Hash constructor function (e.g. :func:`hashlib.sha256`)
const = None
#: Hash's digest size
digest_size = None
#: Hash's block size
block_size = None
def __init__(self, const, names):
"""
initialize new instance.
:arg const:
hash constructor
:arg names:
list of 2+ names. should be list of ``(name, iana_name, ... 0+ aliases)``.
names must be lower-case. only iana name may be None.
"""
self.name = names[0]
self.iana_name = names[1]
self.aliases = names[2:]
self.const = const
if const is None:
return
hash = const()
self.digest_size = hash.digest_size
self.block_size = hash.block_size
# do sanity check on digest size
if len(hash.digest()) != hash.digest_size:
raise RuntimeError("%r constructor failed sanity check" % self.name)
# do sanity check on name.
if hash.name != self.name:
warn("inconsistent digest name: %r resolved to %r, which reports name as %r" %
(self.name, const, hash.name), exc.PasslibRuntimeWarning)
#=========================================================================
# methods
#=========================================================================
def __repr__(self):
return "<lookup_hash(%r): digest_size=%r block_size=%r)" % \
(self.name, self.digest_size, self.block_size)
def _as_tuple(self):
return self.const, self.digest_size, self.block_size
@memoized_property
def supported_by_fastpbkdf2(self):
"""helper to detect if hash is supported by fastpbkdf2()"""
if not _fast_pbkdf2_hmac:
return None
try:
_fast_pbkdf2_hmac(self.name, b"p", b"s", 1)
return True
except ValueError:
# "unsupported hash type"
return False
@memoized_property
def supported_by_hashlib_pbkdf2(self):
"""helper to detect if hash is supported by hashlib.pbkdf2_hmac()"""
if not _stdlib_pbkdf2_hmac:
return None
try:
_stdlib_pbkdf2_hmac(self.name, b"p", b"s", 1)
return True
except ValueError:
# "unsupported hash type"
return False
#=========================================================================
# eoc
#=========================================================================
#=============================================================================
# hmac utils
#=============================================================================
#: translation tables used by compile_hmac()
_TRANS_5C = join_byte_values((x ^ 0x5C) for x in irange(256))
_TRANS_36 = join_byte_values((x ^ 0x36) for x in irange(256))
def compile_hmac(digest, key, multipart=False):
"""
This function returns an efficient HMAC function, hardcoded with a specific digest & key.
It can be used via ``hmac = compile_hmac(digest, key)``.
:arg digest:
digest name or constructor.
:arg key:
secret key as :class:`!bytes` or :class:`!unicode` (unicode will be encoded using utf-8).
:param multipart:
request a multipart constructor instead (see return description).
:returns:
By default, the returned function has the signature ``hmac(msg) -> digest output``.
However, if ``multipart=True``, the returned function has the signature
``hmac() -> update, finalize``, where ``update(msg)`` may be called multiple times,
and ``finalize() -> digest_output`` may be repeatedly called at any point to
calculate the HMAC digest so far.
The returned object will also have a ``digest_info`` attribute, containing
a :class:`lookup_hash` instance for the specified digest.
This function exists, and has the weird signature it does, in order to squeeze as
provide as much efficiency as possible, by omitting much of the setup cost
and features of the stdlib :mod:`hmac` module.
"""
# all the following was adapted from stdlib's hmac module
# resolve digest (cached)
digest_info = lookup_hash(digest)
const, digest_size, block_size = digest_info
assert block_size >= 16, "block size too small"
# prepare key
if not isinstance(key, bytes):
key = to_bytes(key, param="key")
klen = len(key)
if klen > block_size:
key = const(key).digest()
klen = digest_size
if klen < block_size:
key += b'\x00' * (block_size - klen)
# create pre-initialized hash constructors
_inner_copy = const(key.translate(_TRANS_36)).copy
_outer_copy = const(key.translate(_TRANS_5C)).copy
if multipart:
# create multi-part function
# NOTE: this is slightly slower than the single-shot version,
# and should only be used if needed.
def hmac():
"""generated by compile_hmac(multipart=True)"""
inner = _inner_copy()
def finalize():
outer = _outer_copy()
outer.update(inner.digest())
return outer.digest()
return inner.update, finalize
else:
# single-shot function
def hmac(msg):
"""generated by compile_hmac()"""
inner = _inner_copy()
inner.update(msg)
outer = _outer_copy()
outer.update(inner.digest())
return outer.digest()
# add info attr
hmac.digest_info = digest_info
return hmac
#=============================================================================
# pbkdf1
#=============================================================================
def pbkdf1(digest, secret, salt, rounds, keylen=None):
"""pkcs#5 password-based key derivation v1.5
:arg digest:
digest name or constructor.
:arg secret:
secret to use when generating the key.
may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).
:arg salt:
salt string to use when generating key.
may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).
:param rounds:
number of rounds to use to generate key.
:arg keylen:
number of bytes to generate (if omitted / ``None``, uses digest's native size)
:returns:
raw :class:`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.
"""
# resolve digest
const, digest_size, block_size = lookup_hash(digest)
# validate secret & salt
secret = to_bytes(secret, param="secret")
salt = to_bytes(salt, param="salt")
# validate rounds
if not isinstance(rounds, int_types):
raise exc.ExpectedTypeError(rounds, "int", "rounds")
if rounds < 1:
raise ValueError("rounds must be at least 1")
# validate keylen
if keylen is None:
keylen = digest_size
elif not isinstance(keylen, int_types):
raise exc.ExpectedTypeError(keylen, "int or None", "keylen")
elif keylen < 0:
raise ValueError("keylen must be at least 0")
elif keylen > digest_size:
raise ValueError("keylength too large for digest: %r > %r" %
(keylen, digest_size))
# main pbkdf1 loop
block = secret + salt
for _ in irange(rounds):
block = const(block).digest()
return block[:keylen]
#=============================================================================
# pbkdf2
#=============================================================================
_pack_uint32 = Struct(">L").pack
def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None):
"""pkcs#5 password-based key derivation v2.0 using HMAC + arbitrary digest.
:arg digest:
digest name or constructor.
:arg secret:
passphrase to use to generate key.
may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).
:arg salt:
salt string to use when generating key.
may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).
:param rounds:
number of rounds to use to generate key.
:arg keylen:
number of bytes to generate.
if omitted / ``None``, will use digest's native output size.
:returns:
raw bytes of generated key
.. versionchanged:: 1.7
This function will use the first available of the following backends:
* `fastpbk2 <https://pypi.python.org/pypi/fastpbkdf2>`_
* :func:`hashlib.pbkdf2_hmac` (only available in py2 >= 2.7.8, and py3 >= 3.4)
* builtin pure-python backend
See :data:`passlib.crypto.digest.PBKDF2_BACKENDS` to determine
which backend(s) are in use.
"""
# validate secret & salt
secret = to_bytes(secret, param="secret")
salt = to_bytes(salt, param="salt")
# resolve digest
digest_info = lookup_hash(digest)
digest_size = digest_info.digest_size
# validate rounds
if not isinstance(rounds, int_types):
raise exc.ExpectedTypeError(rounds, "int", "rounds")
if rounds < 1:
raise ValueError("rounds must be at least 1")
# validate keylen
if keylen is None:
keylen = digest_size
elif not isinstance(keylen, int_types):
raise exc.ExpectedTypeError(keylen, "int or None", "keylen")
elif keylen < 1:
# XXX: could allow keylen=0, but want to be compat w/ stdlib
raise ValueError("keylen must be at least 1")
# find smallest block count s.t. keylen <= block_count * digest_size;
# make sure block count won't overflow (per pbkdf2 spec)
# this corresponds to throwing error if keylen > digest_size * MAX_UINT32
# NOTE: stdlib will throw error at lower bound (keylen > MAX_SINT32)
# NOTE: have do this before other backends checked, since fastpbkdf2 raises wrong error
# (InvocationError, not OverflowError)
block_count = (keylen + digest_size - 1) // digest_size
if block_count > MAX_UINT32:
raise OverflowError("keylen too long for digest")
#
# check for various high-speed backends
#
# ~3x faster than pure-python backend
# NOTE: have to do this after above guards since fastpbkdf2 lacks bounds checks.
if digest_info.supported_by_fastpbkdf2:
return _fast_pbkdf2_hmac(digest_info.name, secret, salt, rounds, keylen)
# ~1.4x faster than pure-python backend
# NOTE: have to do this after fastpbkdf2 since hashlib-ssl is slower,
# will support larger number of hashes.
if digest_info.supported_by_hashlib_pbkdf2:
return _stdlib_pbkdf2_hmac(digest_info.name, secret, salt, rounds, keylen)
#
# otherwise use our own implementation
#
# generated keyed hmac
keyed_hmac = compile_hmac(digest, secret)
# get helper to calculate pbkdf2 inner loop efficiently
calc_block = _get_pbkdf2_looper(digest_size)
# assemble & return result
return join_bytes(
calc_block(keyed_hmac, keyed_hmac(salt + _pack_uint32(i)), rounds)
for i in irange(1, block_count + 1)
)[:keylen]
#-------------------------------------------------------------------------------------
# pick best choice for pure-python helper
# TODO: consider some alternatives, such as C-accelerated xor_bytes helper if available
#-------------------------------------------------------------------------------------
# NOTE: this env var is only present to support the admin/benchmark_pbkdf2 script
_force_backend = os.environ.get("PASSLIB_PBKDF2_BACKEND") or "any"
if PY3 and _force_backend in ["any", "from-bytes"]:
from functools import partial
def _get_pbkdf2_looper(digest_size):
return partial(_pbkdf2_looper, digest_size)
def _pbkdf2_looper(digest_size, keyed_hmac, digest, rounds):
"""
py3-only implementation of pbkdf2 inner loop;
uses 'int.from_bytes' + integer XOR
"""
from_bytes = int.from_bytes
BIG = "big" # endianess doesn't matter, just has to be consistent
accum = from_bytes(digest, BIG)
for _ in irange(rounds - 1):
digest = keyed_hmac(digest)
accum ^= from_bytes(digest, BIG)
return accum.to_bytes(digest_size, BIG)
_builtin_backend = "from-bytes"
elif _force_backend in ["any", "unpack", "from-bytes"]:
from struct import Struct
from passlib.utils import sys_bits
_have_64_bit = (sys_bits >= 64)
#: cache used by _get_pbkdf2_looper
_looper_cache = {}
def _get_pbkdf2_looper(digest_size):
"""
We want a helper function which performs equivalent of the following::
def helper(keyed_hmac, digest, rounds):
accum = digest
for _ in irange(rounds - 1):
digest = keyed_hmac(digest)
accum ^= digest
return accum
However, no efficient way to implement "bytes ^ bytes" in python.
Instead, using approach where we dynamically compile a helper function based
on digest size. Instead of a single `accum` var, this helper breaks the digest
into a series of integers.
It stores these in a series of`accum_<i>` vars, and performs `accum ^= digest`
by unpacking digest and perform xor for each "accum_<i> ^= digest_<i>".
this keeps everything in locals, avoiding excessive list creation, encoding or decoding,
etc.
:param digest_size:
digest size to compile for, in bytes. (must be multiple of 4).
:return:
helper function with call signature outlined above.
"""
#
# cache helpers
#
try:
return _looper_cache[digest_size]
except KeyError:
pass
#
# figure out most efficient struct format to unpack digest into list of native ints
#
if _have_64_bit and not digest_size & 0x7:
# digest size multiple of 8, on a 64 bit system -- use array of UINT64
count = (digest_size >> 3)
fmt = "=%dQ" % count
elif not digest_size & 0x3:
if _have_64_bit:
# digest size multiple of 4, on a 64 bit system -- use array of UINT64 + 1 UINT32
count = (digest_size >> 3)
fmt = "=%dQI" % count
count += 1
else:
# digest size multiple of 4, on a 32 bit system -- use array of UINT32
count = (digest_size >> 2)
fmt = "=%dI" % count
else:
# stopping here, cause no known hashes have digest size that isn't multiple of 4 bytes.
# if needed, could go crazy w/ "H" & "B"
raise NotImplementedError("unsupported digest size: %d" % digest_size)
struct = Struct(fmt)
#
# build helper source
#
tdict = dict(
digest_size=digest_size,
accum_vars=", ".join("acc_%d" % i for i in irange(count)),
digest_vars=", ".join("dig_%d" % i for i in irange(count)),
)
# head of function
source = (
"def helper(keyed_hmac, digest, rounds):\n"
" '''pbkdf2 loop helper for digest_size={digest_size}'''\n"
" unpack_digest = struct.unpack\n"
" {accum_vars} = unpack_digest(digest)\n"
" for _ in irange(1, rounds):\n"
" digest = keyed_hmac(digest)\n"
" {digest_vars} = unpack_digest(digest)\n"
).format(**tdict)
# xor digest
for i in irange(count):
source += " acc_%d ^= dig_%d\n" % (i, i)
# return result
source += " return struct.pack({accum_vars})\n".format(**tdict)
#
# compile helper
#
code = compile(source, "<generated by passlib.crypto.digest._get_pbkdf2_looper()>", "exec")
gdict = dict(irange=irange, struct=struct)
ldict = dict()
eval(code, gdict, ldict)
helper = ldict['helper']
if __debug__:
helper.__source__ = source
#
# store in cache
#
_looper_cache[digest_size] = helper
return helper
_builtin_backend = "unpack"
else:
assert _force_backend in ["any", "hexlify"]
# XXX: older & slower approach that used int(hexlify()),
# keeping it around for a little while just for benchmarking.
from binascii import hexlify as _hexlify
from passlib.utils import int_to_bytes
def _get_pbkdf2_looper(digest_size):
return _pbkdf2_looper
def _pbkdf2_looper(keyed_hmac, digest, rounds):
hexlify = _hexlify
accum = int(hexlify(digest), 16)
for _ in irange(rounds - 1):
digest = keyed_hmac(digest)
accum ^= int(hexlify(digest), 16)
return int_to_bytes(accum, len(digest))
_builtin_backend = "hexlify"
# helper for benchmark script -- disable hashlib, fastpbkdf2 support if builtin requested
if _force_backend == _builtin_backend:
_fast_pbkdf2_hmac = _stdlib_pbkdf2_hmac = None
# expose info about what backends are active
PBKDF2_BACKENDS = [b for b in [
"fastpbkdf2" if _fast_pbkdf2_hmac else None,
"hashlib-ssl" if _stdlib_pbkdf2_hmac else None,
"builtin-" + _builtin_backend
] if b]
# *very* rough estimate of relative speed (compared to sha256 using 'unpack' backend on 64bit arch)
if "fastpbkdf2" in PBKDF2_BACKENDS:
PBKDF2_SPEED_FACTOR = 3
elif "hashlib-ssl" in PBKDF2_BACKENDS:
PBKDF2_SPEED_FACTOR = 1.4
else:
# remaining backends have *some* difference in performance, but not enough to matter
PBKDF2_SPEED_FACTOR = 1
#=============================================================================
# eof
#=============================================================================

@ -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
#==========================================================================

@ -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
#==========================================================================

@ -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
#=================================================================

@ -0,0 +1,311 @@
"""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)
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 PasswordSizeError(ValueError):
"""
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.
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"
ValueError.__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` and :exc:`ValueError`.
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`.
.. versionadded:: 1.7
"""
def __init__(self, name):
self.name = name
ValueError.__init__(self, "unknown hash algorithm: %r" % name)
#=============================================================================
# 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 ValueError("%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)
#=============================================================================
# eof
#=============================================================================

@ -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.
"""

@ -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
#=============================================================================

File diff suppressed because it is too large Load Diff

@ -0,0 +1 @@
"""passlib.handlers -- holds implementations of all passlib's builtin hash formats"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -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
#=============================================================================

@ -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:
assert hash.startswith(self.salt) and len(hash) == 13
return hash[2:]
else:
# py3's crypt.crypt() can't handle non-utf8 bytes.
# fallback to builtin alg, which is always available.
return self._calc_checksum_builtin(secret)
#---------------------------------------------------------------
# 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:
assert hash.startswith(config[:9]) and len(hash) == 20
return hash[-11:]
else:
# py3's crypt.crypt() can't handle non-utf8 bytes.
# fallback to builtin alg, which is always available.
return self._calc_checksum_builtin(secret)
#---------------------------------------------------------------
# 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
#=============================================================================

@ -0,0 +1,146 @@
"""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
#===================================================================
# 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__):
# NOTE: could set digest_name=hash.name for cpython, but not for some other platforms.
info = lookup_hash(digest)
name = "hex_" + info.name
return 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,)
))
#=============================================================================
# predefined handlers
#=============================================================================
hex_md4 = create_hex_hash("md4")
hex_md5 = create_hex_hash("md5")
hex_md5.django_name = "unsalted_md5"
hex_sha1 = create_hex_hash("sha1")
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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -0,0 +1,272 @@
"""passlib.handlers.digests - plain hash digests
"""
#=============================================================================
# imports
#=============================================================================
# core
from base64 import b64encode, b64decode
from hashlib import md5, sha1
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",
##"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
_hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={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
#=============================================================================

@ -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:
assert hash.startswith(config) and len(hash) == len(config) + 23
return hash[-22:]
else:
# py3's crypt.crypt() can't handle non-utf8 bytes.
# fallback to builtin alg, which is always available.
return self._calc_checksum_builtin(secret)
#---------------------------------------------------------------
# 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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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:
assert hash.startswith(config) and len(hash) == len(config) + 29
return hash[-28:]
else:
# py3's crypt.crypt() can't handle non-utf8 bytes.
# fallback to builtin alg, which is always available.
return self._calc_checksum_builtin(secret)
#---------------------------------------------------------------
# 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
#=============================================================================

@ -0,0 +1,519 @@
"""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):
hash = safe_crypt(secret, self.to_string())
if hash:
# NOTE: avoiding full parsing routine via from_string().checksum,
# and just extracting the bit we need.
cs = self.checksum_size
assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR
return hash[-cs:]
else:
# py3's crypt.crypt() can't handle non-utf8 bytes.
# fallback to builtin alg, which is always available.
return self._calc_checksum_builtin(secret)
#---------------------------------------------------------------
# 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.
: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.
: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 = "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.
: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.
: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 = "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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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_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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -0,0 +1,542 @@
"""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_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...
warn("crypt.crypt() function is present, but doesn't support any "
"formats known to passlib!", 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
#=============================================================================

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -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
#=============================================================================

@ -0,0 +1,444 @@
"""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',
# 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 suppress_cause(exc):
"""
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__ = None
return exc
#=============================================================================
# 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'
#=============================================================================
# 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

@ -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
#=============================================================================

@ -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
#=============================================================================

File diff suppressed because it is too large Load Diff

@ -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
#=============================================================================

@ -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
#=============================================================================

@ -0,0 +1,68 @@
"""passlib.win32 - MS Windows support - DEPRECATED, WILL BE REMOVED IN 1.8
the LMHASH and NTHASH algorithms are used in various windows related contexts,
but generally not in a manner compatible with how passlib is structured.
in particular, they have no identifying marks, both being
32 bytes of binary data. thus, they can't be easily identified
in a context with other hashes, so a CryptHandler hasn't been defined for them.
this module provided two functions to aid in any use-cases which exist.
.. warning::
these functions should not be used for new code unless an existing
system requires them, they are both known broken,
and are beyond insecure on their own.
.. autofunction:: raw_lmhash
.. autofunction:: raw_nthash
See also :mod:`passlib.hash.nthash`.
"""
from warnings import warn
warn("the 'passlib.win32' module is deprecated, and will be removed in "
"passlib 1.8; please use the 'passlib.hash.nthash' and "
"'passlib.hash.lmhash' classes instead.",
DeprecationWarning)
#=============================================================================
# imports
#=============================================================================
# core
from binascii import hexlify
# site
# pkg
from passlib.utils.compat import unicode
from passlib.crypto.des import des_encrypt_block
from passlib.hash import nthash
# local
__all__ = [
"nthash",
"raw_lmhash",
"raw_nthash",
]
#=============================================================================
# helpers
#=============================================================================
LM_MAGIC = b"KGS!@#$%"
raw_nthash = nthash.raw_nthash
def raw_lmhash(secret, encoding="ascii", hex=False):
"""encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex"""
# NOTE: various references say LMHASH uses the OEM codepage of the host
# for its encoding. until a clear reference is found,
# as well as a path for getting the encoding,
# letting this default to "ascii" to prevent incorrect hashes
# from being made w/o user explicitly choosing an encoding.
if isinstance(secret, unicode):
secret = secret.encode(encoding)
ns = secret.upper()[:14] + b"\x00" * (14-len(secret))
out = des_encrypt_block(ns[:7], LM_MAGIC) + des_encrypt_block(ns[7:], LM_MAGIC)
return hexlify(out).decode("ascii") if hex else out
#=============================================================================
# eoc
#=============================================================================

@ -29,7 +29,7 @@ except ImportError:
lmhash, nthash = None, None
from cl_print import color_print
import cl_overriding
from passlib.hash import sha256_crypt, grub_pbkdf2_sha512
from calculate.contrib.passlib.hash import sha256_crypt, grub_pbkdf2_sha512
from calculate.lib.cl_lang import setLocalTranslate

Loading…
Cancel
Save