changeset 6008:d72a5e95c7c0

upgrade bundled passlib to 1.6.2
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 05 Jan 2014 02:43:02 +0100
parents 86090e014653
children b48a69886ca4
files MoinMoin/support/passlib/__init__.py MoinMoin/support/passlib/apps.py MoinMoin/support/passlib/context.py MoinMoin/support/passlib/exc.py MoinMoin/support/passlib/ext/django/models.py MoinMoin/support/passlib/ext/django/utils.py MoinMoin/support/passlib/handlers/bcrypt.py MoinMoin/support/passlib/handlers/cisco.py MoinMoin/support/passlib/handlers/django.py MoinMoin/support/passlib/handlers/fshp.py MoinMoin/support/passlib/handlers/md5_crypt.py MoinMoin/support/passlib/handlers/pbkdf2.py MoinMoin/support/passlib/handlers/phpass.py MoinMoin/support/passlib/handlers/scram.py MoinMoin/support/passlib/handlers/sha1_crypt.py MoinMoin/support/passlib/handlers/sha2_crypt.py MoinMoin/support/passlib/handlers/sun_md5_crypt.py MoinMoin/support/passlib/registry.py MoinMoin/support/passlib/utils/__init__.py MoinMoin/support/passlib/utils/_blowfish/__init__.py MoinMoin/support/passlib/utils/_blowfish/base.py MoinMoin/support/passlib/utils/handlers.py docs/CHANGES docs/REQUIREMENTS
diffstat 24 files changed, 518 insertions(+), 165 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/passlib/__init__.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/__init__.py	Sun Jan 05 02:43:02 2014 +0100
@@ -1,3 +1,3 @@
 """passlib - suite of password hashing & generation routinges"""
 
-__version__ = '1.6.1'
+__version__ = '1.6.2'
--- a/MoinMoin/support/passlib/apps.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/apps.py	Sun Jan 05 02:43:02 2014 +0100
@@ -99,14 +99,22 @@
     deprecated=["hex_md5"],
 )
 
+_django14_schemes = ["django_pbkdf2_sha256", "django_pbkdf2_sha1",
+                     "django_bcrypt"] + _django10_schemes
 django14_context = LazyCryptContext(
-    schemes=["django_pbkdf2_sha256", "django_pbkdf2_sha1", "django_bcrypt"] \
-            + _django10_schemes,
+    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,
 )
 
 # this will always point to latest version
-django_context = django14_context
+django_context = django16_context
 
 #=============================================================================
 # ldap
--- a/MoinMoin/support/passlib/context.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/context.py	Sun Jan 05 02:43:02 2014 +0100
@@ -1653,7 +1653,7 @@
             >>> ctx2.default_scheme()
             "md5_crypt"
 
-        .. versionchanged:: 1.6
+        .. versionadded:: 1.6
             This method was previously named :meth:`!replace`. That alias
             has been deprecated, and will be removed in Passlib 1.8.
 
@@ -1767,7 +1767,7 @@
     def load_path(self, path, update=False, section="passlib", encoding="utf-8"):
         """Load new configuration into CryptContext from a local file.
 
-        This function is a wrapper for :meth:`load`, which
+        This function is a wrapper for :meth:`load` which
         loads a configuration string from the local file *path*,
         instead of an in-memory source. It's behavior and options
         are otherwise identical to :meth:`!load` when provided with
@@ -1842,7 +1842,7 @@
 
         .. note::
 
-            If an error occurs during a :meth:`!load` call, the :class`!CryptContext`
+            If an error occurs during a :meth:`!load` call, the :class:`!CryptContext`
             instance will be restored to the configuration it was in before
             the :meth:`!load` call was made; this is to ensure it is
             *never* left in an inconsistent state due to a load error.
@@ -2293,25 +2293,37 @@
 
         :type secret: unicode, bytes, or None
         :param secret:
-            Optionally, the secret associated with the hash.
-            This is not required, or in fact useful for any current purpose,
-            and can be safely omitted. It's mainly present to allow the
-            development of future deprecation checks which might need this information.
+            Optional secret associated with the provided ``hash``.
+            This is not required, or even currently used for anything...
+            it's for forward-compatibility with any future
+            update checks that might need this information.
+            If provided, Passlib assumes the secret has already been
+            verified successfully against the hash.
+
+            .. versionadded:: 1.6
 
         :returns: ``True`` if hash should be replaced, otherwise ``False``.
 
-        .. versionchanged:: 1.6
-            The *secret* argument was added, and this method was renamed
-            from the longer alias ``hash_needs_update``.
+        :raises ValueError:
+            If the hash did not match any of the configured :meth:`schemes`.
+
+        .. versionadded:: 1.6
+            This method was previously named :meth:`hash_needs_update`.
 
         .. seealso:: the :ref:`context-migration-example` example in the tutorial.
         """
         record = self._get_or_identify_record(hash, scheme, category)
         return record.needs_update(hash, secret)
 
-    @deprecated_method(deprecated="1.6", removed="1.8", replacement="CryptContext.needs_update()")
+    @deprecated_method(deprecated="1.6", removed="2.0", replacement="CryptContext.needs_update()")
     def hash_needs_update(self, hash, scheme=None, category=None):
-        """legacy alias for :meth:`needs_update`"""
+        """Legacy alias for :meth:`needs_update`.
+
+        .. deprecated:: 1.6
+            This method was renamed to :meth:`!needs_update` in version 1.6.
+            This alias will be removed in version 2.0, and should only
+            be used for compatibility with Passlib 1.3 - 1.5.
+        """
         return self.needs_update(hash, scheme, category)
 
     def genconfig(self, scheme=None, category=None, **settings):
@@ -2466,16 +2478,16 @@
 
         :param \*\*kwds:
             All other keyword options are passed to the selected algorithm's
-            :meth:`~passlib.ifc.PasswordHash.encrypt` method.
+            :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
 
         :returns:
             The secret as encoded by the specified algorithm and options.
             The return value will always be a :class:`!str`.
 
         :raises TypeError, ValueError:
