diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-17 23:14:51 -0400 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-17 23:14:51 -0400 |
commit | 64ab6fc89b497efa9169f11d55251e417c4db0ba (patch) | |
tree | b3f6f5dc27b87a6bc90cb3686fa98239ee8ff053 /passlib/utils | |
parent | 8eb4c4d3b58eec6802c698ddbf357b2fd243a68c (diff) | |
parent | cd029846fdc0c3d7ffc7f53caad4579e7e0e8725 (diff) | |
download | passlib-ironpython-support-dev.tar.gz |
Merge from defaultironpython-support-dev
Diffstat (limited to 'passlib/utils')
-rw-r--r-- | passlib/utils/__init__.py | 183 | ||||
-rw-r--r-- | passlib/utils/compat.py | 10 | ||||
-rw-r--r-- | passlib/utils/des.py | 353 | ||||
-rw-r--r-- | passlib/utils/handlers.py | 24 | ||||
-rw-r--r-- | passlib/utils/md4.py | 6 |
5 files changed, 364 insertions, 212 deletions
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 8eecec3..4ba4be8 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -2,6 +2,7 @@ #============================================================================= #imports #============================================================================= +from passlib.utils.compat import PYPY, JYTHON, IRONPYTHON #core from base64 import b64encode, b64decode from codecs import lookup as _lookup_codec @@ -11,17 +12,24 @@ import math import os import sys import random -from passlib.utils.compat import PYPY, IRONPYTHON, JYTHON -_ipy_missing_stringprep = False -if IRONPYTHON: +if JYTHON or IRONPYTHON: + # Jython 2.5.2 lacks stringprep module - + # see http://bugs.jython.org/issue1758320 + # + # IronPython also lacks stringprep module try: import stringprep except ImportError: - _ipy_missing_stringprep = True + stringprep = None + if JYTHON: + _stringprep_missing_reason = "not present under Jython" + else: + _stringprep_missing_reason = "not present under IronPython" else: import stringprep import time -import unicodedata +if stringprep: + import unicodedata from warnings import warn #site #pkg @@ -87,10 +95,6 @@ __all__ = [ # constants #================================================================================= -# Python VM identification -PYPY = hasattr(sys, "pypy_version_info") -JYTHON = sys.platform.startswith('java') - # bitsize of system architecture (32 or 64) sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5) @@ -134,30 +138,46 @@ class classproperty(object): "py3 compatible alias" return self.im_func -def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True): +def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True, + replacement=None, _is_method=False): """decorator to deprecate a function. :arg msg: optional msg, default chosen if omitted :kwd deprecated: release where function was first deprecated :kwd removed: release where function will be removed + :kwd replacement: name/instructions for replacement function. :kwd updoc: add notice to docstring (default ``True``) """ if msg is None: - msg = "the function %(mod)s.%(name)s() is deprecated" + 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): - final = msg % dict( + kwds = dict( mod=func.__module__, name=func.__name__, deprecated=deprecated, removed=removed, - ) + ) + if _is_method: + state = [None] + else: + state = [msg % kwds] def wrapper(*args, **kwds): - warn(final, DeprecationWarning, stacklevel=2) + text = state[0] + if text is None: + klass = args[0].__class__ + kwds.update(klass=klass.__name__, mod=klass.__module__) + text = state[0] = msg % kwds + warn(text, DeprecationWarning, stacklevel=2) return func(*args, **kwds) update_wrapper(wrapper, func) if updoc and (deprecated or removed) and wrapper.__doc__: @@ -170,50 +190,63 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True): return wrapper return build -def relocated_function(target, msg=None, name=None, deprecated=None, mod=None, - removed=None, updoc=True): - """constructor to create alias for relocated function. +def deprecated_method(msg=None, deprecated=None, removed=None, updoc=True, + replacement=None): + """decorator to deprecate a method. - :arg target: import path to target :arg msg: optional msg, default chosen if omitted :kwd deprecated: release where function was first deprecated :kwd removed: release where function will be removed + :kwd replacement: name/instructions for replacement method. :kwd updoc: add notice to docstring (default ``True``) """ - target_mod, target_name = target.rsplit(".",1) - if mod is None: - import inspect - mod = inspect.currentframe(1).f_globals["__name__"] - if not name: - name = target_name - if msg is None: - msg = ("the function %(mod)s.%(name)s() has been moved to " - "%(target_mod)s.%(target_name)s(), the old location is deprecated") - if deprecated: - msg += " as of Passlib %(deprecated)s" - if removed: - msg += ", and will be removed in Passlib %(removed)s" - msg += "." - msg %= dict( - mod=mod, - name=name, - target_mod=target_mod, - target_name=target_name, - deprecated=deprecated, - removed=removed, - ) - state = [None] - def wrapper(*args, **kwds): - warn(msg, DeprecationWarning, stacklevel=2) - func = state[0] - if func is None: - module = __import__(target_mod, fromlist=[target_name], level=0) - func = state[0] = getattr(module, target_name) - return func(*args, **kwds) - wrapper.__module__ = mod - wrapper.__name__ = name - wrapper.__doc__ = msg - return wrapper + return deprecated_function(msg, deprecated, removed, updoc, replacement, + _is_method=True) + +##def relocated_function(target, msg=None, name=None, deprecated=None, mod=None, +## removed=None, updoc=True): +## """constructor to create alias for relocated function. +## +## :arg target: import path to target +## :arg msg: optional msg, default chosen if omitted +## :kwd deprecated: release where function was first deprecated +## :kwd removed: release where function will be removed +## :kwd updoc: add notice to docstring (default ``True``) +## """ +## target_mod, target_name = target.rsplit(".",1) +## if mod is None: +## import inspect +## mod = inspect.currentframe(1).f_globals["__name__"] +## if not name: +## name = target_name +## if msg is None: +## msg = ("the function %(mod)s.%(name)s() has been moved to " +## "%(target_mod)s.%(target_name)s(), the old location is deprecated") +## if deprecated: +## msg += " as of Passlib %(deprecated)s" +## if removed: +## msg += ", and will be removed in Passlib %(removed)s" +## msg += "." +## msg %= dict( +## mod=mod, +## name=name, +## target_mod=target_mod, +## target_name=target_name, +## deprecated=deprecated, +## removed=removed, +## ) +## state = [None] +## def wrapper(*args, **kwds): +## warn(msg, DeprecationWarning, stacklevel=2) +## func = state[0] +## if func is None: +## module = __import__(target_mod, fromlist=[target_name], level=0) +## func = state[0] = getattr(module, target_name) +## return func(*args, **kwds) +## wrapper.__module__ = mod +## wrapper.__name__ = name +## wrapper.__doc__ = msg +## return wrapper class memoized_property(object): """decorator which invokes method once, then replaces attr with result""" @@ -317,7 +350,7 @@ def consteq(left, right): return result == 0 @deprecated_function(deprecated="1.6", removed="1.8") -def splitcomma(source, sep=","): +def splitcomma(source, sep=","): # pragma: no cover """split comma-separated string into list of elements, stripping whitespace and discarding empty elements. """ @@ -350,6 +383,11 @@ def saslprep(source, errname="value"): :returns: normalized unicode string + + .. note:: + + Due to a missing :mod:`!stringprep` module, this feature + is not available on Jython. """ # saslprep - http://tools.ietf.org/html/rfc4013 # stringprep - http://tools.ietf.org/html/rfc3454 @@ -439,12 +477,12 @@ def saslprep(source, errname="value"): return data -# implement stub for ironpython -if _ipy_missing_stringprep: +# replace saslprep() with stub when stringprep is missing +if stringprep is None: def saslprep(source, errname="value"): - "ironpython stub for saslprep()" - raise NotImplementedError("saslprep() requires the stdlib 'stringprep' " - "module, which is not available under IronPython") + "stub for saslprep()" + raise NotImplementedError("saslprep() support requires the 'stringprep' " + "module, which is " + _stringprep_missing_reason) #============================================================================= # bytes helpers @@ -480,7 +518,7 @@ def render_bytes(source, *args): # NOTE: deprecating bytes<->int in favor of just using struct module. @deprecated_function(deprecated="1.6", removed="1.8") -def bytes_to_int(value): +def bytes_to_int(value): # pragma: no cover "decode string of bytes as single big-endian integer" from passlib.utils.compat import byte_elem_value out = 0 @@ -489,7 +527,7 @@ def bytes_to_int(value): return out @deprecated_function(deprecated="1.6", removed="1.8") -def int_to_bytes(value, count): +def int_to_bytes(value, count): # pragma: no cover "encodes integer into single big-endian byte string" assert value < (1<<(8*count)), "value too large for %d bytes: %d" % (count, value) return join_byte_values( @@ -592,15 +630,15 @@ def to_unicode(source, source_encoding="utf-8", errname="value"): * returns unicode strings unchanged. * returns bytes strings decoded using *source_encoding* """ + assert source_encoding if isinstance(source, unicode): return source elif isinstance(source, bytes): - assert source_encoding return source.decode(source_encoding) else: raise ExpectedStringError(source, errname) -if PY3 or IRONPYTHON: +if PY3: def to_native_str(source, encoding="utf-8", errname="value"): if isinstance(source, bytes): return source.decode(encoding) @@ -639,7 +677,7 @@ add_doc(to_native_str, """) @deprecated_function(deprecated="1.6", removed="1.7") -def to_hash_str(source, encoding="ascii"): +def to_hash_str(source, encoding="ascii"): # pragma: no cover "deprecated, use to_native_str() instead" return to_native_str(source, encoding, errname="hash") @@ -713,7 +751,7 @@ class Base64Engine(object): if isinstance(charmap, unicode): charmap = charmap.encode("latin-1") elif not isinstance(charmap, bytes): - raise TypeError("charmap must be unicode/bytes string") + raise ExpectedStringError(charmap, "charmap") if len(charmap) != 64: raise ValueError("charmap must be 64 characters in length") if len(set(charmap)) != 64: @@ -1160,8 +1198,7 @@ class Base64Engine(object): :returns: a string of length ``int(ceil(bits/6.0))``. """ - if value < 0: - raise ValueError("value cannot be negative") + assert value >= 0, "caller did not sanitize input" pad = -bits % 6 bits += pad if self.big: @@ -1313,22 +1350,14 @@ else: if isinstance(secret, bytes): # Python 3's crypt() only accepts unicode, which is then # encoding using utf-8 before passing to the C-level crypt(). - # so we have to decode the secret, but also check that it - # re-encodes to the original sequence of bytes... otherwise - # the call to crypt() will digest the wrong value. + # so we have to decode the secret. orig = secret try: secret = secret.decode("utf-8") except UnicodeDecodeError: return None - if secret.encode("utf-8") != orig: - # just in case original encoding wouldn't be reproduced - # during call to os_crypt. not sure if/how this could - # happen, but being paranoid. - from passlib.exc import PasslibRuntimeWarning - warn("utf-8 password didn't re-encode correctly!", - PasslibRuntimeWarning) - return None + assert secret.encode("utf-8") == orig, \ + "utf-8 spec says this can't happen!" if _NULL in secret: raise ValueError("null character in secret") if isinstance(hash, bytes): diff --git a/passlib/utils/compat.py b/passlib/utils/compat.py index 3d7b012..5b413cd 100644 --- a/passlib/utils/compat.py +++ b/passlib/utils/compat.py @@ -85,7 +85,7 @@ __all__ = [ 'print_', # type detection - 'is_mapping', +## 'is_mapping', 'callable', 'int_types', 'num_types', @@ -289,9 +289,9 @@ else: #============================================================================= # typing #============================================================================= -def is_mapping(obj): - # non-exhaustive check, enough to distinguish from lists, etc - return hasattr(obj, "items") +##def is_mapping(obj): +## # non-exhaustive check, enough to distinguish from lists, etc +## return hasattr(obj, "items") if (3,0) <= sys.version_info < (3,2): # callable isn't dead, it's just resting @@ -454,7 +454,7 @@ class _LazyOverlayModule(ModuleType): attrs.update(self.__dict__) attrs.update(self.__attrmap) proxy = self.__proxy - if proxy: + if proxy is not None: attrs.update(dir(proxy)) return list(attrs) diff --git a/passlib/utils/des.py b/passlib/utils/des.py index 4172a2e..25f1d0d 100644 --- a/passlib/utils/des.py +++ b/passlib/utils/des.py @@ -1,4 +1,5 @@ -""" +"""passlib.utils.des -- DES block encryption routines + History ======= These routines (which have since been drastically modified for python) @@ -32,8 +33,7 @@ The copyright & license for that source is as follows:: @version $Id: UnixCrypt2.txt,v 1.1.1.1 2005/09/13 22:20:13 christos Exp $ @author Greg Wilkins (gregw) -netbsd des-crypt implementation, -which has some nice notes on how this all works - +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 """ @@ -45,7 +45,10 @@ which has some nice notes on how this all works - # core import struct # pkg -from passlib.utils.compat import bytes, join_byte_values, byte_elem_value, irange, irange +from passlib import exc +from passlib.utils.compat import bytes, join_byte_values, byte_elem_value, \ + b, irange, irange, int_types +from passlib.utils import deprecated_function # local __all__ = [ "expand_des_key", @@ -54,30 +57,29 @@ __all__ = [ ] #========================================================= -#precalculated iteration ranges & constants +# constants #========================================================= -R8 = irange(8) -RR8 = irange(7, -1, -1) -RR4 = irange(3, -1, -1) -RR12_1 = irange(11, 1, -1) -RR9_1 = irange(9,-1,-1) -RR6_S2 = irange(6, -1, -2) -RR14_S2 = irange(14, -1, -2) -R16_S2 = irange(0, 16, 2) +# masks/upper limits for various integer sizes +INT_24_MASK = 0xffffff +INT_56_MASK = 0xffffffffffffff +INT_64_MASK = 0xffffffffffffffff -INT_24_MAX = 0xffffff -INT_64_MAX = 0xffffffff -INT_64_MAX = 0xffffffffffffffff +# mask to clear parity bits from 64-bit key +_KDATA_MASK = 0xfefefefefefefefe +_KPARITY_MASK = 0x0101010101010101 -uint64_struct = struct.Struct(">Q") +# mask used to setup key schedule +_KS_MASK = 0xfcfcfcfcffffffff #========================================================= -# static tables for des +# static DES tables #========================================================= -PCXROT = IE3264 = SPE = CF6464 = None #placeholders filled in by load_tables -def load_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 @@ -558,10 +560,14 @@ def load_tables(): 0x0000000004040000, 0x0000000004040004, 0x0000000004040400, 0x0000000004040404, ), ) #========================================================= - #eof load_data + # eof _load_tables() #========================================================= -def permute(c, p): +#========================================================= +# 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 @@ -573,104 +579,213 @@ def permute(c, p): return out #========================================================= -#des frontend +# packing & unpacking +#========================================================= +_uint64_struct = struct.Struct(">Q") + +_BNULL = b('\x00') + +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(_BNULL+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 7 byte des key to 8 byte des key (by adding parity bit every 7 bits)" - if not isinstance(key, bytes): - raise TypeError("key must be bytes, not %s" % (type(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: this function 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. + ##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 - #NOTE: could probably do this much more cleverly and efficiently, - # but no need really given it's use. +#========================================================= +# des encryption +#========================================================= +def des_encrypt_block(key, input, salt=0, rounds=1): + """encrypt single block of data using DES, operates on 8-byte strings. - #NOTE: the parity bits are generally ignored, including by des_encrypt_block below - assert len(key) == 7 + :arg key: + DES key as 7 byte string, or 8 byte string with parity bits + (parity bit values are ignored). - def iter_bits(source): - for c in source: - v = byte_elem_value(c) - for i in irange(7,-1,-1): - yield (v>>i) & 1 + :arg input: + plaintext block to encrypt, as 8 byte string. - out = 0 - p = 1 - for i, b in enumerate(iter_bits(key)): - out = (out<<1) + b - p ^= b - if i % 7 == 6: - out = (out<<1) + p - p = 1 - - return join_byte_values( - ((out>>s) & 0xFF) - for s in irange(8*7,-8,-8) - ) + :arg salt: + optional 24-bit integer used to mutate the base DES algorithm in a + manner specific to :class:`~passlib.hash.des_crypt` and it's variants: + + for each bit ``i`` which is set in the salt value, + bits ``i`` and ``i+24`` are swapped in the DES E-box output. + the default (``salt=0``) provides the normal DES behavior. -def des_encrypt_block(key, input): - """do traditional encryption of a single DES block + :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 it's variants use + alternate rounds values. - :arg key: 8 byte des key - :arg input: 8 byte plaintext - :returns: 8 byte ciphertext + :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. - all values must be :class:`bytes` + :returns: + resulting 8-byte ciphertext block. """ - if not isinstance(key, bytes): - raise TypeError("key must be bytes, not %s" % (type(key),)) - if len(key) == 7: - key = expand_des_key(key) - if not isinstance(input, bytes): - raise TypeError("input must be bytes, not %s" % (type(input),)) - input = uint64_struct.unpack(input)[0] - key = uint64_struct.unpack(key)[0] - out = mdes_encrypt_int_block(key, input, 0, 1) - return uint64_struct.pack(out) - -def mdes_encrypt_int_block(key, input, salt=0, rounds=1): - """do modified multi-round DES encryption of single DES block. - - the function implements the salted, variable-round version - of DES used by :class:`~passlib.hash.des_crypt` and related variants. - it also can perform regular DES encryption - by using ``salt=0, rounds=1`` (the default values). - - :arg key: 8 byte des key as integer - :arg input: 8 byte plaintext block as integer - :arg salt: integer 24 bit salt, used to mutate output (defaults to 0) - :arg rounds: number of rounds of DES encryption to apply (defaults to 1) - - The salt is used to to mutate the normal DES encrypt operation - by swapping bits ``i`` and ``i+24`` in the DES E-Box output - if and only if bit ``i`` is set in the salt value. Thus, - if the salt is set to ``0``, normal DES encryption is performed. + # 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 block as 8 byte integer + 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() - #bounds check - assert 0 <= input <= INT_64_MAX, "input value out of range" - assert 0 <= salt <= INT_24_MAX, "salt value out of range" - assert rounds >= 0, "rounds out of range" - assert 0 <= key <= INT_64_MAX, "key value out of range" + # load SPE into local vars to speed things up and remove an array access call + SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE - #load tables if not already done - if PCXROT is None: - load_tables() + # NOTE: parity bits are ignored completely + # (UTs do fuzz testing to ensure this) - #convert key int -> key schedule - #NOTE: generation was modified to output two elements at a time, - #to optimize for per-round algorithm below. - mask = ~0x0303030300000000 - def _gen(K): + # 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: - K1 = permute(K, p_even) - K = permute(K1, p_odd) - yield K1 & mask, K & mask - ks_list = list(_gen(key)) + 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 + # expand 24 bit salt -> 32 bit per des_crypt & bsdi_crypt salt = ( ((salt & 0x00003f) << 26) | ((salt & 0x000fc0) << 12) | @@ -678,26 +793,25 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1): ((salt & 0xfc0000) >> 16) ) - #init L & R + # init L & R if input == 0: L = R = 0 else: L = ((input >> 31) & 0xaaaaaaaa) | (input & 0x55555555) - L = permute(L, IE3264) + L = _permute(L, IE3264) R = ((input >> 32) & 0xaaaaaaaa) | ((input >> 1) & 0x55555555) - R = permute(R, IE3264) - - #load SPE into local vars to speed things up and remove an array access call - SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE + R = _permute(R, IE3264) - #run specified number of passed + #------------------------------------------------------------------- + # 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 + # 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 alter specific bits + 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] ^ @@ -705,7 +819,7 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1): 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 alter specific bits + 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] ^ @@ -716,6 +830,9 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # swap L and R L, R = R, L + #------------------------------------------------------------------- + # return final result + #------------------------------------------------------------------- C = ( ((L>>3) & 0x0f0f0f0f00000000) | @@ -725,10 +842,16 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1): | ((R<<1) & 0x00000000f0f0f0f0) ) - - C = permute(C, CF6464) - - return C + return _permute(C, CF6464) + +def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover + warn("mdes_encrypt_int_block() has been deprecated as of Passlib 1.6," + "and will be removed in Passlib 1.8, use des_encrypt_int_block instead.") + if isinstance(key, bytes): + if len(key) == 7: + key = expand_des_key(key) + key = _unpack64(key) + return des_encrypt_int_block(key, input, salt, rounds) #========================================================= #eof diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index fbf7b69..eb2b7d3 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -19,11 +19,11 @@ from passlib.exc import MissingBackendError, PasslibConfigWarning, \ from passlib.registry import get_crypt_handler from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\ BASE64_CHARS, HASH64_CHARS, rng, to_native_str, \ - is_crypt_handler, deprecated_function, to_unicode, \ + is_crypt_handler, to_unicode, \ MAX_PASSWORD_SIZE from passlib.utils.compat import b, join_byte_values, bytes, irange, u, \ uascii_to_str, join_unicode, unicode, str_to_uascii, \ - join_unicode, base_string_types + join_unicode, base_string_types, PY2, int_types # local __all__ = [ # helpers for implementing MCF handlers @@ -173,7 +173,7 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10, elif rounds: rounds = int(rounds, rounds_base) elif default_rounds is None: - raise exc.MalformedHashError(handler, "missing rounds field") + raise exc.MalformedHashError(handler, "empty rounds field") else: rounds = default_rounds @@ -411,15 +411,15 @@ class GenericHandler(object): # NOTE: no clear route to reasonbly convert unicode -> raw bytes, # so relaxed does nothing here if not isinstance(checksum, bytes): - raise TypeError("checksum must be byte string") + raise exc.ExpectedTypeError(checksum, "bytes", "checksum") elif not isinstance(checksum, unicode): - if self.relaxed: + if isinstance(checksum, bytes) and self.relaxed: warn("checksum should be unicode, not bytes", PasslibHashWarning) checksum = checksum.decode("ascii") else: - raise TypeError("checksum must be unicode string") + raise exc.ExpectedTypeError(checksum, "unicode", "checksum") # handle stub if checksum == self._stub_checksum: @@ -960,14 +960,14 @@ class HasSalt(GenericHandler): # check type if self._salt_is_bytes: if not isinstance(salt, bytes): - raise TypeError("salt must be specified as bytes") + raise exc.ExpectedTypeError(salt, "bytes", "salt") else: if not isinstance(salt, unicode): - # XXX: should we disallow bytes here? - if isinstance(salt, bytes): + # NOTE: allowing bytes under py2 so salt can be native str. + if isinstance(salt, bytes) and (PY2 or self.relaxed): salt = salt.decode("ascii") else: - raise TypeError("salt must be specified as unicode") + raise exc.ExpectedTypeError(salt, "unicode", "salt") # check charset sc = self.salt_chars @@ -1138,8 +1138,8 @@ class HasRounds(GenericHandler): % (self.name,)) # check type - if not isinstance(rounds, int): - raise TypeError("rounds must be an integer") + if not isinstance(rounds, int_types): + raise exc.ExpectedTypeError(rounds, "integer", "rounds") # check bounds mn = self.min_rounds diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py index cd3d012..4389a09 100644 --- a/passlib/utils/md4.py +++ b/passlib/utils/md4.py @@ -242,14 +242,14 @@ def _has_native_md4(): try: h = hashlib.new("md4") except ValueError: - #not supported + # not supported - ssl probably missing (e.g. ironpython) return False result = h.hexdigest() if result == '31d6cfe0d16ae931b73c59d7e0c089c0': return True if PYPY and result == '': - #as of 1.5, pypy md4 just returns null! - #since this is expected, don't bother w/ warning. + # as of pypy 1.5-1.7, this returns empty string! + # since this is expected, don't bother w/ warning. return False #anything else should alert user from passlib.exc import PasslibRuntimeWarning |