summaryrefslogtreecommitdiff
path: root/passlib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/utils')
-rw-r--r--passlib/utils/__init__.py183
-rw-r--r--passlib/utils/compat.py10
-rw-r--r--passlib/utils/des.py353
-rw-r--r--passlib/utils/handlers.py24
-rw-r--r--passlib/utils/md4.py6
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