-            * if any of the arguments have an invalid type or value.
-            * if the selected algorithm's underlying :meth:`~passlib.ifc.PasswordHash.encrypt`
-              method throws an error based on *secret* or the provided *kwds*.
+            * If any of the arguments have an invalid type or value.
+              This includes any keywords passed to the underlying hash's
+              :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
 
         .. seealso:: the :ref:`context-basic-example` example in the tutorial
         """
@@ -2517,14 +2529,19 @@
             and should match it's :attr:`~passlib.ifc.PasswordHash.context_kwds`.
 
         :returns:
-            ``True`` if the password matched hash, else ``False``.
-
-        :raises TypeError, ValueError:
-            * if any of the arguments have an invalid type or value.
-            * if the selected algorithm's underlying :meth:`~passlib.ifc.PasswordHash.verify`
-              method throws an error based on *secret* or the provided *kwds*.
-
-        :raises ValueError: if the hash could not be identified.
+            ``True`` if the password matched the hash, else ``False``.
+
+        :raises ValueError:
+            * if the hash did not match any of the configured :meth:`schemes`.
+
+            * if any of the arguments have an invalid value (this includes
+              any keywords passed to the underlying hash's
+              :meth:`PasswordHash.verify() <passlib.ifc.PasswordHash.verify>` method).
+
+        :raises TypeError:
+            * if any of the arguments have an invalid type (this includes
+              any keywords passed to the underlying hash's
+              :meth:`PasswordHash.verify() <passlib.ifc.PasswordHash.verify>` method).
 
         .. seealso:: the :ref:`context-basic-example` example in the tutorial
         """
@@ -2569,20 +2586,25 @@
 
         :param \*\*kwds:
             all additional keywords are passed to the appropriate handler,
-            and should match it's :attr:`context keywords <passlib.hash.PasswordHash.context_kwds>`.
+            and should match that hash's
+            :attr:`PasswordHash.context_kwds <passlib.ifc.PasswordHash.context_kwds>`.
 
         :returns:
             This function returns a tuple containing two elements:
-            the first indicates whether the password verified,
-            and the second whether the existing hash needs to be replaced.
-            The return value will always match one of the following 3 cases:
+            ``(verified, replacement_hash)``. The first is a boolean
+            flag indicating whether the password verified,
+            and the second an optional replacement hash.
+            The tuple will always match one of the following 3 cases:
 
             * ``(False, None)`` indicates the secret failed to verify.
             * ``(True, None)`` indicates the secret verified correctly,
-              and the hash does not need upgrading.
+              and the hash does not need updating.
             * ``(True, str)`` indicates the secret verified correctly,
-              and the existing hash needs to be updated. the :class:`!str`
-              will be the freshly generated hash to replace the old one with.
+              but the current hash needs to be updated. The :class:`!str`
+              will be the freshly generated hash, to replace the old one.
+
+        :raises TypeError, ValueError:
+            For the same reasons as :meth:`verify`.
 
         .. seealso:: the :ref:`context-migration-example` example in the tutorial.
         """
@@ -2643,6 +2665,8 @@
     As well, it allows constructing a context at *module-init* time,
     but using :func:`!onload()` to provide dynamic configuration
     at *application-run* time.
+
+    .. versionadded:: 1.4
     """
     _lazy_kwds = None
 
--- a/MoinMoin/support/passlib/exc.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/exc.py	Sun Jan 05 02:43:02 2014 +0100
@@ -43,7 +43,8 @@
 # warnings
 #=============================================================================
 class PasslibWarning(UserWarning):
-    """base class for Passlib's user warnings.
+    """base class for Passlib's user warnings,
+    derives from the builtin :exc:`UserWarning`.
 
     .. versionadded:: 1.6
     """
@@ -61,6 +62,8 @@
 
     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):
@@ -75,6 +78,8 @@
 
     * A malformed hash string was encountered which (while parsable)
       should be re-encoded.
+
+    .. versionadded:: 1.6
     """
 
 class PasslibRuntimeWarning(PasslibWarning):
@@ -83,11 +88,15 @@
     The fact that it's a warning instead of an error means Passlib
     was able to correct for the issue, but that it's anonmalous 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
     """
 
 #=============================================================================
--- a/MoinMoin/support/passlib/ext/django/models.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/ext/django/models.py	Sun Jan 05 02:43:02 2014 +0100
@@ -72,20 +72,39 @@
             from django.contrib.auth.models import UNUSABLE_PASSWORD
 
         def is_password_usable(encoded):
-            return (encoded is not None and encoded != UNUSABLE_PASSWORD)
+            return encoded is not None and encoded != UNUSABLE_PASSWORD
 
         def is_valid_secret(secret):
             return secret is not None
 
-    else:
+    elif VERSION < (1,6):
         has_hashers = True
         from django.contrib.auth.hashers import UNUSABLE_PASSWORD, \
                                                 is_password_usable
 
+        # NOTE: 1.4 - 1.5 - empty passwords no longer valid.
         def is_valid_secret(secret):
-            # NOTE: changed in 1.4 - empty passwords no longer valid.
             return bool(secret)
 
+    else:
+        has_hashers = True
+        from django.contrib.auth.hashers import is_password_usable
+
+        # 1.6 - empty passwords valid again
+        def is_valid_secret(secret):
+            return secret is not None
+
+    if VERSION < (1,6):
+        def make_unusable_password():
+            return UNUSABLE_PASSWORD
+    else:
+        from django.contrib.auth.hashers import make_password as _make_password
+        def make_unusable_password():
+            return _make_password(None)
+
+    # django 1.4.6+ uses a separate hasher for "sha1$$digest" hashes
+    has_unsalted_sha1 = (VERSION >= (1,4,6))
+
     #
     # backport ``User.set_unusable_password()`` for Django 0.9
     # (simplifies rest of the code)
@@ -95,7 +114,7 @@
 
         @_manager.monkeypatch(USER_PATH)
         def set_unusable_password(user):
-            user.password = UNUSABLE_PASSWORD
+            user.password = make_unusable_password()
 
         @_manager.monkeypatch(USER_PATH)
         def has_usable_password(user):
@@ -123,6 +142,8 @@
         hash = user.password
         if not is_valid_secret(password) or not is_password_usable(hash):
             return False
+        if not hash and VERSION < (1,4):
+            return False
         # NOTE: pulls _get_category from module globals
         cat = _get_category(user)
         ok, new_hash = password_context.verify_and_update(password, hash,
@@ -159,12 +180,18 @@
         def make_password(password, salt=None, hasher="default"):
             "passlib replacement for make_password()"
             if not is_valid_secret(password):
-                return UNUSABLE_PASSWORD
-            kwds = {}
-            if salt is not None:
+                return make_unusable_password()
+            if hasher == "default":
+                scheme = None
+            else:
+                scheme = hasher_to_passlib_name(hasher)
+            kwds = dict(scheme=scheme)
+            handler = password_context.handler(scheme)
+            # NOTE: django make specify an empty string for the salt,
+            #       even if scheme doesn't accept a salt. we omit keyword
+            #       in that case.
+            if salt is not None and (salt or 'salt' in handler.setting_kwds):
                 kwds['salt'] = salt
-            if hasher != "default":
-                kwds['scheme'] = hasher_to_passlib_name(hasher)
             return password_context.encrypt(password, **kwds)
 
         @_manager.monkeypatch(HASHERS_PATH)
@@ -175,18 +202,29 @@
                 scheme = None
             else:
                 scheme = hasher_to_passlib_name(algorithm)
+            # NOTE: resolving scheme -> handler instead of
+            #       passing scheme into get_passlib_hasher(),
+            #       in case context contains custom handler
+            #       shadowing name of a builtin handler.
             handler = password_context.handler(scheme)
-            return get_passlib_hasher(handler)
+            return get_passlib_hasher(handler, algorithm=algorithm)
 
-        # NOTE: custom helper that doesn't exist in django proper
-        #       (though submitted a patch - https://code.djangoproject.com/ticket/18184)
+        # identify_hasher() was added in django 1.5,
+        # patching it anyways for 1.4, so passlib's version is always available.
         @_manager.monkeypatch(HASHERS_PATH)
         @_manager.monkeypatch(FORMS_PATH)
         def identify_hasher(encoded):
             "passlib helper to identify hasher from encoded password"
             handler = password_context.identify(encoded, resolve=True,
                                                 required=True)
-            return get_passlib_hasher(handler)
+            algorithm = None
+            if (has_unsalted_sha1 and handler.name == "django_salted_sha1" and
+                    encoded.startswith("sha1$$")):
+                # django 1.4.6+ uses a separate hasher for "sha1$$digest" hashes,
+                # but passlib just reuses the "sha1$salt$digest" handler.
+                # we want to resolve to correct django hasher.
+                algorithm = "unsalted_sha1"
+            return get_passlib_hasher(handler, algorithm=algorithm)
 
     _patched = True
     log.debug("... finished monkeypatching django")
--- a/MoinMoin/support/passlib/ext/django/utils.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/ext/django/utils.py	Sun Jan 05 02:43:02 2014 +0100
@@ -28,6 +28,15 @@
 #=============================================================================
 # default policies
 #=============================================================================
+
+# map preset names -> passlib.app attrs
+_preset_map = {
+    "django-1.0": "django10_context",
+    "django-1.4": "django14_context",
+    "django-1.6": "django16_context",
+    "django-latest": "django_context",
+}
+
 def get_preset_config(name):
     """Returns configuration string for one of the preset strings
     supported by the ``PASSLIB_CONFIG`` setting.
@@ -35,36 +44,41 @@
 
     * ``"passlib-default"`` - default config used by this release of passlib.
     * ``"django-default"`` - config matching currently installed django version.
-    * ``"django-latest"`` - config matching newest django version (currently same as ``"django-1.4"``).
+    * ``"django-latest"`` - config matching newest django version (currently same as ``"django-1.6"``).
     * ``"django-1.0"`` - config used by stock Django 1.0 - 1.3 installs
-    * ``"django-1.4"`` -config used by stock Django 1.4 installs
+    * ``"django-1.4"`` - config used by stock Django 1.4 installs
+    * ``"django-1.6"`` - config used by stock Django 1.6 installs
     """
     # TODO: add preset which includes HASHERS + PREFERRED_HASHERS,
-    #       after having imported any custom hashers. "django-current"
+    #       after having imported any custom hashers. e.g. "django-current"
     if name == "django-default":
-        if (0,0) < DJANGO_VERSION < (1,4):
+        if not DJANGO_VERSION:
+            raise ValueError("can't resolve django-default preset, "
+                             "django not installed")
+        if DJANGO_VERSION < (1,4):
             name = "django-1.0"
-        else:
+        elif DJANGO_VERSION < (1,6):
             name = "django-1.4"
-    if name == "django-1.0":
-        from passlib.apps import django10_context
-        return django10_context.to_string()
-    if name == "django-1.4" or name == "django-latest":
-        from passlib.apps import django14_context
-        return django14_context.to_string()
+        else:
+            name = "django-1.6"
     if name == "passlib-default":
         return PASSLIB_DEFAULT
-    raise ValueError("unknown preset config name: %r" % name)
+    try:
+        attr = _preset_map[name]
+    except KeyError:
+        raise ValueError("unknown preset config name: %r" % name)
+    import passlib.apps
+    return getattr(passlib.apps, attr).to_string()
 
 # default context used by passlib 1.6
 PASSLIB_DEFAULT = """
 [passlib]
 
 ; list of schemes supported by configuration
-; currently all django 1.4 hashes, django 1.0 hashes,
+; currently all django 1.6, 1.4, and 1.0 hashes,
 ; and three common modular crypt format hashes.
 schemes =
-    django_pbkdf2_sha256, django_pbkdf2_sha1, django_bcrypt,
+    django_pbkdf2_sha256, django_pbkdf2_sha1, django_bcrypt, django_bcrypt_sha256,
     django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5,
     sha512_crypt, bcrypt, phpass
 
@@ -119,6 +133,10 @@
     "convert hasher name -> passlib handler name"
     if hasher_name.startswith(PASSLIB_HASHER_PREFIX):
         return hasher_name[len(PASSLIB_HASHER_PREFIX):]
+    if hasher_name == "unsalted_sha1":
+        # django 1.4.6+ uses a separate hasher for "sha1$$digest" hashes,
+        # but passlib just reuses the "sha1$salt$digest" handler.
+        hasher_name = "sha1"
     for name in list_crypt_handlers():
         if name.startswith(DJANGO_PASSLIB_PREFIX) or name in _other_django_hashes:
             handler = get_crypt_handler(name)
@@ -132,13 +150,14 @@
 #=============================================================================
 # wrapping passlib handlers as django hashers
 #=============================================================================
-_FAKE_SALT = "--fake-salt--"
+_GEN_SALT_SIGNAL = "--!!!generate-new-salt!!!--"
 
 class _HasherWrapper(object):
     """helper for wrapping passlib handlers in Hasher-compatible class."""
 
     # filled in by subclass, drives the other methods.
     passlib_handler = None
+    iterations = None
 
     @classproperty
     def algorithm(cls):
@@ -146,19 +165,22 @@
         return PASSLIB_HASHER_PREFIX + cls.passlib_handler.name
 
     def salt(self):
-        # XXX: our encode wrapper generates a new salt each time it's called,
-        #      so just returning a 'no value' flag here.
-        return _FAKE_SALT
+        # NOTE: passlib's handler.encrypt() should generate new salt each time,
+        #       so this just returns a special constant which tells
+        #       encode() (below) not to pass a salt keyword along.
+        return _GEN_SALT_SIGNAL
 
     def verify(self, password, encoded):
         return self.passlib_handler.verify(password, encoded)
 
     def encode(self, password, salt=None, iterations=None):
         kwds = {}
-        if salt is not None and salt != _FAKE_SALT:
+        if salt is not None and salt != _GEN_SALT_SIGNAL:
             kwds['salt'] = salt
         if iterations is not None:
             kwds['rounds'] = iterations
+        elif self.iterations is not None:
+            kwds['rounds'] = self.iterations
         return self.passlib_handler.encrypt(password, **kwds)
 
     _translate_kwds = dict(checksum="hash", rounds="iterations")
@@ -178,10 +200,17 @@
                 items.append((_(key), value))
         return SortedDict(items)
 
+    # added in django 1.6
+    def must_update(self, encoded):
+        # TODO: would like to do something useful here,
+        #       but would require access to password context,
+        #       which would mean a serious recoding of this ext.
+        return False
+
 # cache of hasher wrappers generated by get_passlib_hasher()
 _hasher_cache = WeakKeyDictionary()
 
-def get_passlib_hasher(handler):
+def get_passlib_hasher(handler, algorithm=None):
     """create *Hasher*-compatible wrapper for specified passlib hash.
 
     This takes in the name of a passlib hash (or the handler object itself),
@@ -204,8 +233,14 @@
         handler = get_crypt_handler(handler)
     if hasattr(handler, "django_name"):
         # return native hasher instance
-        # XXX: should cache this too.
-        return _get_hasher(handler.django_name)
+        # XXX: should add this to _hasher_cache[]
+        name = handler.django_name
+        if name == "sha1" and algorithm == "unsalted_sha1":
+            # django 1.4.6+ uses a separate hasher for "sha1$$digest" hashes,
+            # but passlib just reuses the "sha1$salt$digest" handler.
+            # we want to resolve to correct django hasher.
+            name = algorithm
+        return _get_hasher(name)
     if handler.name == "django_disabled":
         raise ValueError("can't wrap unusable-password handler")
     try:
--- a/MoinMoin/support/passlib/handlers/bcrypt.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/bcrypt.py	Sun Jan 05 02:43:02 2014 +0100
@@ -12,23 +12,25 @@
 #=============================================================================
 from __future__ import with_statement, absolute_import
 # core
+from base64 import b64encode
+from hashlib import sha256
 import os
 import re
 import logging; log = logging.getLogger(__name__)
 from warnings import warn
 # site
 try:
-    from bcrypt import hashpw as pybcrypt_hashpw
+    import bcrypt as _bcrypt
 except ImportError: # pragma: no cover
-    pybcrypt_hashpw = None
+    _bcrypt = None
 try:
     from bcryptor.engine import Engine as bcryptor_engine
 except ImportError: # pragma: no cover
     bcryptor_engine = None
 # pkg
 from passlib.exc import PasslibHashWarning
-from passlib.utils import bcrypt64, safe_crypt, repeat_string, \
-                          classproperty, rng, getrandstr, test_crypt
+from passlib.utils import bcrypt64, safe_crypt, repeat_string, to_bytes, \
+                          classproperty, rng, getrandstr, test_crypt, to_unicode
 from passlib.utils.compat import bytes, b, u, uascii_to_str, unicode, str_to_uascii
 import passlib.utils.handlers as uh
 
@@ -117,9 +119,9 @@
     checksum_chars = bcrypt64.charmap
 
     #--HasManyIdents--
-    default_ident = u("$2a$")
-    ident_values = (u("$2$"), IDENT_2A, IDENT_2X, IDENT_2Y)
-    ident_aliases = {u("2"): u("$2$"), u("2a"): IDENT_2A,  u("2y"): IDENT_2Y}
+    default_ident = IDENT_2A
+    ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y)
+    ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A,  u("2y"): IDENT_2Y}
 
     #--HasSalt--
     min_salt_size = max_salt_size = 22
@@ -128,7 +130,7 @@
 
     #--HasRounds--
     default_rounds = 12 # current passlib default
-    min_rounds = 4 # bcrypt spec specified minimum
+    min_rounds = 4 # minimum from bcrypt specification
     max_rounds = 31 # 32-bit integer limit (since real_rounds=1<<rounds)
     rounds_cost = "log2"
 
@@ -164,8 +166,13 @@
         if ident is None:
             ident = self.ident
         if ident == IDENT_2Y:
+            # none of passlib's backends suffered from crypt_blowfish's
+            # buggy "2a" hash, which means we can safely implement
+            # crypt_blowfish's "2y" hash by passing "2a" to the backends.
             ident = IDENT_2A
         else:
+            # no backends currently support 2x, but that should have
+            # been caught earlier in from_string()
             assert ident != IDENT_2X
         config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
         return uascii_to_str(config)
@@ -197,7 +204,8 @@
             return hash
 
     def _generate_salt(self, salt_size):
-        # override to correct generate salt bits
+        # generate random salt as normal,
+        # but repair last char so the padding bits always decode to zero.
         salt = super(bcrypt, self)._generate_salt(salt_size)
         return bcrypt64.repair_unused(salt)
 
@@ -231,11 +239,15 @@
     #===================================================================
     # primary interface
     #===================================================================
-    backends = ("pybcrypt", "bcryptor", "os_crypt", "builtin")
+    backends = ("bcrypt", "pybcrypt", "bcryptor", "os_crypt", "builtin")
+
+    @classproperty
+    def _has_backend_bcrypt(cls):
+        return _bcrypt is not None and hasattr(_bcrypt, "_ffi")
 
     @classproperty
     def _has_backend_pybcrypt(cls):
-        return pybcrypt_hashpw is not None
+        return _bcrypt is not None and not hasattr(_bcrypt, "_ffi")
 
     @classproperty
     def _has_backend_bcryptor(cls):
@@ -251,7 +263,8 @@
 
     @classproperty
     def _has_backend_os_crypt(cls):
-        # XXX: what to do if only h2 is supported? h1 is *very* rare.
+        # XXX: what to do if "2" isn't supported, but "2a" is?
+        #      "2" is *very* rare, and can fake it using "2a"+repeat_string
         h1 = '$2$04$......................1O4gOrCYaqBG3o/4LnT2ykQUt1wbyju'
         h2 = '$2a$04$......................qiOQjkB8hxU8OzRhS.GhRMa4VUnkPty'
         return test_crypt("test",h1) and test_crypt("test", h2)
@@ -260,6 +273,17 @@
     def _no_backends_msg(cls):
         return "no bcrypt backends available - please install py-bcrypt"
 
+    def _calc_checksum(self, secret):
+        "common backend code"
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        if _BNULL in secret:
+            # NOTE: especially important to forbid NULLs for bcrypt, since many
+            # backends (bcryptor, bcrypt) happily accept them, and then
+            # silently truncate the password at first NULL they encounter!
+            raise uh.exc.NullPasswordError(self)
+        return self._calc_checksum_backend(secret)
+
     def _calc_checksum_os_crypt(self, secret):
         config = self._get_config()
         hash = safe_crypt(secret, config)
@@ -278,17 +302,35 @@
                 "recommend installing py-bcrypt.",
                 )
 
+    def _calc_checksum_bcrypt(self, secret):
+        # bcrypt behavior:
+        #   hash must be ascii bytes
+        #   secret must be bytes
+        #   returns bytes
+        if self.ident == IDENT_2:
+            # bcrypt doesn't support $2$ hashes; but we can fake $2$ behavior
+            # using the $2a$ algorithm, by repeating the password until
+            # it's at least 72 chars in length.
+            if secret:
+                secret = repeat_string(secret, 72)
+            config = self._get_config(IDENT_2A)
+        else:
+            config = self._get_config()
+        if isinstance(config, unicode):
+            config = config.encode("ascii")
+        hash = _bcrypt.hashpw(secret, config)
+        assert hash.startswith(config) and len(hash) == len(config)+31
+        assert isinstance(hash, bytes)
+        return hash[-31:].decode("ascii")
+
     def _calc_checksum_pybcrypt(self, secret):
         # py-bcrypt behavior:
         #   py2: unicode secret/hash encoded as ascii bytes before use,
         #        bytes taken as-is; returns ascii bytes.
-        #   py3: not supported (patch submitted)
-        if isinstance(secret, unicode):
-            secret = secret.encode("utf-8")
-        if _BNULL in secret:
-            raise uh.exc.NullPasswordError(self)
+        #   py3: unicode secret encoded as utf-8 bytes,
+        #        hash encoded as ascii bytes, returns ascii unicode.
         config = self._get_config()
-        hash = pybcrypt_hashpw(secret, config)
+        hash = _bcrypt.hashpw(secret, config)
         assert hash.startswith(config) and len(hash) == len(config)+31
         return str_to_uascii(hash[-31:])
 
@@ -297,13 +339,6 @@
         #   py2: unicode secret/hash encoded as ascii bytes before use,
         #        bytes taken as-is; returns ascii bytes.
         #   py3: not supported
-        if isinstance(secret, unicode):
-            secret = secret.encode("utf-8")
-        if _BNULL in secret:
-            # NOTE: especially important to forbid NULLs for bcryptor,
-            # since it happily accepts them, and then silently truncates
-            # the password at first one it encounters :(
-            raise uh.exc.NullPasswordError(self)
         if self.ident == IDENT_2:
             # bcryptor doesn't support $2$ hashes; but we can fake $2$ behavior
             # using the $2a$ algorithm, by repeating the password until
@@ -318,10 +353,6 @@
         return str_to_uascii(hash[-31:])
 
     def _calc_checksum_builtin(self, secret):
-        if isinstance(secret, unicode):
-            secret = secret.encode("utf-8")
-        if _BNULL in secret:
-            raise uh.exc.NullPasswordError(self)
         chk = _builtin_bcrypt(secret, self.ident.strip("$"),
                               self.salt.encode("ascii"), self.rounds)
         return chk.decode("ascii")
@@ -330,6 +361,97 @@
     # eoc
     #===================================================================
 
+_UDOLLAR = u("$")
+
+class bcrypt_sha256(bcrypt):
+    """This class implements a composition of BCrypt+SHA256, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept
+    all the same optional keywords as the base :class:`bcrypt` hash.
+
+    .. versionadded:: 1.6.2
+    """
+    name = "bcrypt_sha256"
+
+    # this is locked at 2a for now.
+    ident_values = (IDENT_2A,)
+
+    # sample hash:
+    # $bcrypt-sha256$2a,6$/3OeRpbOf8/l6nPPRdZPp.$nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
+    # $bcrypt-sha256$           -- prefix/identifier
+    # 2a                        -- bcrypt variant
+    # ,                         -- field separator
+    # 6                         -- bcrypt work factor
+    # $                         -- section separator
+    # /3OeRpbOf8/l6nPPRdZPp.    -- salt
+    # $                         -- section separator
+    # nRiyYqPobEZGdNRBWihQhiFDh1ws1tu  -- digest
+
+    # XXX: we can't use .ident attr due to bcrypt code using it.
+    #      working around that via prefix.
+    prefix = u('$bcrypt-sha256$')
+
+    _hash_re = re.compile(r"""
+        ^
+        [$]bcrypt-sha256
+        [$](?P<variant>[a-z0-9]+)
+        ,(?P<rounds>\d{1,2})
+        [$](?P<salt>[^$]{22})
+        ([$](?P<digest>.{31}))?
+        $
+        """, re.X)
+
+    @classmethod
+    def identify(cls, hash):
+        hash = uh.to_unicode_for_identify(hash)
+        if not hash:
+            return False
+        return hash.startswith(cls.prefix)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        if not hash.startswith(cls.prefix):
+            raise uh.exc.InvalidHashError(cls)
+        m = cls._hash_re.match(hash)
+        if not m:
+            raise uh.exc.MalformedHashError(cls)
+        rounds = m.group("rounds")
+        if rounds.startswith(uh._UZERO) and rounds != uh._UZERO:
+            raise uh.exc.ZeroPaddedRoundsError(cls)
+        return cls(ident=m.group("variant"),
+                   rounds=int(rounds),
+                   salt=m.group("salt"),
+                   checksum=m.group("digest"),
+                   )
+
+    def to_string(self):
+        hash = u("%s%s,%d$%s") % (self.prefix, self.ident.strip(_UDOLLAR),
+                                  self.rounds, self.salt)
+        if self.checksum:
+            hash = u("%s$%s") % (hash, self.checksum)
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        # NOTE: this bypasses bcrypt's _calc_checksum,
+        #       so has to take care of all it's issues, such as secret encoding.
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        # NOTE: can't use digest directly, since bcrypt stops at first NULL.
+        # NOTE: bcrypt doesn't fully mix entropy for bytes 55-72 of password
+        #       (XXX: citation needed), so we don't want key to be > 55 bytes.
+        #       thus, have to use base64 (44 bytes) rather than hex (64 bytes).
+        key = b64encode(sha256(secret).digest())
+        return self._calc_checksum_backend(key)
+
+    # patch set_backend so it modifies bcrypt class, not this one...
+    # else it would clobber our _calc_checksum() wrapper above.
+    @classmethod
+    def set_backend(cls, *args, **kwds):
+        return bcrypt.set_backend(*args, **kwds)
+
 #=============================================================================
 # eof
 #=============================================================================
--- a/MoinMoin/support/passlib/handlers/cisco.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/cisco.py	Sun Jan 05 02:43:02 2014 +0100
@@ -61,14 +61,14 @@
 
         user = self.user
         if user:
-            # NOTE: not *positive* about this, but it looks like per-user
-            # accounts use first 4 chars of user as salt, whereas global
-            # "enable" passwords don't have any salt at all.
+            # not positive about this, but it looks like per-user
+            # accounts use the first 4 chars of the username as the salt,
+            # whereas global "enable" passwords don't have any salt at all.
             if isinstance(user, unicode):
                 user = user.encode("utf-8")
             secret += user[:4]
 
-        # pad/truncate to 16
+        # null-pad or truncate to 16 bytes
         secret = right_pad_string(secret, 16)
 
         # md5 digest
@@ -123,6 +123,8 @@
     setting_kwds = ("salt",)
     checksum_chars = uh.UPPER_HEX_CHARS
 
+    # 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
 
@@ -154,9 +156,9 @@
         self.salt = self._norm_salt(salt)
 
     def _norm_salt(self, salt):
-        # NOTE: the "salt" for this algorithm is a small integer.
+        "the salt for this algorithm is an integer 0-52, not a string"
         # XXX: not entirely sure that values >15 are valid, so for
-        # compatibility we don't output those values but we do accept them.
+        # compatibility we don't output those values, but we do accept them.
         if salt is None:
             if self.use_defaults:
                 salt = self._generate_salt()
--- a/MoinMoin/support/passlib/handlers/django.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/django.py	Sun Jan 05 02:43:02 2014 +0100
@@ -4,12 +4,14 @@
 #=============================================================================
 # core
 from base64 import b64encode
-from hashlib import md5, sha1
+from binascii import hexlify
+from hashlib import md5, sha1, sha256
 import re
 import logging; log = logging.getLogger(__name__)
 from warnings import warn
 # site
 # pkg
+from passlib.hash import bcrypt, pbkdf2_sha1, pbkdf2_sha256
 from passlib.utils import to_unicode, classproperty
 from passlib.utils.compat import b, bytes, str_to_uascii, uascii_to_str, unicode, u
 from passlib.utils.pbkdf2 import pbkdf2
@@ -28,6 +30,8 @@
 #=============================================================================
 # lazy imports & constants
 #=============================================================================
+
+# imported by django_des_crypt._calc_checksum()
 des_crypt = None
 
 def _import_des_crypt():
@@ -162,7 +166,7 @@
             secret = secret.encode("utf-8")
         return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
 
-django_bcrypt = uh.PrefixWrapper("django_bcrypt", "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::
@@ -181,6 +185,70 @@
     """)
 django_bcrypt.django_name = "bcrypt"
 
+class django_bcrypt_sha256(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:`/lib/passlib.hash.bcrypt` for more details.
+
+    .. versionadded:: 1.6.2
+    """
+    name = "django_bcrypt_sha256"
+    django_name = "bcrypt_sha256"
+    _digest = sha256
+
+    # NOTE: django bcrypt ident locked at "$2a$", so omitting 'ident' support.
+    setting_kwds = ("salt", "rounds")
+
+    # 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 __init__(self, **kwds):
+        if 'ident' in kwds and kwds.get("use_defaults"):
+            raise TypeError("%s does not support the ident keyword" %
+                            self.__class__.__name__)
+        return super(django_bcrypt_sha256, self).__init__(**kwds)
+
+    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)
+
+    # patch set_backend so it modifies bcrypt class, not this one...
+    # else it would clobber our _calc_checksum() wrapper above.
+    @classmethod
+    def set_backend(cls, *args, **kwds):
+        return bcrypt.set_backend(*args, **kwds)
+
 class django_pbkdf2_sha256(DjangoVariableHash):
     """This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
 
@@ -202,7 +270,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 10000, but must be within ``range(1,1<<32)``.
+        Defaults to 20000, but must be within ``range(1,1<<32)``.
 
     :type relaxed: bool
     :param relaxed:
@@ -224,7 +292,7 @@
     max_rounds = 0xffffffff # setting at 32-bit limit for now
     checksum_chars = uh.PADDED_BASE64_CHARS
     checksum_size = 44 # 32 bytes -> base64
-    default_rounds = 10000 # NOTE: using django default here
+    default_rounds = pbkdf2_sha256.default_rounds # NOTE: django 1.6 uses 12000
     _prf = "hmac-sha256"
 
     def _calc_checksum(self, secret):
@@ -255,7 +323,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 10000, but must be within ``range(1,1<<32)``.
+        Defaults to 60000, but must be within ``range(1,1<<32)``.
 
     :type relaxed: bool
     :param relaxed:
@@ -274,6 +342,7 @@
     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
     _prf = "hmac-sha1"
 
 #=============================================================================
@@ -311,6 +380,21 @@
     min_salt_size = default_salt_size = 2
     _stub_checksum = u('.')*11
 
+    # 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)
@@ -331,11 +415,14 @@
         return cls(salt=salt, checksum=chk)
 
     def to_string(self):
-        # NOTE: always filling in salt field, so that we're compatible
-        # with django 1.0 (which requires it)
         salt = self.salt
         chk = salt[:2] + (self.checksum or self._stub_checksum)
-        return uh.render_mc2(self.ident, salt, chk)
+        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,
@@ -352,15 +439,23 @@
     claims the special hash string ``"!"`` which Django uses
     to indicate an account's password has been disabled.
 
-    * newly encrypted passwords will hash to ``!``.
+    * newly encrypted passwords will hash to ``"!"``.
     * it rejects all passwords.
+
+    .. note::
+
+        Django 1.6 prepends a randomly generate 40-char alphanumeric string
+        to each unusuable password. This class recognizes such strings,
+        but for backwards compatibility, still returns ``"!"``.
+
+    .. versionchanged:: 1.6.2 added Django 1.6 support
     """
     name = "django_disabled"
 
     @classmethod
     def identify(cls, hash):
         hash = uh.to_unicode_for_identify(hash)
-        return hash == u("!")
+        return hash.startswith(u("!"))
 
     def _calc_checksum(self, secret):
         return u("!")
--- a/MoinMoin/support/passlib/handlers/fshp.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/fshp.py	Sun Jan 05 02:43:02 2014 +0100
@@ -40,7 +40,7 @@
 
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 50000, must be between 1 and 4294967295, inclusive.
+        Defaults to 100000, must be between 1 and 4294967295, inclusive.
 
     :param variant:
         Optionally specifies variant of FSHP to use.
@@ -79,7 +79,7 @@
     #--HasRounds--
     # FIXME: should probably use different default rounds
     # based on the variant. setting for default variant (sha256) for now.
-    default_rounds = 50000 # current passlib default, FSHP uses 4096
+    default_rounds = 100000 # 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"
--- a/MoinMoin/support/passlib/handlers/md5_crypt.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/md5_crypt.py	Sun Jan 05 02:43:02 2014 +0100
@@ -60,7 +60,7 @@
     # 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.
+    # implementation of $1$ wasn't sufficient. *nothing else* was changed.
 
     #===================================================================
     # init & validate inputs
@@ -143,8 +143,8 @@
     # 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'; determined
-    #    by the whether 'i' a multiple of 2,3, and/or 7.
+    # 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)';
@@ -152,12 +152,12 @@
     #
     # 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
-    # * runs through as many pairs of rounds as possible for remaining rounds
-    # * performs once last round if the total rounds should be odd.
+    # * 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 40,000+ times in python, resulting in ~20% increase in
+    # 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
--- a/MoinMoin/support/passlib/handlers/pbkdf2.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/pbkdf2.py	Sun Jan 05 02:43:02 2014 +0100
@@ -136,8 +136,8 @@
 # derived handlers
 #------------------------------------------------------------------------
 pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 60000, ident=u("$pbkdf2$"))
-pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32)
-pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64)
+pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 20000)
+pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 19000)
 
 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)
@@ -202,7 +202,7 @@
     max_salt_size = 1024
 
     #--HasRounds--
-    default_rounds = 60000
+    default_rounds = pbkdf2_sha1.default_rounds
     min_rounds = 1
     max_rounds = 0xffffffff # setting at 32-bit limit for now
     rounds_cost = "linear"
@@ -303,7 +303,9 @@
     salt_chars = uh.HASH64_CHARS
 
     #--HasRounds--
-    default_rounds = 60000
+    # 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"
@@ -430,7 +432,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 12000, but must be within ``range(1,1<<32)``.
+        Defaults to 19000, but must be within ``range(1,1<<32)``.
 
     :type relaxed: bool
     :param relaxed:
@@ -455,7 +457,7 @@
     min_salt_size = 0
     max_salt_size = 1024
 
-    default_rounds = 12000
+    default_rounds = pbkdf2_sha512.default_rounds
     min_rounds = 1
     max_rounds = 0xffffffff # setting at 32-bit limit for now
     rounds_cost = "linear"
--- a/MoinMoin/support/passlib/handlers/phpass.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/phpass.py	Sun Jan 05 02:43:02 2014 +0100
@@ -42,7 +42,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 16, must be between 7 and 30, inclusive.
+        Defaults to 17, 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
@@ -75,7 +75,7 @@
     salt_chars = uh.HASH64_CHARS
 
     #--HasRounds--
-    default_rounds = 16
+    default_rounds = 17
     min_rounds = 7
     max_rounds = 30
     rounds_cost = "log2"
--- a/MoinMoin/support/passlib/handlers/scram.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/scram.py	Sun Jan 05 02:43:02 2014 +0100
@@ -49,7 +49,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 6400, but must be within ``range(1,1<<32)``.
+        Defaults to 20000, but must be within ``range(1,1<<32)``.
 
     :type algs: list of strings
     :param algs:
@@ -102,7 +102,7 @@
     max_salt_size = 1024
 
     #--HasRounds--
-    default_rounds = 6400
+    default_rounds = 20000
     min_rounds = 1
     max_rounds = 2**32-1
     rounds_cost = "linear"
--- a/MoinMoin/support/passlib/handlers/sha1_crypt.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/sha1_crypt.py	Sun Jan 05 02:43:02 2014 +0100
@@ -47,7 +47,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 40000, must be between 1 and 4294967295, inclusive.
+        Defaults to 64000, must be between 1 and 4294967295, inclusive.
 
     :type relaxed: bool
     :param relaxed:
@@ -77,7 +77,7 @@
     salt_chars = uh.HASH64_CHARS
 
     #--HasRounds--
-    default_rounds = 40000 # current passlib default
+    default_rounds = 64000 # current passlib default
     min_rounds = 1 # really, this should be higher.
     max_rounds = 4294967295 # 32-bit integer limit
     rounds_cost = "linear"
--- a/MoinMoin/support/passlib/handlers/sha2_crypt.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/sha2_crypt.py	Sun Jan 05 02:43:02 2014 +0100
@@ -374,7 +374,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 80000, must be between 1000 and 999999999, inclusive.
+        Defaults to 110000, must be between 1000 and 999999999, inclusive.
 
     :type implicit_rounds: bool
     :param implicit_rounds:
@@ -401,7 +401,8 @@
     name = "sha256_crypt"
     ident = u("$5$")
     checksum_size = 43
-    default_rounds = 80000 # current passlib default
+    # NOTE: using 25/75 weighting of builtin & os_crypt backends
+    default_rounds = 110000
 
     #===================================================================
     # backends
@@ -434,7 +435,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 60000, must be between 1000 and 999999999, inclusive.
+        Defaults to 100000, must be between 1000 and 999999999, inclusive.
 
     :type implicit_rounds: bool
     :param implicit_rounds:
@@ -463,7 +464,8 @@
     ident = u("$6$")
     checksum_size = 86
     _cdb_use_512 = True
-    default_rounds = 60000 # current passlib default
+    # NOTE: using 25/75 weighting of builtin & os_crypt backends
+    default_rounds = 100000
 
     #===================================================================
     # backend
--- a/MoinMoin/support/passlib/handlers/sun_md5_crypt.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/handlers/sun_md5_crypt.py	Sun Jan 05 02:43:02 2014 +0100
@@ -193,7 +193,7 @@
     :type rounds: int
     :param rounds:
         Optional number of rounds to use.
-        Defaults to 5000, must be between 0 and 4294963199, inclusive.
+        Defaults to 5500, must be between 0 and 4294963199, inclusive.
 
     :type bare_salt: bool
     :param bare_salt:
@@ -231,7 +231,7 @@
     max_salt_size = None
     salt_chars = uh.HASH64_CHARS
 
-    default_rounds = 5000 # current passlib default
+    default_rounds = 5500 # 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?
--- a/MoinMoin/support/passlib/registry.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/registry.py	Sun Jan 05 02:43:02 2014 +0100
@@ -82,6 +82,7 @@
     apr_md5_crypt = "passlib.handlers.md5_crypt",
     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",
@@ -91,6 +92,7 @@
     crypt16 = "passlib.handlers.des_crypt",
     des_crypt = "passlib.handlers.des_crypt",
     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",
--- a/MoinMoin/support/passlib/utils/__init__.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/utils/__init__.py	Sun Jan 05 02:43:02 2014 +0100
@@ -175,7 +175,8 @@
                 warn(text, DeprecationWarning, stacklevel=2)
                 return func(*args, **kwds)
         update_wrapper(wrapper, func)
-        if updoc and (deprecated or removed) and wrapper.__doc__:
+        if updoc and (deprecated or removed) and \
+                   wrapper.__doc__ and ".. deprecated::" not in wrapper.__doc__:
             txt = deprecated or ''
             if removed or replacement:
                 txt += "\n    "
@@ -310,6 +311,7 @@
         result = 1
 
     # run constant-time string comparision
+    # TODO: use izip instead (but first verify it's faster than zip for this case)
     if is_py3_bytes:
         for l,r in zip(tmp, right):
             result |= l ^ r
@@ -330,14 +332,16 @@
     return [ elem.strip() for elem in source.split(sep) ]
 
 def saslprep(source, param="value"):
-    """Normalizes unicode string using SASLPrep stringprep profile.
+    """Normalizes unicode strings using SASLPrep stringprep profile.
 
     The SASLPrep profile is defined in :rfc:`4013`.
     It provides a uniform scheme for normalizing unicode usernames
     and passwords before performing byte-value sensitive operations
     such as hashing. Among other things, it normalizes diacritic
     representations, removes non-printing characters, and forbids
-    invalid characters such as ``\\n``.
+    invalid characters such as ``\\n``. Properly internationalized
+    applications should run user passwords through this function
+    before hashing.
 
     :arg source:
         unicode string to normalize & validate
@@ -358,6 +362,8 @@
         This function is not available under Jython,
         as the Jython stdlib is missing the :mod:`!stringprep` module
         (`Jython issue 1758320 <http://bugs.jython.org/issue1758320>`_).
+
+    .. versionadded:: 1.6
     """
     # saslprep - http://tools.ietf.org/html/rfc4013
     # stringprep - http://tools.ietf.org/html/rfc3454
@@ -414,7 +420,7 @@
     in_table_c8 = stringprep.in_table_c8
     in_table_c9 = stringprep.in_table_c9
     for c in data:
-        # check for this mapping stage should have removed
+        # check for chars mapping stage should have removed
         assert not in_table_b1(c), "failed to strip B.1 in mapping stage"
         assert not in_table_c12(c), "failed to replace C.1.2 in mapping stage"
 
@@ -1446,14 +1452,14 @@
 
 def genseed(value=None):
     "generate prng seed value from system resources"
-    # if value is rng, extract a bunch of bits from it's state
     from hashlib import sha512
-    if hasattr(value, "getrandbits"):
-        value = value.getrandbits(1<<15)
-    text = u("%s %s %s %.15f %.15f %s") % (
-        # if caller specified a seed value (e.g. current rng state), mix it in
+    text = u("%s %s %s %s %.15f %.15f %s") % (
+        # if caller specified a seed value, mix it in
         value,
 
+        # if caller's seed value was an RNG, mix in bits from it's state
+        value.getrandbits(1<<15) if hasattr(value, "getrandbits") else None,
+
         # add current process id
         # NOTE: not available in some environments, e.g. GAE
         os.getpid() if hasattr(os, "getpid") else None,
--- a/MoinMoin/support/passlib/utils/_blowfish/__init__.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/utils/_blowfish/__init__.py	Sun Jan 05 02:43:02 2014 +0100
@@ -143,18 +143,20 @@
 
     engine = BlowfishEngine()
 
-    # convert password & salt into list of 18 32-bit integers.
+    # 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
-    # NOTE: [:4] is due to salt being 16 bytes originally,
-    #       and the list needs to wrap properly
-    engine.eks_expand(pass_words, salt_words[:4])
+    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_rounds_expand0(pass_words, salt_words, rounds)
+    engine.eks_repeated_expand(pass_words, salt_words, rounds)
 
     # encipher constant data, and encode to bytes as digest.
     data = list(BCRYPT_CDATA)
--- a/MoinMoin/support/passlib/utils/_blowfish/base.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/utils/_blowfish/base.py	Sun Jan 05 02:43:02 2014 +0100
@@ -378,8 +378,8 @@
     #===================================================================
     # eks-blowfish routines
     #===================================================================
-    def eks_expand(self, key_words, salt_words):
-        "perform EKS version of Blowfish keyschedule setup"
+    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*.
 
@@ -415,7 +415,7 @@
                 box[i], box[i+1] = l,r = encipher(l,r) # next()
                 i += 2
 
-    def eks_rounds_expand0(self, key_words, salt_words, rounds):
+    def eks_repeated_expand(self, key_words, salt_words, rounds):
         "perform rounds stage of EKS keyschedule setup"
         expand = self.expand
         n = 0
--- a/MoinMoin/support/passlib/utils/handlers.py	Sun Jan 05 02:16:19 2014 +0100
+++ b/MoinMoin/support/passlib/utils/handlers.py	Sun Jan 05 02:43:02 2014 +0100
@@ -1443,19 +1443,23 @@
         elif not cls.has_backend(name):
             raise exc.MissingBackendError("%s backend not available: %r" %
                                           (cls.name, name))
-        cls._calc_checksum = getattr(cls, "_calc_checksum_" + name)
+        cls._calc_checksum_backend = getattr(cls, "_calc_checksum_" + name)
         cls._backend = name
         return name
 
-    def _calc_checksum(self, secret):
-        "stub for _calc_checksum(), default backend will be selected first time stub is called"
+    def _calc_checksum_backend(self, secret):
+        "stub for _calc_checksum_backend(), default backend will be selected first time stub is called"
         # if we got here, no backend has been loaded; so load default backend
         assert not self._backend, "set_backend() failed to replace lazy loader"
         self.set_backend()
         assert self._backend, "set_backend() failed to load a default backend"
 
         # this should now invoke the backend-specific version, so call it again.
-        return self._calc_checksum(secret)
+        return self._calc_checksum_backend(secret)
+
+    def _calc_checksum(self, secret):
+        "wrapper for backend, for common code"""
+        return self._calc_checksum_backend(secret)
 
 #=============================================================================
 # wrappers
--- a/docs/CHANGES	Sun Jan 05 02:16:19 2014 +0100
+++ b/docs/CHANGES	Sun Jan 05 02:43:02 2014 +0100
@@ -86,6 +86,8 @@
     when requested for a specific single page. As we have a link to this in
     every page's html output, this likely also lightens the load caused by bots
     and search engine crawlers.
+  * upgraded bundled passlib to 1.6.2
+
 
 Version 1.9.7:
   New features:
--- a/docs/REQUIREMENTS	Sun Jan 05 02:16:19 2014 +0100
+++ b/docs/REQUIREMENTS	Sun Jan 05 02:43:02 2014 +0100
@@ -102,7 +102,7 @@
 
 passlib (password hashing library)
 ==================================
-shipped: 1.6.1
+shipped: 1.6.2
 minimum: 1.3(?)
 
 Note: moin could work without passlib also (NOT RECOMMENDED), but would not