changeset 5919:efd7c0be3339

added passlib 1.6.1 to MoinMoin/support/ removed passlib's unit tests (so our test runner does not run them also)
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Fri, 18 Jan 2013 01:38:07 +0100
parents 5126fadbf24f
children fe7003b1cc4d
files MoinMoin/support/passlib/__init__.py MoinMoin/support/passlib/_setup/__init__.py MoinMoin/support/passlib/_setup/docdist.py MoinMoin/support/passlib/_setup/stamp.py MoinMoin/support/passlib/apache.py MoinMoin/support/passlib/apps.py MoinMoin/support/passlib/context.py MoinMoin/support/passlib/exc.py MoinMoin/support/passlib/ext/__init__.py MoinMoin/support/passlib/ext/django/__init__.py MoinMoin/support/passlib/ext/django/models.py MoinMoin/support/passlib/ext/django/utils.py MoinMoin/support/passlib/handlers/__init__.py MoinMoin/support/passlib/handlers/bcrypt.py MoinMoin/support/passlib/handlers/cisco.py MoinMoin/support/passlib/handlers/des_crypt.py MoinMoin/support/passlib/handlers/digests.py MoinMoin/support/passlib/handlers/django.py MoinMoin/support/passlib/handlers/fshp.py MoinMoin/support/passlib/handlers/ldap_digests.py MoinMoin/support/passlib/handlers/md5_crypt.py MoinMoin/support/passlib/handlers/misc.py MoinMoin/support/passlib/handlers/mssql.py MoinMoin/support/passlib/handlers/mysql.py MoinMoin/support/passlib/handlers/oracle.py MoinMoin/support/passlib/handlers/pbkdf2.py MoinMoin/support/passlib/handlers/phpass.py MoinMoin/support/passlib/handlers/postgres.py MoinMoin/support/passlib/handlers/roundup.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/handlers/windows.py MoinMoin/support/passlib/hash.py MoinMoin/support/passlib/hosts.py MoinMoin/support/passlib/ifc.py MoinMoin/support/passlib/registry.py MoinMoin/support/passlib/utils/__init__.py MoinMoin/support/passlib/utils/_blowfish/__init__.py MoinMoin/support/passlib/utils/_blowfish/_gen_files.py MoinMoin/support/passlib/utils/_blowfish/base.py MoinMoin/support/passlib/utils/_blowfish/unrolled.py MoinMoin/support/passlib/utils/compat.py MoinMoin/support/passlib/utils/des.py MoinMoin/support/passlib/utils/handlers.py MoinMoin/support/passlib/utils/md4.py MoinMoin/support/passlib/utils/pbkdf2.py MoinMoin/support/passlib/win32.py
diffstat 49 files changed, 18428 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/__init__.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,3 @@
+"""passlib - suite of password hashing & generation routinges"""
+
+__version__ = '1.6.1'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/_setup/__init__.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,1 @@
+"""passlib.setup - helpers used by passlib's setup.py script"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/_setup/docdist.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,87 @@
+"custom command to build doc.zip file"
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import os
+from distutils import dir_util
+from distutils.cmd import Command
+from distutils.errors import *
+from distutils.spawn import spawn
+# local
+__all__ = [
+    "docdist"
+]
+#=============================================================================
+# command
+#=============================================================================
+class docdist(Command):
+
+    description = "create zip file containing standalone html docs"
+
+    user_options = [
+        ('build-dir=', None, 'Build directory'),
+        ('dist-dir=', 'd',
+         "directory to put the source distribution archive(s) in "
+         "[default: dist]"),
+        ('format=', 'f',
+         "archive format to create (tar, ztar, gztar, zip)"),
+        ('sign', 's', 'sign files using gpg'),
+        ('identity=', 'i', 'GPG identity used to sign files'),
+    ]
+
+    def initialize_options(self):
+        self.build_dir = None
+        self.dist_dir = None
+        self.format = None
+        self.keep_temp = False
+        self.sign = False
+        self.identity = None
+
+    def finalize_options(self):
+        if self.identity and not self.sign:
+            raise DistutilsOptionError(
+                "Must use --sign for --identity to have meaning"
+            )
+        if self.build_dir is None:
+            cmd = self.get_finalized_command('build')
+            self.build_dir = os.path.join(cmd.build_base, 'docdist')
+        if not self.dist_dir:
+            self.dist_dir = "dist"
+        if not self.format:
+            self.format = "zip"
+
+    def run(self):
+        # call build sphinx to build docs
+        self.run_command("build_sphinx")
+        cmd = self.get_finalized_command("build_sphinx")
+        source_dir = cmd.builder_target_dir
+
+        # copy to directory with appropriate name
+        dist = self.distribution
+        arc_name = "%s-docs-%s" % (dist.get_name(), dist.get_version())
+        tmp_dir = os.path.join(self.build_dir, arc_name)
+        if os.path.exists(tmp_dir):
+            dir_util.remove_tree(tmp_dir, dry_run=self.dry_run)
+        self.copy_tree(source_dir, tmp_dir, preserve_symlinks=True)
+
+        # make archive from dir
+        arc_base = os.path.join(self.dist_dir, arc_name)
+        self.arc_filename = self.make_archive(arc_base, self.format,
+                                              self.build_dir)
+
+        # Sign if requested
+        if self.sign:
+            gpg_args = ["gpg", "--detach-sign", "-a", self.arc_filename]
+            if self.identity:
+                gpg_args[2:2] = ["--local-user", self.identity]
+            spawn(gpg_args,
+                  dry_run=self.dry_run)
+
+        # cleanup
+        if not self.keep_temp:
+            dir_util.remove_tree(tmp_dir, dry_run=self.dry_run)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/_setup/stamp.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,57 @@
+"update version string during build"
+#=============================================================================
+# imports
+#=============================================================================
+from __future__ import with_statement
+# core
+import os
+import re
+import time
+from distutils.dist import Distribution
+# pkg
+# local
+__all__ = [
+    "stamp_source",
+    "stamp_distutils_output",
+]
+#=============================================================================
+# helpers
+#=============================================================================
+def get_command_class(opts, name):
+    return opts['cmdclass'].get(name) or Distribution().get_command_class(name)
+
+def stamp_source(base_dir, version, dry_run=False):
+    "update version string in passlib dist"
+    path = os.path.join(base_dir, "passlib", "__init__.py")
+    with open(path) as fh:
+        input = fh.read()
+    output, count = re.subn('(?m)^__version__\s*=.*$',
+                    '__version__ = ' + repr(version),
+                    input)
+    assert count == 1, "failed to replace version string"
+    if not dry_run:
+        os.unlink(path) # sdist likes to use hardlinks
+        with open(path, "w") as fh:
+            fh.write(output)
+
+def stamp_distutils_output(opts, version):
+
+    # subclass buildpy to update version string in source
+    _build_py = get_command_class(opts, "build_py")
+    class build_py(_build_py):
+        def build_packages(self):
+            _build_py.build_packages(self)
+            stamp_source(self.build_lib, version, self.dry_run)
+    opts['cmdclass']['build_py'] = build_py
+
+    # subclass sdist to do same thing
+    _sdist = get_command_class(opts, "sdist")
+    class sdist(_sdist):
+        def make_release_tree(self, base_dir, files):
+            _sdist.make_release_tree(self, base_dir, files)
+            stamp_source(base_dir, version, self.dry_run)
+    opts['cmdclass']['sdist'] = sdist
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/apache.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,1037 @@
+"""passlib.apache - apache password support"""
+# XXX: relocate this to passlib.ext.apache?
+#=============================================================================
+# imports
+#=============================================================================
+from __future__ import with_statement
+# core
+from hashlib import md5
+import logging; log = logging.getLogger(__name__)
+import os
+import sys
+from warnings import warn
+# site
+# pkg
+from passlib.context import CryptContext
+from passlib.exc import ExpectedStringError
+from passlib.hash import htdigest
+from passlib.utils import consteq, render_bytes, to_bytes, deprecated_method, is_ascii_codec
+from passlib.utils.compat import b, bytes, join_bytes, str_to_bascii, u, \
+                                 unicode, BytesIO, iteritems, imap, PY3
+# local
+__all__ = [
+    'HtpasswdFile',
+    'HtdigestFile',
+]
+
+#=============================================================================
+# constants & support
+#=============================================================================
+_UNSET = object()
+
+_BCOLON = b(":")
+
+# byte values that aren't allowed in fields.
+_INVALID_FIELD_CHARS = b(":\n\r\t\x00")
+
+#=============================================================================
+# backport of OrderedDict for PY2.5
+#=============================================================================
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python 2.5
+    class OrderedDict(dict):
+        """hacked OrderedDict replacement.
+
+        NOTE: this doesn't provide a full OrderedDict implementation,
+        just the minimum needed by the Htpasswd internals.
+        """
+        def __init__(self):
+            self._keys = []
+
+        def __iter__(self):
+            return iter(self._keys)
+
+        def __setitem__(self, key, value):
+            if key not in self:
+                self._keys.append(key)
+            super(OrderedDict, self).__setitem__(key, value)
+
+        def __delitem__(self, key):
+            super(OrderedDict, self).__delitem__(key)
+            self._keys.remove(key)
+
+        def iteritems(self):
+            return ((key, self[key]) for key in self)
+
+        # these aren't used or implemented, so disabling them for safety.
+        update = pop = popitem = clear = keys = iterkeys = None
+
+#=============================================================================
+# common helpers
+#=============================================================================
+class _CommonFile(object):
+    """common framework for HtpasswdFile & HtdigestFile"""
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # charset encoding used by file (defaults to utf-8)
+    encoding = None
+
+    # whether users() and other public methods should return unicode or bytes?
+    # (defaults to False under PY2, True under PY3)
+    return_unicode = None
+
+    # if bound to local file, these will be set.
+    _path = None # local file path
+    _mtime = None # mtime when last loaded, or 0
+
+    # if true, automatically save to local file after changes are made.
+    autosave = False
+
+    # ordered dict mapping key -> value for all records in database.
+    # (e.g. user => hash for Htpasswd)
+    _records = None
+
+    #===================================================================
+    # alt constuctors
+    #===================================================================
+    @classmethod
+    def from_string(cls, data, **kwds):
+        """create new object from raw string.
+
+        :type data: unicode or bytes
+        :arg data:
+            database to load, as single string.
+
+        :param \*\*kwds:
+            all other keywords are the same as in the class constructor
+        """
+        if 'path' in kwds:
+            raise TypeError("'path' not accepted by from_string()")
+        self = cls(**kwds)
+        self.load_string(data)
+        return self
+
+    @classmethod
+    def from_path(cls, path, **kwds):
+        """create new object from file, without binding object to file.
+
+        :type path: str
+        :arg path:
+            local filepath to load from
+
+        :param \*\*kwds:
+            all other keywords are the same as in the class constructor
+        """
+        self = cls(**kwds)
+        self.load(path)
+        return self
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, path=None, new=False, autoload=True, autosave=False,
+                 encoding="utf-8", return_unicode=PY3,
+                 ):
+        # set encoding
+        if not encoding:
+            warn("``encoding=None`` is deprecated as of Passlib 1.6, "
+                 "and will cause a ValueError in Passlib 1.8, "
+                 "use ``return_unicode=False`` instead.",
+                 DeprecationWarning, stacklevel=2)
+            encoding = "utf-8"
+            return_unicode = False
+        elif not is_ascii_codec(encoding):
+            # htpasswd/htdigest files assumes 1-byte chars, and use ":" separator,
+            # so only ascii-compatible encodings are allowed.
+            raise ValueError("encoding must be 7-bit ascii compatible")
+        self.encoding = encoding
+
+        # set other attrs
+        self.return_unicode = return_unicode
+        self.autosave = autosave
+        self._path = path
+        self._mtime = 0
+
+        # init db
+        if not autoload:
+            warn("``autoload=False`` is deprecated as of Passlib 1.6, "
+                 "and will be removed in Passlib 1.8, use ``new=True`` instead",
+                 DeprecationWarning, stacklevel=2)
+            new = True
+        if path and not new:
+            self.load()
+        else:
+            self._records = OrderedDict()
+
+    def __repr__(self):
+        tail = ''
+        if self.autosave:
+            tail += ' autosave=True'
+        if self._path:
+            tail += ' path=%r' % self._path
+        if self.encoding != "utf-8":
+            tail += ' encoding=%r' % self.encoding
+        return "<%s 0x%0x%s>" % (self.__class__.__name__, id(self), tail)
+
+    # NOTE: ``path`` is a property so that ``_mtime`` is wiped when it's set.
+    def _get_path(self):
+        return self._path
+    def _set_path(self, value):
+        if value != self._path:
+            self._mtime = 0
+        self._path = value
+    path = property(_get_path, _set_path)
+
+    @property
+    def mtime(self):
+        "modify time when last loaded (if bound to a local file)"
+        return self._mtime
+
+    #===================================================================
+    # loading
+    #===================================================================
+    def load_if_changed(self):
+        """Reload from ``self.path`` only if file has changed since last load"""
+        if not self._path:
+            raise RuntimeError("%r is not bound to a local file" % self)
+        if self._mtime and self._mtime == os.path.getmtime(self._path):
+            return False
+        self.load()
+        return True
+
+    def load(self, path=None, force=True):
+        """Load state from local file.
+        If no path is specified, attempts to load from ``self.path``.
+
+        :type path: str
+        :arg path: local file to load from
+
+        :type force: bool
+        :param force:
+            if ``force=False``, only load from ``self.path`` if file
+            has changed since last load.
+
+            .. deprecated:: 1.6
+                This keyword will be removed in Passlib 1.8;
+                Applications should use :meth:`load_if_changed` instead.
+        """
+        if path is not None:
+            with open(path, "rb") as fh:
+                self._mtime = 0
+                self._load_lines(fh)
+        elif not force:
+            warn("%(name)s.load(force=False) is deprecated as of Passlib 1.6,"
+                 "and will be removed in Passlib 1.8; "
+                 "use %(name)s.load_if_changed() instead." %
+                 dict(name=self.__class__.__name__),
+                 DeprecationWarning, stacklevel=2)
+            return self.load_if_changed()
+        elif self._path:
+            with open(self._path, "rb") as fh:
+                self._mtime = os.path.getmtime(self._path)
+                self._load_lines(fh)
+        else:
+            raise RuntimeError("%s().path is not set, an explicit path is required" %
+                               self.__class__.__name__)
+        return True
+
+    def load_string(self, data):
+        "Load state from unicode or bytes string, replacing current state"
+        data = to_bytes(data, self.encoding, "data")
+        self._mtime = 0
+        self._load_lines(BytesIO(data))
+
+    def _load_lines(self, lines):
+        "load from sequence of lists"
+        # XXX: found reference that "#" comment lines may be supported by
+        #      htpasswd, should verify this, and figure out how to handle them.
+        #      if true, this would also affect what can be stored in user field.
+        # XXX: if multiple entries for a key, should we use the first one
+        #      or the last one? going w/ first entry for now.
+        # XXX: how should this behave if parsing fails? currently
+        #      it will contain everything that was loaded up to error.
+        #      could clear / restore old state instead.
+        parse = self._parse_record
+        records = self._records = OrderedDict()
+        for idx, line in enumerate(lines):
+            key, value = parse(line, idx+1)
+            if key not in records:
+                records[key] = value
+
+    def _parse_record(cls, record, lineno): # pragma: no cover - abstract method
+        "parse line of file into (key, value) pair"
+        raise NotImplementedError("should be implemented in subclass")
+
+    #===================================================================
+    # saving
+    #===================================================================
+    def _autosave(self):
+        "subclass helper to call save() after any changes"
+        if self.autosave and self._path:
+            self.save()
+
+    def save(self, path=None):
+        """Save current state to file.
+        If no path is specified, attempts to save to ``self.path``.
+        """
+        if path is not None:
+            with open(path, "wb") as fh:
+                fh.writelines(self._iter_lines())
+        elif self._path:
+            self.save(self._path)
+            self._mtime = os.path.getmtime(self._path)
+        else:
+            raise RuntimeError("%s().path is not set, cannot autosave" %
+                               self.__class__.__name__)
+
+    def to_string(self):
+        "Export current state as a string of bytes"
+        return join_bytes(self._iter_lines())
+
+    def _iter_lines(self):
+        "iterator yielding lines of database"
+        return (self._render_record(key,value) for key,value in iteritems(self._records))
+
+    def _render_record(cls, key, value): # pragma: no cover - abstract method
+        "given key/value pair, encode as line of file"
+        raise NotImplementedError("should be implemented in subclass")
+
+    #===================================================================
+    # field encoding
+    #===================================================================
+    def _encode_user(self, user):
+        "user-specific wrapper for _encode_field()"
+        return self._encode_field(user, "user")
+
+    def _encode_realm(self, realm): # pragma: no cover - abstract method
+        "realm-specific wrapper for _encode_field()"
+        return self._encode_field(realm, "realm")
+
+    def _encode_field(self, value, param="field"):
+        """convert field to internal representation.
+
+        internal representation is always bytes. byte strings are left as-is,
+        unicode strings encoding using file's default encoding (or ``utf-8``
+        if no encoding has been specified).
+
+        :raises UnicodeEncodeError:
+            if unicode value cannot be encoded using default encoding.
+
+        :raises ValueError:
+            if resulting byte string contains a forbidden character,
+            or is too long (>255 bytes).
+
+        :returns:
+            encoded identifer as bytes
+        """
+        if isinstance(value, unicode):
+            value = value.encode(self.encoding)
+        elif not isinstance(value, bytes):
+            raise ExpectedStringError(value, param)
+        if len(value) > 255:
+            raise ValueError("%s must be at most 255 characters: %r" %
+                             (param, value))
+        if any(c in _INVALID_FIELD_CHARS for c in value):
+            raise ValueError("%s contains invalid characters: %r" %
+                             (param, value,))
+        return value
+
+    def _decode_field(self, value):
+        """decode field from internal representation to format
+        returns by users() method, etc.
+
+        :raises UnicodeDecodeError:
+            if unicode value cannot be decoded using default encoding.
+            (usually indicates wrong encoding set for file).
+
+        :returns:
+            field as unicode or bytes, as appropriate.
+        """
+        assert isinstance(value, bytes), "expected value to be bytes"
+        if self.return_unicode:
+            return value.decode(self.encoding)
+        else:
+            return value
+
+    # FIXME: htpasswd doc says passwords limited to 255 chars under Windows & MPE,
+    # and that longer ones are truncated. this may be side-effect of those
+    # platforms supporting the 'plaintext' scheme. these classes don't currently
+    # check for this.
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# htpasswd editing
+#=============================================================================
+
+# FIXME: apr_md5_crypt technically the default only for windows, netware and tpf.
+# TODO: find out if htpasswd's "crypt" mode is a crypt() *call* or just des_crypt implementation.
+#       if the former, we can support anything supported by passlib.hosts.host_context,
+#       allowing more secure hashes than apr_md5_crypt to be used.
+#       could perhaps add this behavior as an option to the constructor.
+#       c.f. http://httpd.apache.org/docs/2.2/programs/htpasswd.html
+htpasswd_context = CryptContext([
+    "apr_md5_crypt", # man page notes supported everywhere, default on Windows, Netware, TPF
+    "des_crypt", # man page notes server does NOT support this on Windows, Netware, TPF
+    "ldap_sha1", # man page notes only for transitioning <-> ldap
+    "plaintext" # man page notes server ONLY supports this on Windows, Netware, TPF
+    ])
+
+class HtpasswdFile(_CommonFile):
+    """class for reading & writing Htpasswd files.
+
+    The class constructor accepts the following arguments:
+
+    :type path: filepath
+    :param path:
+
+        Specifies path to htpasswd file, use to implicitly load from and save to.
+
+        This class has two modes of operation:
+
+        1. It can be "bound" to a local file by passing a ``path`` to the class
+           constructor. In this case it will load the contents of the file when
+           created, and the :meth:`load` and :meth:`save` methods will automatically
+           load from and save to that file if they are called without arguments.
+
+        2. Alternately, it can exist as an independant object, in which case
+           :meth:`load` and :meth:`save` will require an explicit path to be
+           provided whenever they are called. As well, ``autosave`` behavior
+           will not be available.
+
+           This feature is new in Passlib 1.6, and is the default if no
+           ``path`` value is provided to the constructor.
+
+        This is also exposed as a readonly instance attribute.
+
+    :type new: bool
+    :param new:
+
+        Normally, if *path* is specified, :class:`HtpasswdFile` will
+        immediately load the contents of the file. However, when creating
+        a new htpasswd file, applications can set ``new=True`` so that
+        the existing file (if any) will not be loaded.
+
+        .. versionadded:: 1.6
+            This feature was previously enabled by setting ``autoload=False``.
+            That alias has been deprecated, and will be removed in Passlib 1.8
+
+    :type autosave: bool
+    :param autosave:
+
+        Normally, any changes made to an :class:`HtpasswdFile` instance
+        will not be saved until :meth:`save` is explicitly called. However,
+        if ``autosave=True`` is specified, any changes made will be
+        saved to disk immediately (assuming *path* has been set).
+
+        This is also exposed as a writeable instance attribute.
+
+    :type encoding: str
+    :param encoding:
+
+        Optionally specify character encoding used to read/write file
+        and hash passwords. Defaults to ``utf-8``, though ``latin-1``
+        is the only other commonly encountered encoding.
+
+        This is also exposed as a readonly instance attribute.
+
+    :type default_scheme: str
+    :param default_scheme:
+        Optionally specify default scheme to use when encoding new passwords.
+        Must be one of ``"apr_md5_crypt"``, ``"des_crypt"``, ``"ldap_sha1"``,
+        ``"plaintext"``. It defaults to ``"apr_md5_crypt"``.
+
+        .. versionadded:: 1.6
+            This keyword was previously named ``default``. That alias
+            has been deprecated, and will be removed in Passlib 1.8.
+
+    :type context: :class:`~passlib.context.CryptContext`
+    :param context:
+        :class:`!CryptContext` instance used to encrypt
+        and verify the hashes found in the htpasswd file.
+        The default value is a pre-built context which supports all
+        of the hashes officially allowed in an htpasswd file.
+
+        This is also exposed as a readonly instance attribute.
+
+        .. warning::
+
+            This option may be used to add support for non-standard hash
+            formats to an htpasswd file. However, the resulting file
+            will probably not be usuable by another application,
+            and particularly not by Apache.
+
+    :param autoload:
+        Set to ``False`` to prevent the constructor from automatically
+        loaded the file from disk.
+
+        .. deprecated:: 1.6
+            This has been replaced by the *new* keyword.
+            Instead of setting ``autoload=False``, you should use
+            ``new=True``. Support for this keyword will be removed
+            in Passlib 1.8.
+
+    :param default:
+        Change the default algorithm used to encrypt new passwords.
+
+        .. deprecated:: 1.6
+            This has been renamed to *default_scheme* for clarity.
+            Support for this alias will be removed in Passlib 1.8.
+
+    Loading & Saving
+    ================
+    .. automethod:: load
+    .. automethod:: load_if_changed
+    .. automethod:: load_string
+    .. automethod:: save
+    .. automethod:: to_string
+
+    Inspection
+    ================
+    .. automethod:: users
+    .. automethod:: check_password
+    .. automethod:: get_hash
+
+    Modification
+    ================
+    .. automethod:: set_password
+    .. automethod:: delete
+
+    Alternate Constructors
+    ======================
+    .. automethod:: from_string
+
+    Attributes
+    ==========
+    .. attribute:: path
+
+        Path to local file that will be used as the default
+        for all :meth:`load` and :meth:`save` operations.
+        May be written to, initialized by the *path* constructor keyword.
+
+    .. attribute:: autosave
+
+        Writeable flag indicating whether changes will be automatically
+        written to *path*.
+
+    Errors
+    ======
+    :raises ValueError:
+        All of the methods in this class will raise a :exc:`ValueError` if
+        any user name contains a forbidden character (one of ``:\\r\\n\\t\\x00``),
+        or is longer than 255 characters.
+    """
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # NOTE: _records map stores <user> for the key, and <hash> for the value,
+    # both in bytes which use self.encoding
+
+    #===================================================================
+    # init & serialization
+    #===================================================================
+    def __init__(self, path=None, default_scheme=None, context=htpasswd_context,
+                 **kwds):
+        if 'default' in kwds:
+            warn("``default`` is deprecated as of Passlib 1.6, "
+                 "and will be removed in Passlib 1.8, it has been renamed "
+                 "to ``default_scheem``.",
+                 DeprecationWarning, stacklevel=2)
+            default_scheme = kwds.pop("default")
+        if default_scheme:
+            context = context.copy(default=default_scheme)
+        self.context = context
+        super(HtpasswdFile, self).__init__(path, **kwds)
+
+    def _parse_record(self, record, lineno):
+        # NOTE: should return (user, hash) tuple
+        result = record.rstrip().split(_BCOLON)
+        if len(result) != 2:
+            raise ValueError("malformed htpasswd file (error reading line %d)"
+                             % lineno)
+        return result
+
+    def _render_record(self, user, hash):
+        return render_bytes("%s:%s\n", user, hash)
+
+    #===================================================================
+    # public methods
+    #===================================================================
+
+    def users(self):
+        "Return list of all users in database"
+        return [self._decode_field(user) for user in self._records]
+
+    ##def has_user(self, user):
+    ##    "check whether entry is present for user"
+    ##    return self._encode_user(user) in self._records
+
+    ##def rename(self, old, new):
+    ##    """rename user account"""
+    ##    old = self._encode_user(old)
+    ##    new = self._encode_user(new)
+    ##    hash = self._records.pop(old)
+    ##    self._records[new] = hash
+    ##    self._autosave()
+
+    def set_password(self, user, password):
+        """Set password for user; adds user if needed.
+
+        :returns:
+            * ``True`` if existing user was updated.
+            * ``False`` if user account was added.
+
+        .. versionchanged:: 1.6
+            This method was previously called ``update``, it was renamed
+            to prevent ambiguity with the dictionary method.
+            The old alias is deprecated, and will be removed in Passlib 1.8.
+        """
+        user = self._encode_user(user)
+        hash = self.context.encrypt(password)
+        if PY3:
+            hash = hash.encode(self.encoding)
+        existing = (user in self._records)
+        self._records[user] = hash
+        self._autosave()
+        return existing
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="set_password")
+    def update(self, user, password):
+        "set password for user"
+        return self.set_password(user, password)
+
+    def get_hash(self, user):
+        """Return hash stored for user, or ``None`` if user not found.
+
+        .. versionchanged:: 1.6
+            This method was previously named ``find``, it was renamed
+            for clarity. The old name is deprecated, and will be removed
+            in Passlib 1.8.
+        """
+        try:
+            return self._records[self._encode_user(user)]
+        except KeyError:
+            return None
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="get_hash")
+    def find(self, user):
+        "return hash for user"
+        return self.get_hash(user)
+
+    # XXX: rename to something more explicit, like delete_user()?
+    def delete(self, user):
+        """Delete user's entry.
+
+        :returns:
+            * ``True`` if user deleted.
+            * ``False`` if user not found.
+        """
+        try:
+            del self._records[self._encode_user(user)]
+        except KeyError:
+            return False
+        self._autosave()
+        return True
+
+    def check_password(self, user, password):
+        """Verify password for specified user.
+
+        :returns:
+            * ``None`` if user not found.
+            * ``False`` if user found, but password does not match.
+            * ``True`` if user found and password matches.
+
+        .. versionchanged:: 1.6
+            This method was previously called ``verify``, it was renamed
+            to prevent ambiguity with the :class:`!CryptContext` method.
+            The old alias is deprecated, and will be removed in Passlib 1.8.
+        """
+        user = self._encode_user(user)
+        hash = self._records.get(user)
+        if hash is None:
+            return None
+        if isinstance(password, unicode):
+            # NOTE: encoding password to match file, making the assumption
+            # that server will use same encoding to hash the password.
+            password = password.encode(self.encoding)
+        ok, new_hash = self.context.verify_and_update(password, hash)
+        if ok and new_hash is not None:
+            # rehash user's password if old hash was deprecated
+            self._records[user] = new_hash
+            self._autosave()
+        return ok
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="check_password")
+    def verify(self, user, password):
+        "verify password for user"
+        return self.check_password(user, password)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# htdigest editing
+#=============================================================================
+class HtdigestFile(_CommonFile):
+    """class for reading & writing Htdigest files.
+
+    The class constructor accepts the following arguments:
+
+    :type path: filepath
+    :param path:
+
+        Specifies path to htdigest file, use to implicitly load from and save to.
+
+        This class has two modes of operation:
+
+        1. It can be "bound" to a local file by passing a ``path`` to the class
+           constructor. In this case it will load the contents of the file when
+           created, and the :meth:`load` and :meth:`save` methods will automatically
+           load from and save to that file if they are called without arguments.
+
+        2. Alternately, it can exist as an independant object, in which case
+           :meth:`load` and :meth:`save` will require an explicit path to be
+           provided whenever they are called. As well, ``autosave`` behavior
+           will not be available.
+
+           This feature is new in Passlib 1.6, and is the default if no
+           ``path`` value is provided to the constructor.
+
+        This is also exposed as a readonly instance attribute.
+
+    :type default_realm: str
+    :param default_realm:
+
+        If ``default_realm`` is set, all the :class:`HtdigestFile`
+        methods that require a realm will use this value if one is not
+        provided explicitly. If unset, they will raise an error stating
+        that an explicit realm is required.
+
+        This is also exposed as a writeable instance attribute.
+
+        .. versionadded:: 1.6
+
+    :type new: bool
+    :param new:
+
+        Normally, if *path* is specified, :class:`HtdigestFile` will
+        immediately load the contents of the file. However, when creating
+        a new htpasswd file, applications can set ``new=True`` so that
+        the existing file (if any) will not be loaded.
+
+        .. versionadded:: 1.6
+            This feature was previously enabled by setting ``autoload=False``.
+            That alias has been deprecated, and will be removed in Passlib 1.8
+
+    :type autosave: bool
+    :param autosave:
+
+        Normally, any changes made to an :class:`HtdigestFile` instance
+        will not be saved until :meth:`save` is explicitly called. However,
+        if ``autosave=True`` is specified, any changes made will be
+        saved to disk immediately (assuming *path* has been set).
+
+        This is also exposed as a writeable instance attribute.
+
+    :type encoding: str
+    :param encoding:
+
+        Optionally specify character encoding used to read/write file
+        and hash passwords. Defaults to ``utf-8``, though ``latin-1``
+        is the only other commonly encountered encoding.
+
+        This is also exposed as a readonly instance attribute.
+
+    :param autoload:
+        Set to ``False`` to prevent the constructor from automatically
+        loaded the file from disk.
+
+        .. deprecated:: 1.6
+            This has been replaced by the *new* keyword.
+            Instead of setting ``autoload=False``, you should use
+            ``new=True``. Support for this keyword will be removed
+            in Passlib 1.8.
+
+    Loading & Saving
+    ================
+    .. automethod:: load
+    .. automethod:: load_if_changed
+    .. automethod:: load_string
+    .. automethod:: save
+    .. automethod:: to_string
+
+    Inspection
+    ==========
+    .. automethod:: realms
+    .. automethod:: users
+    .. automethod:: check_password(user[, realm], password)
+    .. automethod:: get_hash
+
+    Modification
+    ============
+    .. automethod:: set_password(user[, realm], password)
+    .. automethod:: delete
+    .. automethod:: delete_realm
+
+    Alternate Constructors
+    ======================
+    .. automethod:: from_string
+
+    Attributes
+    ==========
+    .. attribute:: default_realm
+
+        The default realm that will be used if one is not provided
+        to methods that require it. By default this is ``None``,
+        in which case an explicit realm must be provided for every
+        method call. Can be written to.
+
+    .. attribute:: path
+
+        Path to local file that will be used as the default
+        for all :meth:`load` and :meth:`save` operations.
+        May be written to, initialized by the *path* constructor keyword.
+
+    .. attribute:: autosave
+
+        Writeable flag indicating whether changes will be automatically
+        written to *path*.
+
+    Errors
+    ======
+    :raises ValueError:
+        All of the methods in this class will raise a :exc:`ValueError` if
+        any user name or realm contains a forbidden character (one of ``:\\r\\n\\t\\x00``),
+        or is longer than 255 characters.
+    """
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # NOTE: _records map stores (<user>,<realm>) for the key,
+    # and <hash> as the value, all as <self.encoding> bytes.
+
+    # NOTE: unlike htpasswd, this class doesn't use a CryptContext,
+    # as only one hash format is supported: htdigest.
+
+    # optionally specify default realm that will be used if none
+    # is provided to a method call. otherwise realm is always required.
+    default_realm = None
+
+    #===================================================================
+    # init & serialization
+    #===================================================================
+    def __init__(self, path=None, default_realm=None, **kwds):
+        self.default_realm = default_realm
+        super(HtdigestFile, self).__init__(path, **kwds)
+
+    def _parse_record(self, record, lineno):
+        result = record.rstrip().split(_BCOLON)
+        if len(result) != 3:
+            raise ValueError("malformed htdigest file (error reading line %d)"
+                             % lineno)
+        user, realm, hash = result
+        return (user, realm), hash
+
+    def _render_record(self, key, hash):
+        user, realm = key
+        return render_bytes("%s:%s:%s\n", user, realm, hash)
+
+    def _encode_realm(self, realm):
+        # override default _encode_realm to fill in default realm field
+        if realm is None:
+            realm = self.default_realm
+            if realm is None:
+                raise TypeError("you must specify a realm explicitly, "
+                                  "or set the default_realm attribute")
+        return self._encode_field(realm, "realm")
+
+    #===================================================================
+    # public methods
+    #===================================================================
+
+    def realms(self):
+        """Return list of all realms in database"""
+        realms = set(key[1] for key in self._records)
+        return [self._decode_field(realm) for realm in realms]
+
+    def users(self, realm=None):
+        """Return list of all users in specified realm.
+
+        * uses ``self.default_realm`` if no realm explicitly provided.
+        * returns empty list if realm not found.
+        """
+        realm = self._encode_realm(realm)
+        return [self._decode_field(key[0]) for key in self._records
+                if key[1] == realm]
+
+    ##def has_user(self, user, realm=None):
+    ##    "check if user+realm combination exists"
+    ##    user = self._encode_user(user)
+    ##    realm = self._encode_realm(realm)
+    ##    return (user,realm) in self._records
+
+    ##def rename_realm(self, old, new):
+    ##    """rename all accounts in realm"""
+    ##    old = self._encode_realm(old)
+    ##    new = self._encode_realm(new)
+    ##    keys = [key for key in self._records if key[1] == old]
+    ##    for key in keys:
+    ##        hash = self._records.pop(key)
+    ##        self._records[key[0],new] = hash
+    ##    self._autosave()
+    ##    return len(keys)
+
+    ##def rename(self, old, new, realm=None):
+    ##    """rename user account"""
+    ##    old = self._encode_user(old)
+    ##    new = self._encode_user(new)
+    ##    realm = self._encode_realm(realm)
+    ##    hash = self._records.pop((old,realm))
+    ##    self._records[new,realm] = hash
+    ##    self._autosave()
+
+    def set_password(self, user, realm=None, password=_UNSET):
+        """Set password for user; adds user & realm if needed.
+
+        If ``self.default_realm`` has been set, this may be called
+        with the syntax ``set_password(user, password)``,
+        otherwise it must be called with all three arguments:
+        ``set_password(user, realm, password)``.
+
+        :returns:
+            * ``True`` if existing user was updated
+            * ``False`` if user account added.
+        """
+        if password is _UNSET:
+            # called w/ two args - (user, password), use default realm
+            realm, password = None, realm
+        user = self._encode_user(user)
+        realm = self._encode_realm(realm)
+        key = (user, realm)
+        existing = (key in self._records)
+        hash = htdigest.encrypt(password, user, realm, encoding=self.encoding)
+        if PY3:
+            hash = hash.encode(self.encoding)
+        self._records[key] = hash
+        self._autosave()
+        return existing
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="set_password")
+    def update(self, user, realm, password):
+        "set password for user"
+        return self.set_password(user, realm, password)
+
+    # XXX: rename to something more explicit, like get_hash()?
+    def get_hash(self, user, realm=None):
+        """Return :class:`~passlib.hash.htdigest` hash stored for user.
+
+        * uses ``self.default_realm`` if no realm explicitly provided.
+        * returns ``None`` if user or realm not found.
+
+        .. versionchanged:: 1.6
+            This method was previously named ``find``, it was renamed
+            for clarity. The old name is deprecated, and will be removed
+            in Passlib 1.8.
+        """
+        key = (self._encode_user(user), self._encode_realm(realm))
+        hash = self._records.get(key)
+        if hash is None:
+            return None
+        if PY3:
+            hash = hash.decode(self.encoding)
+        return hash
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="get_hash")
+    def find(self, user, realm):
+        "return hash for user"
+        return self.get_hash(user, realm)
+
+    # XXX: rename to something more explicit, like delete_user()?
+    def delete(self, user, realm=None):
+        """Delete user's entry for specified realm.
+
+        if realm is not specified, uses ``self.default_realm``.
+
+        :returns:
+            * ``True`` if user deleted,
+            * ``False`` if user not found in realm.
+        """
+        key = (self._encode_user(user), self._encode_realm(realm))
+        try:
+            del self._records[key]
+        except KeyError:
+            return False
+        self._autosave()
+        return True
+
+    def delete_realm(self, realm):
+        """Delete all users for specified realm.
+
+        if realm is not specified, uses ``self.default_realm``.
+
+        :returns: number of users deleted (0 if realm not found)
+        """
+        realm = self._encode_realm(realm)
+        records = self._records
+        keys = [key for key in records if key[1] == realm]
+        for key in keys:
+            del records[key]
+        self._autosave()
+        return len(keys)
+
+    def check_password(self, user, realm=None, password=_UNSET):
+        """Verify password for specified user + realm.
+
+        If ``self.default_realm`` has been set, this may be called
+        with the syntax ``check_password(user, password)``,
+        otherwise it must be called with all three arguments:
+        ``check_password(user, realm, password)``.
+
+        :returns:
+            * ``None`` if user or realm not found.
+            * ``False`` if user found, but password does not match.
+            * ``True`` if user found and password matches.
+
+        .. versionchanged:: 1.6
+            This method was previously called ``verify``, it was renamed
+            to prevent ambiguity with the :class:`!CryptContext` method.
+            The old alias is deprecated, and will be removed in Passlib 1.8.
+        """
+        if password is _UNSET:
+            # called w/ two args - (user, password), use default realm
+            realm, password = None, realm
+        user = self._encode_user(user)
+        realm = self._encode_realm(realm)
+        hash = self._records.get((user,realm))
+        if hash is None:
+            return None
+        return htdigest.verify(password, hash, user, realm,
+                               encoding=self.encoding)
+
+    @deprecated_method(deprecated="1.6", removed="1.8",
+                       replacement="check_password")
+    def verify(self, user, realm, password):
+        "verify password for user"
+        return self.check_password(user, realm, password)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/apps.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,184 @@
+"""passlib.apps"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import logging; log = logging.getLogger(__name__)
+from itertools import chain
+# site
+# pkg
+from passlib import hash
+from passlib.context import LazyCryptContext
+from passlib.utils import sys_bits
+# local
+__all__ = [
+    'custom_app_context',
+    'django_context',
+    'ldap_context', 'ldap_nocrypt_context',
+    'mysql_context', 'mysql4_context', 'mysql3_context',
+    'phpass_context',
+    'phpbb3_context',
+    'postgres_context',
+]
+
+#=============================================================================
+# master containing all identifiable hashes
+#=============================================================================
+def _load_master_config():
+    from passlib.registry import list_crypt_handlers
+
+    # get master list
+    schemes = list_crypt_handlers()
+
+    # exclude the ones we know have ambiguous or greedy identify() methods.
+    excluded = [
+        # frequently confused for eachother
+        'bigcrypt',
+        'crypt16',
+
+        # no good identifiers
+        'cisco_pix',
+        'cisco_type7',
+        'htdigest',
+        'mysql323',
+        'oracle10',
+
+        # all have same size
+        'lmhash',
+        'msdcc',
+        'msdcc2',
+        'nthash',
+
+        # plaintext handlers
+        'plaintext',
+        'ldap_plaintext',
+
+        # disabled handlers
+        'django_disabled',
+        'unix_disabled',
+        'unix_fallback',
+    ]
+    for name in excluded:
+        schemes.remove(name)
+
+    # return config
+    return dict(schemes=schemes, default="sha256_crypt")
+master_context = LazyCryptContext(onload=_load_master_config)
+
+#=============================================================================
+# for quickly bootstrapping new custom applications
+#=============================================================================
+custom_app_context = LazyCryptContext(
+    # choose some reasonbly strong schemes
+    schemes=["sha512_crypt", "sha256_crypt"],
+
+    # set some useful global options
+    default="sha256_crypt" if sys_bits < 64 else "sha512_crypt",
+    all__vary_rounds = 0.1,
+
+    # set a good starting point for rounds selection
+    sha512_crypt__min_rounds = 60000,
+    sha256_crypt__min_rounds = 80000,
+
+    # if the admin user category is selected, make a much stronger hash,
+    admin__sha512_crypt__min_rounds = 120000,
+    admin__sha256_crypt__min_rounds = 160000,
+    )
+
+#=============================================================================
+# django
+#=============================================================================
+_django10_schemes = [
+        "django_salted_sha1", "django_salted_md5", "django_des_crypt",
+        "hex_md5", "django_disabled",
+]
+
+django10_context = LazyCryptContext(
+    schemes=_django10_schemes,
+    default="django_salted_sha1",
+    deprecated=["hex_md5"],
+)
+
+django14_context = LazyCryptContext(
+    schemes=["django_pbkdf2_sha256", "django_pbkdf2_sha1", "django_bcrypt"] \
+            + _django10_schemes,
+    deprecated=_django10_schemes,
+)
+
+# this will always point to latest version
+django_context = django14_context
+
+#=============================================================================
+# ldap
+#=============================================================================
+std_ldap_schemes = ["ldap_salted_sha1", "ldap_salted_md5",
+                      "ldap_sha1", "ldap_md5",
+                      "ldap_plaintext" ]
+
+# create context with all std ldap schemes EXCEPT crypt
+ldap_nocrypt_context = LazyCryptContext(std_ldap_schemes)
+
+# create context with all possible std ldap + ldap crypt schemes
+def _iter_ldap_crypt_schemes():
+    from passlib.utils import unix_crypt_schemes
+    return ('ldap_' + name for name in unix_crypt_schemes)
+
+def _iter_ldap_schemes():
+    "helper which iterates over supported std ldap schemes"
+    return chain(std_ldap_schemes, _iter_ldap_crypt_schemes())
+ldap_context = LazyCryptContext(_iter_ldap_schemes())
+
+### create context with all std ldap schemes + crypt schemes for localhost
+##def _iter_host_ldap_schemes():
+##    "helper which iterates over supported std ldap schemes"
+##    from passlib.handlers.ldap_digests import get_host_ldap_crypt_schemes
+##    return chain(std_ldap_schemes, get_host_ldap_crypt_schemes())
+##ldap_host_context = LazyCryptContext(_iter_host_ldap_schemes())
+
+#=============================================================================
+# mysql
+#=============================================================================
+mysql3_context = LazyCryptContext(["mysql323"])
+mysql4_context = LazyCryptContext(["mysql41", "mysql323"], deprecated="mysql323")
+mysql_context = mysql4_context # tracks latest mysql version supported
+
+#=============================================================================
+# postgres
+#=============================================================================
+postgres_context = LazyCryptContext(["postgres_md5"])
+
+#=============================================================================
+# phpass & variants
+#=============================================================================
+def _create_phpass_policy(**kwds):
+    "helper to choose default alg based on bcrypt availability"
+    kwds['default'] = 'bcrypt' if hash.bcrypt.has_backend() else 'phpass'
+    return kwds
+
+phpass_context = LazyCryptContext(
+    schemes=["bcrypt", "phpass", "bsdi_crypt"],
+    onload=_create_phpass_policy,
+    )
+
+phpbb3_context = LazyCryptContext(["phpass"], phpass__ident="H")
+
+# TODO: support the drupal phpass variants (see phpass homepage)
+
+#=============================================================================
+# roundup
+#=============================================================================
+
+_std_roundup_schemes = [ "ldap_hex_sha1", "ldap_hex_md5", "ldap_des_crypt", "roundup_plaintext" ]
+roundup10_context = LazyCryptContext(_std_roundup_schemes)
+
+# NOTE: 'roundup15' really applies to roundup 1.4.17+
+roundup_context = roundup15_context = LazyCryptContext(
+    schemes=_std_roundup_schemes + [ "ldap_pbkdf2_sha1" ],
+    deprecated=_std_roundup_schemes,
+    default = "ldap_pbkdf2_sha1",
+    ldap_pbkdf2_sha1__default_rounds = 10000,
+    )
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/context.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,2687 @@
+"""passlib.context - CryptContext implementation"""
+#=============================================================================
+# imports
+#=============================================================================
+from __future__ import with_statement
+# core
+from functools import update_wrapper
+import inspect
+import re
+import hashlib
+from math import log as logb, ceil
+import logging; log = logging.getLogger(__name__)
+import os
+import re
+from time import sleep
+from warnings import warn
+# site
+# pkg
+from passlib.exc import PasslibConfigWarning, ExpectedStringError, ExpectedTypeError
+from passlib.registry import get_crypt_handler, _validate_handler_name
+from passlib.utils import rng, tick, to_bytes, deprecated_method, \
+                          to_unicode, splitcomma
+from passlib.utils.compat import bytes, iteritems, num_types, \
+                                 PY2, PY3, PY_MIN_32, unicode, SafeConfigParser, \
+                                 NativeStringIO, BytesIO, base_string_types
+# local
+__all__ = [
+    'CryptContext',
+    'LazyCryptContext',
+    'CryptPolicy',
+]
+
+#=============================================================================
+# support
+#=============================================================================
+
+# private object to detect unset params
+_UNSET = object()
+
+# TODO: merge the following helpers into _CryptConfig
+
+def _coerce_vary_rounds(value):
+    "parse vary_rounds string to percent as [0,1) float, or integer"
+    if value.endswith("%"):
+        # XXX: deprecate this in favor of raw float?
+        return float(value.rstrip("%"))*.01
+    try:
+        return int(value)
+    except ValueError:
+        return float(value)
+
+# set of options which aren't allowed to be set via policy
+_forbidden_scheme_options = set(["salt"])
+    # 'salt' - not allowed since a fixed salt would defeat the purpose.
+
+# dict containing funcs used to coerce strings to correct type
+# for scheme option keys.
+_coerce_scheme_options = dict(
+    min_rounds=int,
+    max_rounds=int,
+    default_rounds=int,
+    vary_rounds=_coerce_vary_rounds,
+    salt_size=int,
+)
+
+def _is_handler_registered(handler):
+    """detect if handler is registered or a custom handler"""
+    return get_crypt_handler(handler.name, None) is handler
+
+#=============================================================================
+# crypt policy
+#=============================================================================
+_preamble = ("The CryptPolicy class has been deprecated as of "
+             "Passlib 1.6, and will be removed in Passlib 1.8. ")
+
+class CryptPolicy(object):
+    """
+    .. deprecated:: 1.6
+        This class has been deprecated, and will be removed in Passlib 1.8.
+        All of it's functionality has been rolled into :class:`CryptContext`.
+
+    This class previously stored the configuration options for the
+    CryptContext class. In the interest of interface simplification,
+    all of this class' functionality has been rolled into the CryptContext
+    class itself.
+    The documentation for this class is now focused on  documenting how to
+    migrate to the new api. Additionally, where possible, the deprecation
+    warnings issued by the CryptPolicy methods will list the replacement call
+    that should be used.
+
+    Constructors
+    ============
+    CryptPolicy objects can be constructed directly using any of
+    the keywords accepted by :class:`CryptContext`. Direct uses of the
+    :class:`!CryptPolicy` constructor should either pass the keywords
+    directly into the CryptContext constructor, or to :meth:`CryptContext.update`
+    if the policy object was being used to update an existing context object.
+
+    In addition to passing in keywords directly,
+    CryptPolicy objects can be constructed by the following methods:
+
+    .. automethod:: from_path
+    .. automethod:: from_string
+    .. automethod:: from_source
+    .. automethod:: from_sources
+    .. automethod:: replace
+
+    Introspection
+    =============
+    All of the informational methods provided by this class have been deprecated
+    by identical or similar methods in the :class:`CryptContext` class:
+
+    .. automethod:: has_schemes
+    .. automethod:: schemes
+    .. automethod:: iter_handlers
+    .. automethod:: get_handler
+    .. automethod:: get_options
+    .. automethod:: handler_is_deprecated
+    .. automethod:: get_min_verify_time
+
+    Exporting
+    =========
+    .. automethod:: iter_config
+    .. automethod:: to_dict
+    .. automethod:: to_file
+    .. automethod:: to_string
+
+    .. note::
+        CryptPolicy are immutable.
+        Use the :meth:`replace` method to mutate existing instances.
+
+    .. deprecated:: 1.6
+    """
+    #===================================================================
+    # class methods
+    #===================================================================
+    @classmethod
+    def from_path(cls, path, section="passlib", encoding="utf-8"):
+        """create a CryptPolicy instance from a local file.
+
+        .. deprecated:: 1.6
+
+        Creating a new CryptContext from a file, which was previously done via
+        ``CryptContext(policy=CryptPolicy.from_path(path))``, can now be
+        done via ``CryptContext.from_path(path)``.
+        See :meth:`CryptContext.from_path` for details.
+
+        Updating an existing CryptContext from a file, which was previously done
+        ``context.policy = CryptPolicy.from_path(path)``, can now be
+        done via ``context.load_path(path)``.
+        See :meth:`CryptContext.load_path` for details.
+        """
+        warn(_preamble +
+             "Instead of ``CryptPolicy.from_path(path)``, "
+             "use ``CryptContext.from_path(path)`` "
+             " or ``context.load_path(path)`` for an existing CryptContext.",
+             DeprecationWarning, stacklevel=2)
+        return cls(_internal_context=CryptContext.from_path(path, section,
+                                                            encoding))
+
+    @classmethod
+    def from_string(cls, source, section="passlib", encoding="utf-8"):
+        """create a CryptPolicy instance from a string.
+
+        .. deprecated:: 1.6
+
+        Creating a new CryptContext from a string, which was previously done via
+        ``CryptContext(policy=CryptPolicy.from_string(data))``, can now be
+        done via ``CryptContext.from_string(data)``.
+        See :meth:`CryptContext.from_string` for details.
+
+        Updating an existing CryptContext from a string, which was previously done
+        ``context.policy = CryptPolicy.from_string(data)``, can now be
+        done via ``context.load(data)``.
+        See :meth:`CryptContext.load` for details.
+        """
+        warn(_preamble +
+             "Instead of ``CryptPolicy.from_string(source)``, "
+             "use ``CryptContext.from_string(source)`` or "
+             "``context.load(source)`` for an existing CryptContext.",
+             DeprecationWarning, stacklevel=2)
+        return cls(_internal_context=CryptContext.from_string(source, section,
+                                                              encoding))
+
+    @classmethod
+    def from_source(cls, source, _warn=True):
+        """create a CryptPolicy instance from some source.
+
+        this method autodetects the source type, and invokes
+        the appropriate constructor automatically. it attempts
+        to detect whether the source is a configuration string, a filepath,
+        a dictionary, or an existing CryptPolicy instance.
+
+        .. deprecated:: 1.6
+
+        Create a new CryptContext, which could previously be done via
+        ``CryptContext(policy=CryptPolicy.from_source(source))``, should
+        now be done using an explicit method: the :class:`CryptContext`
+        constructor itself, :meth:`CryptContext.from_path`,
+        or :meth:`CryptContext.from_string`.
+
+        Updating an existing CryptContext, which could previously be done via
+        ``context.policy = CryptPolicy.from_source(source)``, should
+        now be done using an explicit method: :meth:`CryptContext.update`,
+        or :meth:`CryptContext.load`.
+        """
+        if _warn:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy.from_source()``, "
+                 "use ``CryptContext.from_string(path)`` "
+                 " or ``CryptContext.from_path(source)``, as appropriate.",
+                 DeprecationWarning, stacklevel=2)
+        if isinstance(source, CryptPolicy):
+            return source
+        elif isinstance(source, dict):
+            return cls(_internal_context=CryptContext(**source))
+        elif not isinstance(source, (bytes,unicode)):
+            raise TypeError("source must be CryptPolicy, dict, config string, "
+                            "or file path: %r" % (type(source),))
+        elif any(c in source for c in "\n\r\t") or not source.strip(" \t./\;:"):
+            return cls(_internal_context=CryptContext.from_string(source))
+        else:
+            return cls(_internal_context=CryptContext.from_path(source))
+
+    @classmethod
+    def from_sources(cls, sources, _warn=True):
+        """create a CryptPolicy instance by merging multiple sources.
+
+        each source is interpreted as by :meth:`from_source`,
+        and the results are merged together.
+
+        .. deprecated:: 1.6
+            Instead of using this method to merge multiple policies together,
+            a :class:`CryptContext` instance should be created, and then
+            the multiple sources merged together via :meth:`CryptContext.load`.
+        """
+        if _warn:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy.from_sources()``, "
+                 "use the various CryptContext constructors "
+                 " followed by ``context.update()``.",
+                 DeprecationWarning, stacklevel=2)
+        if len(sources) == 0:
+            raise ValueError("no sources specified")
+        if len(sources) == 1:
+            return cls.from_source(sources[0], _warn=False)
+        kwds = {}
+        for source in sources:
+            kwds.update(cls.from_source(source, _warn=False)._context.to_dict(resolve=True))
+        return cls(_internal_context=CryptContext(**kwds))
+
+    def replace(self, *args, **kwds):
+        """create a new CryptPolicy, optionally updating parts of the
+        existing configuration.
+
+        .. deprecated:: 1.6
+            Callers of this method should :meth:`CryptContext.update` or
+            :meth:`CryptContext.copy` instead.
+        """
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "Instead of ``context.policy.replace()``, "
+                 "use ``context.update()`` or ``context.copy()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().replace()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.update()`` or ``context.copy()``.",
+                 DeprecationWarning, stacklevel=2)
+        sources = [ self ]
+        if args:
+            sources.extend(args)
+        if kwds:
+            sources.append(kwds)
+        return CryptPolicy.from_sources(sources, _warn=False)
+
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # internal CryptContext we're wrapping to handle everything
+    # until this class is removed.
+    _context = None
+
+    # flag indicating this is wrapper generated by the CryptContext.policy
+    # attribute, rather than one created independantly by the application.
+    _stub_policy = False
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, *args, **kwds):
+        context = kwds.pop("_internal_context", None)
+        if context:
+            assert isinstance(context, CryptContext)
+            self._context = context
+            self._stub_policy = kwds.pop("_stub_policy", False)
+            assert not (args or kwds), "unexpected args: %r %r" % (args,kwds)
+        else:
+            if args:
+                if len(args) != 1:
+                    raise TypeError("only one positional argument accepted")
+                if kwds:
+                    raise TypeError("cannot specify positional arg and kwds")
+                kwds = args[0]
+            warn(_preamble +
+                 "Instead of constructing a CryptPolicy instance, "
+                 "create a CryptContext directly, or use ``context.update()`` "
+                 "and ``context.load()`` to reconfigure existing CryptContext "
+                 "instances.",
+                 DeprecationWarning, stacklevel=2)
+            self._context = CryptContext(**kwds)
+
+    #===================================================================
+    # public interface for examining options
+    #===================================================================
+    def has_schemes(self):
+        """return True if policy defines *any* schemes for use.
+
+        .. deprecated:: 1.6
+            applications should use ``bool(context.schemes())`` instead.
+            see :meth:`CryptContext.schemes`.
+        """
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "Instead of ``context.policy.has_schemes()``, "
+                 "use ``bool(context.schemes())``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().has_schemes()``, "
+                 "create a CryptContext instance and "
+                 "use ``bool(context.schemes())``.",
+                 DeprecationWarning, stacklevel=2)
+        return bool(self._context.schemes())
+
+    def iter_handlers(self):
+        """return iterator over handlers defined in policy.
+
+        .. deprecated:: 1.6
+            applications should use ``context.schemes(resolve=True))`` instead.
+            see :meth:`CryptContext.schemes`.
+        """
+        if self._stub_policy:
+            warn(_preamble +
+                 "Instead of ``context.policy.iter_handlers()``, "
+                 "use ``context.schemes(resolve=True)``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().iter_handlers()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.schemes(resolve=True)``.",
+                 DeprecationWarning, stacklevel=2)
+        return self._context.schemes(resolve=True)
+
+    def schemes(self, resolve=False):
+        """return list of schemes defined in policy.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.schemes` instead.
+        """
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "Instead of ``context.policy.schemes()``, "
+                 "use ``context.schemes()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().schemes()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.schemes()``.",
+                 DeprecationWarning, stacklevel=2)
+        return list(self._context.schemes(resolve=resolve))
+
+    def get_handler(self, name=None, category=None, required=False):
+        """return handler as specified by name, or default handler.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.handler` instead,
+            though note that the ``required`` keyword has been removed,
+            and the new method will always act as if ``required=True``.
+        """
+        if self._stub_policy:
+            warn(_preamble +
+                 "Instead of ``context.policy.get_handler()``, "
+                 "use ``context.handler()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().get_handler()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.handler()``.",
+                 DeprecationWarning, stacklevel=2)
+        # CryptContext.handler() doesn't support required=False,
+        # so wrapping it in try/except
+        try:
+            return self._context.handler(name, category)
+        except KeyError:
+            if required:
+                raise
+            else:
+                return None
+
+    def get_min_verify_time(self, category=None):
+        """get min_verify_time setting for policy.
+
+        .. deprecated:: 1.6
+            min_verify_time will be removed entirely in passlib 1.8
+        """
+        warn("get_min_verify_time() and min_verify_time option is deprecated, "
+             "and will be removed in Passlib 1.8", DeprecationWarning,
+             stacklevel=2)
+        return self._context._config.get_context_option_with_flag(category, "min_verify_time")[0] or 0
+
+    def get_options(self, name, category=None):
+        """return dictionary of options specific to a given handler.
+
+        .. deprecated:: 1.6
+            this method has no direct replacement in the 1.6 api, as there
+            is not a clearly defined use-case. however, examining the output of
+            :meth:`CryptContext.to_dict` should serve as the closest alternative.
+        """
+        # XXX: might make a public replacement, but need more study of the use cases.
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "``context.policy.get_options()`` will no longer be available.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "``CryptPolicy().get_options()`` will no longer be available.",
+                 DeprecationWarning, stacklevel=2)
+        if hasattr(name, "name"):
+            name = name.name
+        return self._context._config._get_record_options_with_flag(name, category)[0]
+
+    def handler_is_deprecated(self, name, category=None):
+        """check if handler has been deprecated by policy.
+
+        .. deprecated:: 1.6
+            this method has no direct replacement in the 1.6 api, as there
+            is not a clearly defined use-case. however, examining the output of
+            :meth:`CryptContext.to_dict` should serve as the closest alternative.
+        """
+        # XXX: might make a public replacement, but need more study of the use cases.
+        if self._stub_policy:
+            warn(_preamble +
+                 "``context.policy.handler_is_deprecated()`` will no longer be available.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "``CryptPolicy().handler_is_deprecated()`` will no longer be available.",
+                 DeprecationWarning, stacklevel=2)
+        if hasattr(name, "name"):
+            name = name.name
+        return self._context._is_deprecated_scheme(name, category)
+
+    #===================================================================
+    # serialization
+    #===================================================================
+
+    def iter_config(self, ini=False, resolve=False):
+        """iterate over key/value pairs representing the policy object.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.to_dict` instead.
+        """
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "Instead of ``context.policy.iter_config()``, "
+                 "use ``context.to_dict().items()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().iter_config()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.to_dict().items()``.",
+                 DeprecationWarning, stacklevel=2)
+        # hacked code that renders keys & values in manner that approximates
+        # old behavior. context.to_dict() is much cleaner.
+        context = self._context
+        if ini:
+            def render_key(key):
+                return context._render_config_key(key).replace("__", ".")
+            def render_value(value):
+                if isinstance(value, (list,tuple)):
+                    value = ", ".join(value)
+                return value
+            resolve = False
+        else:
+            render_key = context._render_config_key
+            render_value = lambda value: value
+        return (
+            (render_key(key), render_value(value))
+            for key, value in context._config.iter_config(resolve)
+        )
+
+    def to_dict(self, resolve=False):
+        """export policy object as dictionary of options.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.to_dict` instead.
+        """
+        if self._stub_policy:
+            warn(_preamble +
+                 "Instead of ``context.policy.to_dict()``, "
+                 "use ``context.to_dict()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().to_dict()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.to_dict()``.",
+                 DeprecationWarning, stacklevel=2)
+        return self._context.to_dict(resolve)
+
+    def to_file(self, stream, section="passlib"): # pragma: no cover -- deprecated & unused
+        """export policy to file.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.to_string` instead,
+            and then write the output to a file as desired.
+        """
+        if self._stub_policy:
+            warn(_preamble +
+                 "Instead of ``context.policy.to_file(stream)``, "
+                 "use ``stream.write(context.to_string())``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().to_file(stream)``, "
+                 "create a CryptContext instance and "
+                 "use ``stream.write(context.to_string())``.",
+                 DeprecationWarning, stacklevel=2)
+        out = self._context.to_string(section=section)
+        if PY2:
+            out = out.encode("utf-8")
+        stream.write(out)
+
+    def to_string(self, section="passlib", encoding=None):
+        """export policy to file.
+
+        .. deprecated:: 1.6
+            applications should use :meth:`CryptContext.to_string` instead.
+        """
+        if self._stub_policy:
+            warn(_preamble + # pragma: no cover -- deprecated & unused
+                 "Instead of ``context.policy.to_string()``, "
+                 "use ``context.to_string()``.",
+                 DeprecationWarning, stacklevel=2)
+        else:
+            warn(_preamble +
+                 "Instead of ``CryptPolicy().to_string()``, "
+                 "create a CryptContext instance and "
+                 "use ``context.to_string()``.",
+                 DeprecationWarning, stacklevel=2)
+        out = self._context.to_string(section=section)
+        if encoding:
+            out = out.encode(encoding)
+        return out
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# _CryptRecord helper class
+#=============================================================================
+class _CryptRecord(object):
+    """wraps a handler and automatically applies various options.
+
+    this is a helper used internally by CryptContext in order to reduce the
+    amount of work that needs to be done by CryptContext.verify().
+    this class takes in all the options for a particular (scheme, category)
+    combination, and attempts to provide as short a code-path as possible for
+    the particular configuration.
+    """
+
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # informational attrs
+    handler = None # handler instance this is wrapping
+    category = None # user category this applies to
+    deprecated = False # set if handler itself has been deprecated in config
+
+    # rounds management - filled in by _init_rounds_options()
+    _has_rounds_options = False # if _has_rounds_bounds OR _generate_rounds is set
+    _has_rounds_bounds = False # if either min_rounds or max_rounds set
+    _min_rounds = None # minimum rounds allowed by policy, or None
+    _max_rounds = None # maximum rounds allowed by policy, or None
+    _generate_rounds = None # rounds generation function, or None
+
+    # encrypt()/genconfig() attrs
+    settings = None # options to be passed directly to encrypt()
+
+    # verify() attrs
+    _min_verify_time = None
+
+    # needs_update() attrs
+    _needs_update = None # optional callable provided by handler
+    _has_rounds_introspection = False # if rounds can be extract from hash
+
+    # cloned directly from handler, not affected by config options.
+    identify = None
+    genhash = None
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, handler, category=None, deprecated=False,
+                 min_rounds=None, max_rounds=None, default_rounds=None,
+                 vary_rounds=None, min_verify_time=None,
+                 **settings):
+        # store basic bits
+        self.handler = handler
+        self.category = category
+        self.deprecated = deprecated
+        self.settings = settings
+
+        # validate & normalize rounds options
+        self._init_rounds_options(min_rounds, max_rounds, default_rounds,
+                             vary_rounds)
+
+        # init wrappers for handler methods we modify args to
+        self._init_encrypt_and_genconfig()
+        self._init_verify(min_verify_time)
+        self._init_needs_update()
+
+        # these aren't wrapped by _CryptRecord, copy them directly from handler.
+        self.identify = handler.identify
+        self.genhash = handler.genhash
+
+    #===================================================================
+    # virtual attrs
+    #===================================================================
+    @property
+    def scheme(self):
+        return self.handler.name
+
+    @property
+    def _errprefix(self):
+        "string used to identify record in error messages"
+        handler = self.handler
+        category = self.category
+        if category:
+            return "%s %s config" % (handler.name, category)
+        else:
+            return "%s config" % (handler.name,)
+
+    def __repr__(self): # pragma: no cover -- debugging
+        return "<_CryptRecord 0x%x for %s>" % (id(self), self._errprefix)
+
+    #===================================================================
+    # rounds generation & limits - used by encrypt & deprecation code
+    #===================================================================
+    def _init_rounds_options(self, mn, mx, df, vr):
+        "parse options and compile efficient generate_rounds function"
+        #----------------------------------------------------
+        # extract hard limits from handler itself
+        #----------------------------------------------------
+        handler = self.handler
+        if 'rounds' not in handler.setting_kwds:
+            # doesn't even support rounds keyword.
+            return
+        hmn = getattr(handler, "min_rounds", None)
+        hmx = getattr(handler, "max_rounds", None)
+
+        def check_against_handler(value, name):
+            "issue warning if value outside handler limits"
+            if hmn is not None and value < hmn:
+                warn("%s: %s value is below handler minimum %d: %d" %
+                     (self._errprefix, name, hmn, value), PasslibConfigWarning)
+            if hmx is not None and value > hmx:
+                warn("%s: %s value is above handler maximum %d: %d" %
+                     (self._errprefix, name, hmx, value), PasslibConfigWarning)
+
+        #----------------------------------------------------
+        # set policy limits
+        #----------------------------------------------------
+        if mn is not None:
+            if mn < 0:
+                raise ValueError("%s: min_rounds must be >= 0" % self._errprefix)
+            check_against_handler(mn, "min_rounds")
+            self._min_rounds = mn
+            self._has_rounds_bounds = True
+
+        if mx is not None:
+            if mn is not None and mx < mn:
+                raise ValueError("%s: max_rounds must be "
+                                 ">= min_rounds" % self._errprefix)
+            elif mx < 0:
+                raise ValueError("%s: max_rounds must be >= 0" % self._errprefix)
+            check_against_handler(mx, "max_rounds")
+            self._max_rounds = mx
+            self._has_rounds_bounds = True
+
+        #----------------------------------------------------
+        # validate default_rounds
+        #----------------------------------------------------
+        if df is not None:
+            if mn is not None and df < mn:
+                    raise ValueError("%s: default_rounds must be "
+                                     ">= min_rounds" % self._errprefix)
+            if mx is not None and df > mx:
+                    raise ValueError("%s: default_rounds must be "
+                                     "<= max_rounds" % self._errprefix)
+            check_against_handler(df, "default_rounds")
+        elif vr or mx or mn:
+            # need an explicit default to work with
+            df = getattr(handler, "default_rounds", None) or mx or mn
+            assert df is not None, "couldn't find fallback default_rounds"
+        else:
+            # no need for rounds generation
+            self._has_rounds_options = self._has_rounds_bounds
+            return
+
+        # clip default to handler & policy limits *before* vary rounds
+        # is calculated, so that proportion vr values are scaled against
+        # the effective default.
+        def clip(value):
+            "clip value to intersection of policy + handler limits"
+            if mn is not None and value < mn:
+                value = mn
+            if hmn is not None and value < hmn:
+                value = hmn
+            if mx is not None and value > mx:
+                value = mx
+            if hmx is not None and value > hmx:
+                value = hmx
+            return value
+        df = clip(df)
+
+        #----------------------------------------------------
+        # validate vary_rounds,
+        # coerce df/vr to linear scale,
+        # and setup scale_value() to undo coercion
+        #----------------------------------------------------
+        # NOTE: vr=0 same as if vr not set
+        if vr:
+            if vr < 0:
+                raise ValueError("%s: vary_rounds must be >= 0" %
+                                 self._errprefix)
+            def scale_value(value, upper):
+                return value
+            if isinstance(vr, float):
+                # vr is value from 0..1 expressing fraction of default rounds.
+                if vr > 1:
+                    # XXX: deprecate 1.0 ?
+                    raise ValueError("%s: vary_rounds must be < 1.0" %
+                                     self._errprefix)
+                # calculate absolute vr value based on df & rounds_cost
+                cost_scale = getattr(handler, "rounds_cost", "linear")
+                assert cost_scale in ["log2", "linear"]
+                if cost_scale == "log2":
+                    # convert df & vr to linear scale for limit calc,
+                    # and redefine scale_value() to convert back to log2.
+                    df = 1<<df
+                    def scale_value(value, upper):
+                        if value <= 0:
+                            return 0
+                        elif upper:
+                            return int(logb(value,2))
+                        else:
+                            return int(ceil(logb(value,2)))
+                vr = int(df*vr)
+            elif not isinstance(vr, int):
+                raise TypeError("vary_rounds must be int or float")
+            # else: vr is explicit number of rounds to vary df by.
+
+        #----------------------------------------------------
+        # set up rounds generation function.
+        #----------------------------------------------------
+        if not vr:
+            # fixed rounds value
+            self._generate_rounds = lambda : df
+        else:
+            # randomly generate rounds in range df +/- vr
+            lower = clip(scale_value(df-vr,False))
+            upper = clip(scale_value(df+vr,True))
+            if lower == upper:
+                self._generate_rounds = lambda: upper
+            else:
+                assert lower < upper
+                self._generate_rounds = lambda: rng.randint(lower, upper)
+
+        # hack for bsdi_crypt - want to avoid even-valued rounds
+        # NOTE: this technically might generate a rounds value 1 larger
+        # than the requested upper bound - but better to err on side of safety.
+        if getattr(handler, "_avoid_even_rounds", False):
+            gen = self._generate_rounds
+            self._generate_rounds = lambda : gen()|1
+
+        self._has_rounds_options = True
+
+    #===================================================================
+    # encrypt() / genconfig()
+    #===================================================================
+    def _init_encrypt_and_genconfig(self):
+        "initialize genconfig/encrypt wrapper methods"
+        settings = self.settings
+        handler = self.handler
+
+        # check no invalid settings are being set
+        keys = handler.setting_kwds
+        for key in settings:
+            if key not in keys:
+                raise KeyError("keyword not supported by %s handler: %r" %
+                               (handler.name, key))
+
+        # if _prepare_settings() has nothing to do, bypass our wrappers
+        # with reference to original methods.
+        if not (settings or self._has_rounds_options):
+            self.genconfig = handler.genconfig
+            self.encrypt = handler.encrypt
+
+    def genconfig(self, **kwds):
+        "wrapper for handler.genconfig() which adds custom settings/rounds"
+        self._prepare_settings(kwds)
+        return self.handler.genconfig(**kwds)
+
+    def encrypt(self, secret, **kwds):
+        "wrapper for handler.encrypt() which adds custom settings/rounds"
+        self._prepare_settings(kwds)
+        return self.handler.encrypt(secret, **kwds)
+
+    def _prepare_settings(self, kwds):
+        "add default values to settings for encrypt & genconfig"
+        # load in default values for any settings
+        if kwds:
+            for k,v in iteritems(self.settings):
+                if k not in kwds:
+                    kwds[k] = v
+        else:
+            # faster, and the common case
+            kwds.update(self.settings)
+
+        # handle rounds
+        if self._has_rounds_options:
+            rounds = kwds.get("rounds")
+            if rounds is None:
+                # fill in default rounds value
+                gen = self._generate_rounds
+                if gen:
+                    kwds['rounds'] = gen()
+            elif self._has_rounds_bounds:
+                # check bounds for application-provided rounds value.
+                # XXX: should this raise an error instead of warning ?
+                # NOTE: stackdepth=4 is so that error matches
+                # where ctx.encrypt() was called by application code.
+                mn = self._min_rounds
+                if mn is not None and rounds < mn:
+                    warn("%s requires rounds >= %d, increasing value from %d" %
+                         (self._errprefix, mn, rounds), PasslibConfigWarning, 4)
+                    rounds = mn
+                mx = self._max_rounds
+                if mx and rounds > mx:
+                    warn("%s requires rounds <= %d, decreasing value from %d" %
+                         (self._errprefix, mx, rounds), PasslibConfigWarning, 4)
+                    rounds = mx
+                kwds['rounds'] = rounds
+
+    #===================================================================
+    # verify()
+    #===================================================================
+    # TODO: once min_verify_time is removed, this will just be a clone
+    # of handler.verify()
+
+    def _init_verify(self, mvt):
+        "initialize verify() wrapper - implements min_verify_time"
+        if mvt:
+            assert isinstance(mvt, (int,float)) and mvt > 0, "CryptPolicy should catch this"
+            self._min_verify_time = mvt
+        else:
+            # no mvt wrapper needed, so just use handler.verify directly
+            self.verify = self.handler.verify
+
+    def verify(self, secret, hash, **context):
+        "verify helper - adds min_verify_time delay"
+        mvt = self._min_verify_time
+        assert mvt > 0, "wrapper should have been replaced for mvt=0"
+        start = tick()
+        if self.handler.verify(secret, hash, **context):
+            return True
+        end = tick()
+        delta = mvt + start - end
+        if delta > 0:
+            sleep(delta)
+        elif delta < 0:
+            # warn app they exceeded bounds (this might reveal
+            # relative costs of different hashes if under migration)
+            warn("CryptContext: verify exceeded min_verify_time: "
+                 "scheme=%r min_verify_time=%r elapsed=%r" %
+                 (self.scheme, mvt, end-start), PasslibConfigWarning)
+        return False
+
+    #===================================================================
+    # needs_update()
+    #===================================================================
+    def _init_needs_update(self):
+        """initialize state for needs_update()"""
+        # if handler has been deprecated, replace wrapper and skip other checks
+        if self.deprecated:
+            self.needs_update = lambda hash, secret: True
+            return
+
+        # let handler detect hashes with configurations that don't match
+        # current settings. currently do this by calling
+        # ``handler._bind_needs_update(**settings)``, which if defined
+        # should return None or a callable ``needs_update(hash,secret)->bool``.
+        #
+        # NOTE: this interface is still private, because it was hacked in
+        # for the sake of bcrypt & scram, and is subject to change.
+        handler = self.handler
+        const = getattr(handler, "_bind_needs_update", None)
+        if const:
+            self._needs_update = const(**self.settings)
+
+        # XXX: what about a "min_salt_size" deprecator?
+
+        # set flag if we can extract rounds from hash, allowing
+        # needs_update() to check for rounds that are outside of
+        # the configured range.
+        if self._has_rounds_bounds and hasattr(handler, "from_string"):
+            self._has_rounds_introspection = True
+
+    def needs_update(self, hash, secret):
+        # init replaces this method entirely for this case.
+        ### check if handler has been deprecated
+        ##if self.deprecated:
+        ##    return True
+
+        # check handler's detector if it provided one.
+        check = self._needs_update
+        if check and check(hash, secret):
+            return True
+
+        # XXX: should we use from_string() call below to check
+        #      for config strings, and flag them as needing update?
+        #      or throw an error?
+        #      or leave that as an explicitly undefined border case,
+        #      to keep the codepath simpler & faster?
+
+        # if we can parse rounds parameter, check if it's w/in bounds.
+        if self._has_rounds_introspection:
+            # XXX: this might be a good place to use parsehash()
+            hash_obj = self.handler.from_string(hash)
+            try:
+                rounds = hash_obj.rounds
+            except AttributeError: # pragma: no cover -- sanity check
+                # XXX: all builtin hashes should have rounds attr,
+                #      so should a warning be issues here?
+                pass
+            else:
+                mn = self._min_rounds
+                if mn is not None and rounds < mn:
+                    return True
+                mx = self._max_rounds
+                if mx and rounds > mx:
+                    return True
+
+        return False
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# _CryptConfig helper class
+#=============================================================================
+class _CryptConfig(object):
+    """parses, validates, and stores CryptContext config
+
+    this is a helper used internally by CryptContext to handle
+    parsing, validation, and serialization of it's config options.
+    split out from the main class, but not made public since
+    that just complicates interface too much (c.f. CryptPolicy)
+
+    :arg source: config as dict mapping ``(cat,scheme,option) -> value``
+    """
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # triple-nested dict which maps scheme -> category -> key -> value,
+    # storing all hash-specific options
+    _scheme_options = None
+
+    # double-nested dict which maps key -> category -> value
+    # storing all CryptContext options
+    _context_options = None
+
+    # tuple of handler objects
+    handlers = None
+
+    # tuple of scheme objects in same order as handlers
+    schemes = None
+
+    # tuple of categories in alphabetical order (not including None)
+    categories = None
+
+    # dict mapping category -> default scheme
+    _default_schemes = None
+
+    # dict mapping (scheme, category) -> _CryptRecord
+    _records = None
+
+    # dict mapping category -> list of _CryptRecord instances for that category,
+    # in order of schemes(). populated on demand by _get_record_list()
+    _record_lists = None
+
+    #===================================================================
+    # constructor
+    #===================================================================
+    def __init__(self, source):
+        self._init_scheme_list(source.get((None,None,"schemes")))
+        self._init_options(source)
+        self._init_default_schemes()
+        self._init_records()
+
+    def _init_scheme_list(self, data):
+        """initialize .handlers and .schemes attributes"""
+        handlers  = []
+        schemes = []
+        if isinstance(data, str):
+            data = splitcomma(data)
+        for elem in data or ():
+            # resolve elem -> handler & scheme
+            if hasattr(elem, "name"):
+                handler = elem
+                scheme = handler.name
+                _validate_handler_name(scheme)
+            elif isinstance(elem, str):
+                handler = get_crypt_handler(elem)
+                scheme = handler.name
+            else:
+                raise TypeError("scheme must be name or CryptHandler, "
+                                "not %r" % type(elem))
+
+            # check scheme name isn't already in use
+            if scheme in schemes:
+                raise KeyError("multiple handlers with same name: %r" %
+                               (scheme,))
+
+            # add to handler list
+            handlers.append(handler)
+            schemes.append(scheme)
+
+        self.handlers = tuple(handlers)
+        self.schemes = tuple(schemes)
+
+    #===================================================================
+    # lowlevel options
+    #===================================================================
+
+    #---------------------------------------------------------------
+    # init lowlevel option storage
+    #---------------------------------------------------------------
+    def _init_options(self, source):
+        """load config dict into internal representation,
+        and init .categories attr
+        """
+        # prepare dicts & locals
+        norm_scheme_option = self._norm_scheme_option
+        norm_context_option = self._norm_context_option
+        self._scheme_options = scheme_options = {}
+        self._context_options = context_options = {}
+        categories = set()
+
+        # load source config into internal storage
+        for (cat, scheme, key), value in iteritems(source):
+            categories.add(cat)
+            if scheme:
+                # normalize scheme option
+                key, value = norm_scheme_option(key, value)
+
+                # store in scheme_options
+                # map structure: scheme_options[scheme][category][key] = value
+                try:
+                    category_map = scheme_options[scheme]
+                except KeyError:
+                    scheme_options[scheme] = {cat: {key: value}}
+                else:
+                    try:
+                        option_map = category_map[cat]
+                    except KeyError:
+                        category_map[cat] = {key: value}
+                    else:
+                        option_map[key] = value
+            else:
+                # normalize context option
+                if cat and key == "schemes":
+                    raise KeyError("'schemes' context option is not allowed "
+                                   "per category")
+                key, value = norm_context_option(key, value)
+
+                # store in context_options
+                # map structure: context_options[key][category] = value
+                try:
+                    category_map = context_options[key]
+                except KeyError:
+                    context_options[key] = {cat: value}
+                else:
+                    category_map[cat] = value
+
+        # store list of configured categories
+        categories.discard(None)
+        self.categories = tuple(sorted(categories))
+
+    def _norm_scheme_option(self, key, value):
+        # check for invalid options
+        if key == "rounds":
+            # for now, translating this to 'default_rounds' to be helpful.
+            # need to pick one of the two names as official,
+            # and deprecate the other one.
+            key = "default_rounds"
+        elif key in _forbidden_scheme_options:
+            raise KeyError("%r option not allowed in CryptContext "
+                           "configuration" % (key,))
+        # coerce strings for certain fields (e.g. min_rounds uses ints)
+        if isinstance(value, str):
+            func = _coerce_scheme_options.get(key)
+            if func:
+                value = func(value)
+        return key, value
+
+    def _norm_context_option(self, key, value):
+        schemes = self.schemes
+        if key == "default":
+            if hasattr(value, "name"):
+                value = value.name
+            elif not isinstance(value, str):
+                raise ExpectedTypeError(value, "str", "default")
+            if schemes and value not in schemes:
+                raise KeyError("default scheme not found in policy")
+        elif key == "deprecated":
+            if isinstance(value, str):
+                value = splitcomma(value)
+            elif not isinstance(value, (list,tuple)):
+                raise ExpectedTypeError(value, "str or seq", "deprecated")
+            if 'auto' in value:
+                if len(value) > 1:
+                    raise ValueError("cannot list other schemes if "
+                                     "``deprecated=['auto']`` is used")
+            elif schemes:
+                # make sure list of deprecated schemes is subset of configured schemes
+                for scheme in value:
+                    if not isinstance(scheme, str):
+                        raise ExpectedTypeError(value, "str", "deprecated element")
+                    if scheme not in schemes:
+                        raise KeyError("deprecated scheme not found "
+                                   "in policy: %r" % (scheme,))
+        elif key == "min_verify_time":
+            warn("'min_verify_time' is deprecated as of Passlib 1.6, will be "
+                 "ignored in 1.7, and removed in 1.8.", DeprecationWarning)
+            value = float(value)
+            if value < 0:
+                raise ValueError("'min_verify_time' must be >= 0")
+        elif key != "schemes":
+            raise KeyError("unknown CryptContext keyword: %r" % (key,))
+        return key, value
+
+    #---------------------------------------------------------------
+    # reading context options
+    #---------------------------------------------------------------
+    def get_context_optionmap(self, key, _default={}):
+        """return dict mapping category->value for specific context option.
+        (treat retval as readonly).
+        """
+        return self._context_options.get(key, _default)
+
+    def get_context_option_with_flag(self, category, key):
+        """return value of specific option, handling category inheritance.
+        also returns flag indicating whether value is category-specific.
+        """
+        try:
+            category_map = self._context_options[key]
+        except KeyError:
+            return None, False
+        value = category_map.get(None)
+        if category:
+            try:
+                alt = category_map[category]
+            except KeyError:
+                pass
+            else:
+                if value is None or alt != value:
+                    return alt, True
+        return value, False
+
+    #---------------------------------------------------------------
+    # reading scheme options
+    #---------------------------------------------------------------
+    def _get_scheme_optionmap(self, scheme, category, default={}):
+        """return all options for (scheme,category) combination
+        (treat return as readonly)
+        """
+        try:
+            return self._scheme_options[scheme][category]
+        except KeyError:
+            return default
+
+    def get_scheme_options_with_flag(self, scheme, category):
+        """return composite dict of all options set for scheme.
+        includes options inherited from 'all' and from default category.
+        result can be modified.
+        returns (kwds, has_cat_specific_options)
+        """
+        # start out with copy of global options
+        get_optionmap = self._get_scheme_optionmap
+        kwds = get_optionmap("all", None).copy()
+        has_cat_options = False
+
+        # add in category-specific global options
+        if category:
+            defkwds = kwds.copy() # <-- used to detect category-specific options
+            kwds.update(get_optionmap("all", category))
+
+        # add in default options for scheme
+        other = get_optionmap(scheme, None)
+        kwds.update(other)
+
+        # load category-specific options for scheme
+        if category:
+            defkwds.update(other)
+            kwds.update(get_optionmap(scheme, category))
+
+            # compare default category options to see if there's anything
+            # category-specific
+            if kwds != defkwds:
+                has_cat_options = True
+
+        return kwds, has_cat_options
+
+    #===================================================================
+    # deprecated & default schemes
+    #===================================================================
+    def _init_default_schemes(self):
+        """initialize maps containing default scheme for each category.
+
+        have to do this after _init_options(), since the default scheme
+        is affected by the list of deprecated schemes.
+        """
+        # init maps & locals
+        get_optionmap = self.get_context_optionmap
+        default_map = self._default_schemes = get_optionmap("default").copy()
+        dep_map = get_optionmap("deprecated")
+        schemes = self.schemes
+        if not schemes:
+            return
+
+        # figure out default scheme
+        deps = dep_map.get(None) or ()
+        default = default_map.get(None)
+        if not default:
+            for scheme in schemes:
+                if scheme not in deps:
+                    default_map[None] = scheme
+                    break
+            else:
+                raise ValueError("must have at least one non-deprecated scheme")
+        elif default in deps:
+            raise ValueError("default scheme cannot be deprecated")
+
+        # figure out per-category default schemes,
+        for cat in self.categories:
+            cdeps = dep_map.get(cat, deps)
+            cdefault = default_map.get(cat, default)
+            if not cdefault:
+                for scheme in schemes:
+                    if scheme not in cdeps:
+                        default_map[cat] = scheme
+                        break
+                else:
+                    raise ValueError("must have at least one non-deprecated "
+                                     "scheme for %r category" % cat)
+            elif cdefault in cdeps:
+                raise ValueError("default scheme for %r category "
+                                 "cannot be deprecated" % cat)
+
+    def default_scheme(self, category):
+        "return default scheme for specific category"
+        defaults = self._default_schemes
+        try:
+            return defaults[category]
+        except KeyError:
+            pass
+        if not self.schemes:
+            raise KeyError("no hash schemes configured for this "
+                           "CryptContext instance")
+        return defaults[None]
+
+    def is_deprecated_with_flag(self, scheme, category):
+        "is scheme deprecated under particular category?"
+        depmap = self.get_context_optionmap("deprecated")
+        def test(cat):
+            source = depmap.get(cat, depmap.get(None))
+            if source is None:
+                return None
+            elif 'auto' in source:
+                return scheme != self.default_scheme(cat)
+            else:
+                return scheme in source
+        value = test(None) or False
+        if category:
+            alt = test(category)
+            if alt is not None and value != alt:
+                return alt, True
+        return value, False
+
+    #===================================================================
+    # CryptRecord objects
+    #===================================================================
+    def _init_records(self):
+        # NOTE: this step handles final validation of settings,
+        #       checking for violatiions against handler's internal invariants.
+        #       this is why we create all the records now,
+        #       so CryptContext throws error immediately rather than later.
+        self._record_lists = {}
+        records = self._records = {}
+        get_options = self._get_record_options_with_flag
+        categories = self.categories
+        for handler in self.handlers:
+            scheme = handler.name
+            kwds, _ = get_options(scheme, None)
+            records[scheme, None] = _CryptRecord(handler, **kwds)
+            for cat in categories:
+                kwds, has_cat_options = get_options(scheme, cat)
+                if has_cat_options:
+                    records[scheme, cat] = _CryptRecord(handler, cat, **kwds)
+                # NOTE: if handler has no category-specific opts, get_record()
+                # will automatically use the default category's record.
+        # NOTE: default records for specific category stored under the
+        # key (None,category); these are populated on-demand by get_record().
+
+    def _get_record_options_with_flag(self, scheme, category):
+        """return composite dict of options for given scheme + category.
+
+        this is currently a private method, though some variant
+        of it's output may eventually be made public.
+
+        given a scheme & category, it returns two things:
+        a set of all the keyword options to pass to the _CryptRecord constructor,
+        and a bool flag indicating whether any of these options
+        were specific to the named category. if this flag is false,
+        the options are identical to the options for the default category.
+
+        the options dict includes all the scheme-specific settings,
+        as well as optional *deprecated* and *min_verify_time* keywords.
+        """
+        # get scheme options
+        kwds, has_cat_options = self.get_scheme_options_with_flag(scheme, category)
+
+        # throw in deprecated flag
+        value, not_inherited = self.is_deprecated_with_flag(scheme, category)
+        if value:
+            kwds['deprecated'] = True
+        if not_inherited:
+            has_cat_options = True
+
+        # add in min_verify_time setting from context
+        value, not_inherited = self.get_context_option_with_flag(category, "min_verify_time")
+        if value:
+            kwds['min_verify_time'] = value
+        if not_inherited:
+            has_cat_options = True
+
+        return kwds, has_cat_options
+
+    def get_record(self, scheme, category):
+        "return record for specific scheme & category (cached)"
+        # NOTE: this is part of the critical path shared by
+        #       all of CryptContext's PasswordHash methods,
+        #       hence all the caching and error checking.
+
+        # quick lookup in cache
+        try:
+            return self._records[scheme, category]
+        except KeyError:
+            pass
+
+        # type check
+        if category is not None and not isinstance(category, str):
+            if PY2 and isinstance(category, unicode):
+                # for compatibility with unicode-centric py2 apps
+                return self.get_record(scheme, category.encode("utf-8"))
+            raise ExpectedTypeError(category, "str or None", "category")
+        if scheme is not None and not isinstance(scheme, str):
+            raise ExpectedTypeError(scheme, "str or None", "scheme")
+
+        # if scheme=None,
+        # use record for category's default scheme, and cache result.
+        if not scheme:
+            default = self.default_scheme(category)
+            assert default
+            record = self._records[None, category] = self.get_record(default,
+                                                                      category)
+            return record
+
+        # if no record for (scheme, category),
+        # use record for (scheme, None), and cache result.
+        if category:
+            try:
+                cache = self._records
+                record = cache[scheme, category] = cache[scheme, None]
+                return record
+            except KeyError:
+                pass
+
+        # scheme not found in configuration for default category
+        raise KeyError("crypt algorithm not found in policy: %r" % (scheme,))
+
+    def _get_record_list(self, category=None):
+        """return list of records for category (cached)
+
+        this is an internal helper used only by identify_record()
+        """
+        # type check of category - handled by _get_record()
+        # quick lookup in cache
+        try:
+            return self._record_lists[category]
+        except KeyError:
+            pass
+        # cache miss - build list from scratch
+        value = self._record_lists[category] = [
+            self.get_record(scheme, category)
+            for scheme in self.schemes
+            ]
+        return value
+
+    def identify_record(self, hash, category, required=True):
+        """internal helper to identify appropriate _CryptRecord for hash"""
+        # NOTE: this is part of the critical path shared by
+        #       all of CryptContext's PasswordHash methods,
+        #       hence all the caching and error checking.
+        # FIXME: if multiple hashes could match (e.g. lmhash vs nthash)
+        #        this will only return first match. might want to do something
+        #        about this in future, but for now only hashes with
+        #        unique identifiers will work properly in a CryptContext.
+        # XXX: if all handlers have a unique prefix (e.g. all are MCF / LDAP),
+        #      could use dict-lookup to speed up this search.
+        if not isinstance(hash, base_string_types):
+            raise ExpectedStringError(hash, "hash")
+        # type check of category - handled by _get_record_list()
+        for record in self._get_record_list(category):
+            if record.identify(hash):
+                return record
+        if not required:
+            return None
+        elif not self.schemes:
+            raise KeyError("no crypt algorithms supported")
+        else:
+            raise ValueError("hash could not be identified")
+
+    #===================================================================
+    # serialization
+    #===================================================================
+    def iter_config(self, resolve=False):
+        """regenerate original config.
+
+        this is an iterator which yields ``(cat,scheme,option),value`` items,
+        in the order they generally appear inside an INI file.
+        if interpreted as a dictionary, it should match the original
+        keywords passed to the CryptContext (aside from any canonization).
+
+        it's mainly used as the internal backend for most of the public
+        serialization methods.
+        """
+        # grab various bits of data
+        scheme_options = self._scheme_options
+        context_options = self._context_options
+        scheme_keys = sorted(scheme_options)
+        context_keys = sorted(context_options)
+
+        # write loaded schemes (may differ from 'schemes' local var)
+        if 'schemes' in context_keys:
+            context_keys.remove("schemes")
+        value = self.handlers if resolve else self.schemes
+        if value:
+            yield (None, None, "schemes"), list(value)
+
+        # then run through config for each user category
+        for cat in (None,) + self.categories:
+
+            # write context options
+            for key in context_keys:
+                try:
+                    value = context_options[key][cat]
+                except KeyError:
+                    pass
+                else:
+                    if isinstance(value, list):
+                        value = list(value)
+                    yield (cat, None, key), value
+
+            # write per-scheme options for all schemes.
+            for scheme in scheme_keys:
+                try:
+                    kwds = scheme_options[scheme][cat]
+                except KeyError:
+                    pass
+                else:
+                    for key in sorted(kwds):
+                        yield (cat, scheme, key), kwds[key]
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# main CryptContext class
+#=============================================================================
+class CryptContext(object):
+    """Helper for encrypting passwords using different algorithms.
+
+    Instances of this class allow applications to choose a specific
+    set of hash algorithms which they wish to support, set limits and defaults
+    for the rounds and salt sizes those algorithms should use, flag
+    which algorithms should be deprecated, and automatically handle
+    migrating users to stronger hashes when they log in.
+
+    Basic usage::
+
+        >>> ctx = CryptContext(schemes=[...])
+
+    See the Passlib online documentation for details and full documentation.
+    """
+    # FIXME: altering the configuration of this object isn't threadsafe,
+    # but is generally only done during application init, so not a major
+    # issue (just yet).
+
+    # XXX: would like some way to restrict the categories that are allowed,
+    # to restrict what the app OR the config can use.
+
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # _CryptConfig instance holding current parsed config
+    _config = None
+
+    # copy of _config methods, stored in CryptContext instance for speed.
+    _get_record = None
+    _identify_record = None
+
+    #===================================================================
+    # secondary constructors
+    #===================================================================
+    @classmethod
+    def _norm_source(cls, source):
+        "internal helper - accepts string, dict, or context"
+        if isinstance(source, dict):
+            return cls(**source)
+        elif isinstance(source, cls):
+            return source
+        else:
+            self = cls()
+            self.load(source)
+            return self
+
+    @classmethod
+    def from_string(cls, source, section="passlib", encoding="utf-8"):
+        """create new CryptContext instance from an INI-formatted string.
+
+        :type source: unicode or bytes
+        :arg source:
+            string containing INI-formatted content.
+
+        :type section: str
+        :param section:
+            option name of section to read from, defaults to ``"passlib"``.
+
+        :type encoding: str
+        :arg encoding:
+            optional encoding used when source is bytes, defaults to ``"utf-8"``.
+
+        :returns:
+            new :class:`CryptContext` instance, configured based on the
+            parameters in the *source* string.
+
+        Usage example::
+
+            >>> from passlib.context import CryptContext
+            >>> context = CryptContext.from_string('''
+            ... [passlib]
+            ... schemes = sha256_crypt, des_crypt
+            ... sha256_crypt__default_rounds = 30000
+            ... ''')
+
+        .. versionadded:: 1.6
+
+        .. seealso:: :meth:`to_string`, the inverse of this constructor.
+        """
+        if not isinstance(source, base_string_types):
+            raise ExpectedTypeError(source, "unicode or bytes", "source")
+        self = cls(_autoload=False)
+        self.load(source, section=section, encoding=encoding)
+        return self
+
+    @classmethod
+    def from_path(cls, path, section="passlib", encoding="utf-8"):
+        """create new CryptContext instance from an INI-formatted file.
+
+        this functions exactly the same as :meth:`from_string`,
+        except that it loads from a local file.
+
+        :type path: str
+        :arg path:
+            path to local file containing INI-formatted config.
+
+        :type section: str
+        :param section:
+            option name of section to read from, defaults to ``"passlib"``.
+
+        :type encoding: str
+        :arg encoding:
+            encoding used to load file, defaults to ``"utf-8"``.
+
+        :returns:
+            new CryptContext instance, configured based on the parameters
+            stored in the file *path*.
+
+        .. versionadded:: 1.6
+
+        .. seealso:: :meth:`from_string` for an equivalent usage example.
+        """
+        self = cls(_autoload=False)
+        self.load_path(path, section=section, encoding=encoding)
+        return self
+
+    def copy(self, **kwds):
+        """Return copy of existing CryptContext instance.
+
+        This function returns a new CryptContext instance whose configuration
+        is exactly the same as the original, with the exception that any keywords
+        passed in will take precedence over the original settings.
+        As an example::
+
+            >>> from passlib.context import CryptContext
+
+            >>> # given an existing context...
+            >>> ctx1 = CryptContext(["sha256_crypt", "md5_crypt"])
+
+            >>> # copy can be used to make a clone, and update
+            >>> # some of the settings at the same time...
+            >>> ctx2 = custom_app_context.copy(default="md5_crypt")
+
+            >>> # and the original will be unaffected by the change
+            >>> ctx1.default_scheme()
+            "sha256_crypt"
+            >>> ctx2.default_scheme()
+            "md5_crypt"
+
+        .. versionchanged:: 1.6
+            This method was previously named :meth:`!replace`. That alias
+            has been deprecated, and will be removed in Passlib 1.8.
+
+        .. seealso:: :meth:`update`
+        """
+        # XXX: it would be faster to store ref to self._config,
+        #      but don't want to share config objects til sure
+        #      can rely on them being immutable.
+        other = CryptContext(_autoload=False)
+        other.load(self)
+        if kwds:
+            other.load(kwds, update=True)
+        return other
+
+    def replace(self, **kwds):
+        "deprecated alias of :meth:`copy`"
+        warn("CryptContext().replace() has been deprecated in Passlib 1.6, "
+             "and will be removed in Passlib 1.8, "
+             "it has been renamed to CryptContext().copy()",
+             DeprecationWarning, stacklevel=2)
+        return self.copy(**kwds)
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, schemes=None,
+                 # keyword only...
+                 policy=_UNSET, # <-- deprecated
+                 _autoload=True, **kwds):
+        # XXX: add ability to make flag certain contexts as immutable,
+        #      e.g. the builtin passlib ones?
+        # XXX: add a name or import path for the contexts, to help out repr?
+        if schemes is not None:
+            kwds['schemes'] = schemes
+        if policy is not _UNSET:
+            warn("The CryptContext ``policy`` keyword has been deprecated as of Passlib 1.6, "
+                 "and will be removed in Passlib 1.8; please use "
+                 "``CryptContext.from_string()` or "
+                 "``CryptContext.from_path()`` instead.",
+                 DeprecationWarning)
+            if policy is None:
+                self.load(kwds)
+            elif isinstance(policy, CryptPolicy):
+                self.load(policy._context)
+                self.update(kwds)
+            else:
+                raise TypeError("policy must be a CryptPolicy instance")
+        elif _autoload:
+            self.load(kwds)
+        else:
+            assert not kwds, "_autoload=False and kwds are mutually exclusive"
+
+    # XXX: would this be useful?
+    ##def __str__(self):
+    ##    if PY3:
+    ##        return self.to_string()
+    ##    else:
+    ##        return self.to_string().encode("utf-8")
+
+    def __repr__(self):
+        return "<CryptContext at 0x%0x>" % id(self)
+
+    #===================================================================
+    # deprecated policy object
+    #===================================================================
+    def _get_policy(self):
+        # The CryptPolicy class has been deprecated, so to support any
+        # legacy accesses, we create a stub policy object so .policy attr
+        # will continue to work.
+        #
+        # the code waits until app accesses a specific policy object attribute
+        # before issuing deprecation warning, so developer gets method-specific
+        # suggestion for how to upgrade.
+
+        # NOTE: making a copy of the context so the policy acts like a snapshot,
+        # to retain the pre-1.6 behavior.
+        return CryptPolicy(_internal_context=self.copy(), _stub_policy=True)
+
+    def _set_policy(self, policy):
+        warn("The CryptPolicy class and the ``context.policy`` attribute have "
+             "been deprecated as of Passlib 1.6, and will be removed in "
+             "Passlib 1.8; please use the ``context.load()`` and "
+             "``context.update()`` methods instead.",
+             DeprecationWarning, stacklevel=2)
+        if isinstance(policy, CryptPolicy):
+            self.load(policy._context)
+        else:
+            raise TypeError("expected CryptPolicy instance")
+
+    policy = property(_get_policy, _set_policy,
+                    doc="[deprecated] returns CryptPolicy instance "
+                        "tied to this CryptContext")
+
+    #===================================================================
+    # loading / updating configuration
+    #===================================================================
+    @staticmethod
+    def _parse_ini_stream(stream, section, filename):
+        "helper read INI from stream, extract passlib section as dict"
+        # NOTE: this expects a unicode stream under py3,
+        # and a utf-8 bytes stream under py2,
+        # allowing the resulting dict to always use native strings.
+        p = SafeConfigParser()
+        if PY_MIN_32:
+            # python 3.2 deprecated readfp in favor of read_file
+            p.read_file(stream, filename)
+        else:
+            p.readfp(stream, filename)
+        return dict(p.items(section))
+
+    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
+        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
+        an INI-formatted string.
+
+        .. versionadded:: 1.6
+        """
+        def helper(stream):
+            kwds = self._parse_ini_stream(stream, section, path)
+            return self.load(kwds, update=update)
+        if PY3:
+            # decode to unicode, which load() expected under py3
+            with open(path, "rt", encoding=encoding) as stream:
+                return helper(stream)
+        elif encoding in ["utf-8", "ascii"]:
+            # keep as utf-8 bytes, which load() expects under py2
+            with open(path, "rb") as stream:
+                return helper(stream)
+        else:
+            # transcode to utf-8 bytes
+            with open(path, "rb") as fh:
+                tmp = fh.read().decode(encoding).encode("utf-8")
+                return helper(BytesIO(tmp))
+
+    def load(self, source, update=False, section="passlib", encoding="utf-8"):
+        """Load new configuration into CryptContext, replacing existing config.
+
+        :arg source:
+            source of new configuration to load.
+            this value can be a number of different types:
+
+            * a :class:`!dict` object, or compatible Mapping
+
+                the key/value pairs will be interpreted the same
+                keywords for the :class:`CryptContext` class constructor.
+
+            * a :class:`!unicode` or :class:`!bytes` string
+
+                this will be interpreted as an INI-formatted file,
+                and appropriate key/value pairs will be loaded from
+                the specified *section*.
+
+            * another :class:`!CryptContext` object.
+
+                this will export a snapshot of it's configuration
+                using :meth:`to_dict`.
+
+        :type update: bool
+        :param update:
+            By default, :meth:`load` will replace the existing configuration
+            entirely. If ``update=True``, it will preserve any existing
+            configuration options that are not overridden by the new source,
+            much like the :meth:`update` method.
+
+        :type section: str
+        :param section:
+            When parsing an INI-formatted string, :meth:`load` will look for
+            a section named ``"passlib"``. This option allows an alternate
+            section name to be used. Ignored when loading from a dictionary.
+
+        :type encoding: str
+        :param encoding:
+            Encoding to use when decode bytes from string.
+            Defaults to ``"utf-8"``. Ignoring when loading from a dictionary.
+
+        :raises TypeError:
+            * If the source cannot be identified.
+            * If an unknown / malformed keyword is encountered.
+
+        :raises ValueError:
+            If an invalid keyword value is encountered.
+
+        .. note::
+
+            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.
+
+        .. versionadded:: 1.6
+        """
+        #-----------------------------------------------------------
+        # autodetect source type, convert to dict
+        #-----------------------------------------------------------
+        parse_keys = True
+        if isinstance(source, base_string_types):
+            if PY3:
+                source = to_unicode(source, encoding, param="source")
+            else:
+                source = to_bytes(source, "utf-8", source_encoding=encoding,
+                                  param="source")
+            source = self._parse_ini_stream(NativeStringIO(source), section,
+                                            "<string>")
+        elif isinstance(source, CryptContext):
+            # extract dict directly from config, so it can be merged later
+            source = dict(source._config.iter_config(resolve=True))
+            parse_keys = False
+        elif not hasattr(source, "items"):
+            # mappings are left alone, otherwise throw an error.
+            raise ExpectedTypeError(source, "string or dict", "source")
+
+        # XXX: add support for other iterable types, e.g. sequence of pairs?
+
+        #-----------------------------------------------------------
+        # parse dict keys into (category, scheme, option) format,
+        # merge with existing configuration if needed
+        #-----------------------------------------------------------
+        if parse_keys:
+            parse = self._parse_config_key
+            source = dict((parse(key), value)
+                          for key, value in iteritems(source))
+        if update and self._config is not None:
+            # if updating, do nothing if source is empty,
+            if not source:
+                return
+            # otherwise overlay source on top of existing config
+            tmp = source
+            source = dict(self._config.iter_config(resolve=True))
+            source.update(tmp)
+
+        #-----------------------------------------------------------
+        # compile into _CryptConfig instance, and update state
+        #-----------------------------------------------------------
+        config = _CryptConfig(source)
+        self._config = config
+        self._get_record = config.get_record
+        self._identify_record = config.identify_record
+
+    @staticmethod
+    def _parse_config_key(ckey):
+        """helper used to parse ``cat__scheme__option`` keys into a tuple"""
+        # split string into 1-3 parts
+        assert isinstance(ckey, str)
+        parts = ckey.replace(".","__").split("__")
+        count = len(parts)
+        if count == 1:
+            cat, scheme, key = None, None, parts[0]
+        elif count == 2:
+            cat = None
+            scheme, key = parts
+        elif count == 3:
+            cat, scheme, key = parts
+        else:
+            raise TypeError("keys must have less than 3 separators: %r" %
+                            (ckey,))
+        # validate & normalize the parts
+        if cat == "default":
+            cat = None
+        elif not cat and cat is not None:
+            raise TypeError("empty category: %r" % ckey)
+        if scheme == "context":
+            scheme = None
+        elif not scheme and scheme is not None:
+            raise TypeError("empty scheme: %r" % ckey)
+        if not key:
+            raise TypeError("empty option: %r" % ckey)
+        return cat, scheme, key
+
+    def update(self, *args, **kwds):
+        """Helper for quickly changing configuration.
+
+        This acts much like the :meth:`!dict.update` method:
+        it updates the context's configuration,
+        replacing the original value(s) for the specified keys,
+        and preserving the rest.
+        It accepts any :ref:`keyword <context-options>`
+        accepted by the :class:`!CryptContext` constructor.
+
+        .. versionadded:: 1.6
+
+        .. seealso:: :meth:`copy`
+        """
+        if args:
+            if len(args) > 1:
+                raise TypeError("expected at most one positional argument")
+            if kwds:
+                raise TypeError("positional arg and keywords mutually exclusive")
+            self.load(args[0], update=True)
+        elif kwds:
+            self.load(kwds, update=True)
+
+    # XXX: make this public? even just as flag to load?
+    # FIXME: this function suffered some bitrot in 1.6.1,
+    #        will need to be updated before works again.
+    ##def _simplify(self):
+    ##    "helper to remove redundant/unused options"
+    ##    # don't do anything if no schemes are defined
+    ##    if not self._schemes:
+    ##        return
+    ##
+    ##    def strip_items(target, filter):
+    ##        keys = [key for key,value in iteritems(target)
+    ##                if filter(key,value)]
+    ##        for key in keys:
+    ##            del target[key]
+    ##
+    ##    # remove redundant default.
+    ##    defaults = self._default_schemes
+    ##    if defaults.get(None) == self._schemes[0]:
+    ##        del defaults[None]
+    ##
+    ##    # remove options for unused schemes.
+    ##    scheme_options = self._scheme_options
+    ##    schemes = self._schemes + ("all",)
+    ##    strip_items(scheme_options, lambda k,v: k not in schemes)
+    ##
+    ##    # remove rendundant cat defaults.
+    ##    cur = self.default_scheme()
+    ##    strip_items(defaults, lambda k,v: k and v==cur)
+    ##
+    ##    # remove redundant category deprecations.
+    ##    # TODO: this should work w/ 'auto', but needs closer inspection
+    ##    deprecated = self._deprecated_schemes
+    ##    cur = self._deprecated_schemes.get(None)
+    ##    strip_items(deprecated, lambda k,v: k and v==cur)
+    ##
+    ##    # remove redundant category options.
+    ##    for scheme, config in iteritems(scheme_options):
+    ##        if None in config:
+    ##            cur = config[None]
+    ##            strip_items(config, lambda k,v: k and v==cur)
+    ##
+    ##    # XXX: anything else?
+
+    #===================================================================
+    # reading configuration
+    #===================================================================
+    def schemes(self, resolve=False):
+        """return schemes loaded into this CryptContext instance.
+
+        :type resolve: bool
+        :arg resolve:
+            if ``True``, will return a tuple of :class:`~passlib.ifc.PasswordHash`
+            objects instead of their names.
+
+        :returns:
+            returns tuple of the schemes configured for this context
+            via the *schemes* option.
+
+        .. versionadded:: 1.6
+            This was previously available as ``CryptContext().policy.schemes()``
+
+        .. seealso:: the :ref:`schemes <context-schemes-option>` option for usage example.
+        """
+        return self._config.handlers if resolve else self._config.schemes
+
+    # XXX: need to decide if exposing this would be useful to applications
+    #      in any way that isn't already served by to_dict();
+    #      and then decide whether to expose ability as deprecated_schemes(),
+    #      is_deprecated(), or a just add a schemes(deprecated=True) flag.
+    def _is_deprecated_scheme(self, scheme, category=None):
+        "helper used by unittests to check if scheme is deprecated"
+        return self._get_record(scheme, category).deprecated
+
+    def default_scheme(self, category=None, resolve=False):
+        """return name of scheme that :meth:`encrypt` will use by default.
+
+        :type resolve: bool
+        :arg resolve:
+            if ``True``, will return a :class:`~passlib.ifc.PasswordHash`
+            object instead of the name.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            If specified, this will return the catgory-specific default scheme instead.
+
+        :returns:
+            name of the default scheme.
+
+        .. seealso:: the :ref:`default <context-default-option>` option for usage example.
+
+        .. versionadded:: 1.6
+        """
+        # type check of category - handled by _get_record()
+        record = self._get_record(None, category)
+        return record.handler if resolve else record.scheme
+
+    # XXX: need to decide if exposing this would be useful in any way
+    ##def categories(self):
+    ##    """return user-categories with algorithm-specific options in this CryptContext.
+    ##
+    ##    this will always return a tuple.
+    ##    if no categories besides the default category have been configured,
+    ##    the tuple will be empty.
+    ##    """
+    ##    return self._config.categories
+
+    def handler(self, scheme=None, category=None):
+        """helper to resolve name of scheme -> :class:`~passlib.ifc.PasswordHash` object used by scheme.
+
+        :arg scheme:
+            This should identify the scheme to lookup.
+            If omitted or set to ``None``, this will return the handler
+            for the default scheme.
+
+        :arg category:
+            If a user category is specified, and no scheme is provided,
+            it will use the default for that category.
+            Otherwise this parameter is ignored.
+
+        :raises KeyError:
+            If the scheme does not exist OR is not being used within this context.
+
+        :returns:
+            :class:`~passlib.ifc.PasswordHash` object used to implement
+            the named scheme within this context (this will usually
+            be one of the objects from :mod:`passlib.hash`)
+
+        .. versionadded:: 1.6
+            This was previously available as ``CryptContext().policy.get_handler()``
+        """
+        try:
+            return self._get_record(scheme, category).handler
+        except KeyError:
+            pass
+        if self._config.handlers:
+            raise KeyError("crypt algorithm not found in this "
+                           "CryptContext instance: %r" % (scheme,))
+        else:
+            raise KeyError("no crypt algorithms loaded in this "
+                           "CryptContext instance")
+
+    def _get_unregistered_handlers(self):
+        "check if any handlers in this context aren't in the global registry"
+        return tuple(handler for handler in self._config.handlers
+                     if not _is_handler_registered(handler))
+
+    #===================================================================
+    # exporting config
+    #===================================================================
+    @staticmethod
+    def _render_config_key(key):
+        "convert 3-part config key to single string"
+        cat, scheme, option = key
+        if cat:
+            return "%s__%s__%s" % (cat, scheme or "context", option)
+        elif scheme:
+            return "%s__%s" % (scheme, option)
+        else:
+            return option
+
+    @staticmethod
+    def _render_ini_value(key, value):
+        "render value to string suitable for INI file"
+        # convert lists to comma separated lists
+        # (mainly 'schemes' & 'deprecated')
+        if isinstance(value, (list,tuple)):
+            value = ", ".join(value)
+
+        # convert numbers to strings
+        elif isinstance(value, num_types):
+            if isinstance(value, float) and key[2] == "vary_rounds":
+                value = ("%.2f" % value).rstrip("0") if value else "0"
+            else:
+                value = str(value)
+
+        assert isinstance(value, str), \
+               "expected string for key: %r %r" % (key, value)
+
+        # escape any percent signs.
+        return value.replace("%", "%%")
+
+    def to_dict(self, resolve=False):
+        """Return current configuration as a dictionary.
+
+        :type resolve: bool
+        :arg resolve:
+            if ``True``, the ``schemes`` key will contain a list of
+            a :class:`~passlib.ifc.PasswordHash` objects instead of just
+            their names.
+
+        This method dumps the current configuration of the CryptContext
+        instance. The key/value pairs should be in the format accepted
+        by the :class:`!CryptContext` class constructor, in fact
+        ``CryptContext(**myctx.to_dict())`` will create an exact copy of ``myctx``.
+        As an example::
+
+            >>> # you can dump the configuration of any crypt context...
+            >>> from passlib.apps import ldap_nocrypt_context
+            >>> ldap_nocrypt_context.to_dict()
+            {'schemes': ['ldap_salted_sha1',
+            'ldap_salted_md5',
+            'ldap_sha1',
+            'ldap_md5',
+            'ldap_plaintext']}
+
+        .. versionadded:: 1.6
+            This was previously available as ``CryptContext().policy.to_dict()``
+
+        .. seealso:: the :ref:`context-serialization-example` example in the tutorial.
+        """
+        # XXX: should resolve default to conditional behavior
+        # based on presence of unregistered handlers?
+        render_key = self._render_config_key
+        return dict((render_key(key), value)
+                    for key, value in self._config.iter_config(resolve))
+
+    def _write_to_parser(self, parser, section):
+        "helper to write to ConfigParser instance"
+        render_key = self._render_config_key
+        render_value = self._render_ini_value
+        parser.add_section(section)
+        for k,v in self._config.iter_config():
+            v = render_value(k, v)
+            k = render_key(k)
+            parser.set(section, k, v)
+
+    def to_string(self, section="passlib"):
+        """serialize to INI format and return as unicode string.
+
+        :param section:
+            name of INI section to output, defaults to ``"passlib"``.
+
+        :returns:
+            CryptContext configuration, serialized to a INI unicode string.
+
+        This function acts exactly like :meth:`to_dict`, except that it
+        serializes all the contents into a single human-readable string,
+        which can be hand edited, and/or stored in a file. The
+        output of this method is accepted by :meth:`from_string`,
+        :meth:`from_path`, and :meth:`load`. As an example::
+
+            >>> # you can dump the configuration of any crypt context...
+            >>> from passlib.apps import ldap_nocrypt_context
+            >>> print ldap_nocrypt_context.to_string()
+            [passlib]
+            schemes = ldap_salted_sha1, ldap_salted_md5, ldap_sha1, ldap_md5, ldap_plaintext
+
+        .. versionadded:: 1.6
+            This was previously available as ``CryptContext().policy.to_string()``
+
+        .. seealso:: the :ref:`context-serialization-example` example in the tutorial.
+        """
+        parser = SafeConfigParser()
+        self._write_to_parser(parser, section)
+        buf = NativeStringIO()
+        parser.write(buf)
+        unregistered = self._get_unregistered_handlers()
+        if unregistered:
+            buf.write((
+                "# NOTE: the %s handler(s) are not registered with Passlib,\n"
+                "# this string may not correctly reproduce the current configuration.\n\n"
+                ) % ", ".join(repr(handler.name) for handler in unregistered))
+        out = buf.getvalue()
+        if not PY3:
+            out = out.decode("utf-8")
+        return out
+
+    # XXX: is this useful enough to enable?
+    ##def write_to_path(self, path, section="passlib", update=False):
+    ##    "write to INI file"
+    ##    parser = ConfigParser()
+    ##    if update and os.path.exists(path):
+    ##        if not parser.read([path]):
+    ##            raise EnvironmentError("failed to read existing file")
+    ##        parser.remove_section(section)
+    ##    self._write_to_parser(parser, section)
+    ##    fh = file(path, "w")
+    ##    parser.write(fh)
+    ##    fh.close()
+
+    #===================================================================
+    # password hash api
+    #===================================================================
+
+    # NOTE: all the following methods do is look up the appropriate
+    #       _CryptRecord for a given (scheme,category) combination,
+    #       and hand off the real work to the record's methods,
+    #       which are optimized for the specific (scheme,category) configuration.
+    #
+    #       The record objects are cached inside the _CryptConfig
+    #       instance stored in self._config, and are retreived
+    #       via get_record() and identify_record().
+    #
+    #       _get_record() and _identify_record() are references
+    #       to _config methods of the same name,
+    #       stored in CryptContext for speed.
+
+    def _get_or_identify_record(self, hash, scheme=None, category=None):
+        "return record based on scheme, or failing that, by identifying hash"
+        if scheme:
+            if not isinstance(hash, base_string_types):
+                raise ExpectedStringError(hash, "hash")
+            return self._get_record(scheme, category)
+        else:
+            # hash typecheck handled by identify_record()
+            return self._identify_record(hash, category)
+
+    def needs_update(self, hash, scheme=None, category=None, secret=None):
+        """Check if hash needs to be replaced for some reason,
+        in which case the secret should be re-hashed.
+
+        This function is the core of CryptContext's support for hash migration:
+        This function takes in a hash string, and checks the scheme,
+        number of rounds, and other properties against the current policy.
+        It returns ``True`` if the hash is using a deprecated scheme,
+        or is otherwise outside of the bounds specified by the policy
+        (e.g. the number of rounds is lower than :ref:`min_rounds <context-min-rounds-option>`
+        configuration for that algorithm).
+        If so, the password should be re-encrypted using :meth:`encrypt`
+        Otherwise, it will return ``False``.
+
+        :type hash: unicode or bytes
+        :arg hash:
+            The hash string to examine.
+
+        :type scheme: str or None
+        :param scheme:
+
+            Optional scheme to use. Scheme must be one of the ones
+            configured for this context (see the
+            :ref:`schemes <context-schemes-option>` option).
+            If no scheme is specified, it will be identified
+            based on the value of *hash*.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            If specified, this will cause any category-specific defaults to
+            be used when determining if the hash needs to be updated
+            (e.g. is below the minimum rounds).
+
+        :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.
+
+        :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``.
+
+        .. 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()")
+    def hash_needs_update(self, hash, scheme=None, category=None):
+        """legacy alias for :meth:`needs_update`"""
+        return self.needs_update(hash, scheme, category)
+
+    def genconfig(self, scheme=None, category=None, **settings):
+        """Generate a config string for specified scheme.
+
+        This wraps the :meth:`~passlib.ifc.PasswordHash.genconfig`
+        method of the appropriate algorithm, using the default if
+        one is not specified.
+        The main difference between this and calling a hash's
+        :meth:`!genconfig` method directly is that this way, the CryptContext
+        will add in any hash-specific options, such as the default rounds.
+
+        :type scheme: str or None
+        :param scheme:
+
+            Optional scheme to use. Scheme must be one of the ones
+            configured for this context (see the
+            :ref:`schemes <context-schemes-option>` option).
+            If no scheme is specified, the configured default
+            will be used.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            If specified, this will cause any category-specific defaults to
+            be used when hashing the password (e.g. different default scheme,
+            different default rounds values, etc).
+
+        :param \*\*settings:
+            All additional keywords are passed to the appropriate handler,
+            and should match it's :attr:`~passlib.ifc.PasswordHash.setting_kwds`.
+
+        :returns:
+            A configuration string suitable for passing to :meth:`~CryptContext.genhash`,
+            encoding all the provided settings and defaults; or ``None``
+            if the selected algorithm doesn't support configuration strings.
+            The return value will always be a :class:`!str`.
+        """
+        return self._get_record(scheme, category).genconfig(**settings)
+
+    def genhash(self, secret, config, scheme=None, category=None, **kwds):
+        """Generate hash for the specified secret using another hash.
+
+        This wraps the :meth:`~passlib.ifc.PasswordHash.genhash`
+        method of the appropriate algorithm, identifying it based
+        on the provided hash / configuration if a scheme is not specified
+        explicitly.
+
+        :type secret: unicode or bytes
+        :arg secret:
+            the password to hash.
+
+        :type config: unicode or bytes
+        :arg hash:
+            The hash or configuration string to extract the settings and salt
+            from when hashing the password.
+
+        :type scheme: str or None
+        :param scheme:
+
+            Optional scheme to use. Scheme must be one of the ones
+            configured for this context (see the
+            :ref:`schemes <context-schemes-option>` option).
+            If no scheme is specified, it will be identified
+            based on the value of *config*.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            Ignored by this function, this parameter
+            is provided for symmetry with the other methods.
+
+        :param \*\*kwds:
+            All additional keywords are passed to the appropriate handler,
+            and should match it's :attr:`~passlib.ifc.PasswordHash.context_kwds`.
+
+        :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.genhash`
+              method throws an error based on *secret* or the provided *kwds*.
+        """
+        # XXX: could insert normalization to preferred unicode encoding here
+        return self._get_record(scheme, category).genhash(secret, config, **kwds)
+
+    def identify(self, hash, category=None, resolve=False, required=False):
+        """Attempt to identify which algorithm the hash belongs to.
+
+        Note that this will only consider the algorithms
+        currently configured for this context
+        (see the :ref:`schemes <context-schemes-option>` option).
+        All registered algorithms will be checked, from first to last,
+        and whichever one positively identifies the hash first will be returned.
+
+        :type hash: unicode or bytes
+        :arg hash:
+            The hash string to test.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            Ignored by this function, this parameter
+            is provided for symmetry with the other methods.
+
+        :type resolve: bool
+        :param resolve:
+            If ``True``, returns the hash handler itself,
+            instead of the name of the hash.
+
+        :type required: bool
+        :param required:
+            If ``True``, this will raise a ValueError if the hash
+            cannot be identified, instead of returning ``None``.
+
+        :returns:
+            The handler which first identifies the hash,
+            or ``None`` if none of the algorithms identify the hash.
+        """
+        record = self._identify_record(hash, category, required)
+        if record is None:
+            return None
+        elif resolve:
+            return record.handler
+        else:
+            return record.scheme
+
+    def encrypt(self, secret, scheme=None, category=None, **kwds):
+        """run secret through selected algorithm, returning resulting hash.
+
+        :type secret: unicode or bytes
+        :arg secret:
+            the password to hash.
+
+        :type scheme: str or None
+        :param scheme:
+
+            Optional scheme to use. Scheme must be one of the ones
+            configured for this context (see the
+            :ref:`schemes <context-schemes-option>` option).
+            If no scheme is specified, the configured default
+            will be used.
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            If specified, this will cause any category-specific defaults to
+            be used when hashing the password (e.g. different default scheme,
+            different default rounds values, etc).
+
+        :param \*\*kwds:
+            All other keyword options are passed to the selected algorithm's
+            :meth:`~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*.
+
+        .. seealso:: the :ref:`context-basic-example` example in the tutorial
+        """
+        # XXX: could insert normalization to preferred unicode encoding here
+        return self._get_record(scheme, category).encrypt(secret, **kwds)
+
+    def verify(self, secret, hash, scheme=None, category=None, **kwds):
+        """verify secret against an existing hash.
+
+        If no scheme is specified, this will attempt to identify
+        the scheme based on the contents of the provided hash
+        (limited to the schemes configured for this context).
+        It will then check whether the password verifies against the hash.
+
+        :type secret: unicode or bytes
+        :arg secret:
+            the secret to verify
+
+        :type secret: unicode or bytes
+        :arg hash:
+            hash string to compare to
+
+        :type scheme: str
+        :param scheme:
+            Optionally force context to use specific scheme.
+            This is usually not needed, as most hashes can be unambiguously
+            identified. Scheme must be one of the ones configured
+            for this context
+            (see the :ref:`schemes <context-schemes-option>` option).
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>` string.
+            This is mainly used when generating new hashes, it has little
+            effect when verifying; this keyword is mainly provided for symmetry.
+
+        :param \*\*kwds:
+            All additional keywords are passed to the appropriate handler,
+            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.
+
+        .. seealso:: the :ref:`context-basic-example` example in the tutorial
+        """
+        # XXX: have record strip context kwds if scheme doesn't use them?
+        # XXX: could insert normalization to preferred unicode encoding here
+        # XXX: what about supporting a setter() callback ala django 1.4 ?
+        record = self._get_or_identify_record(hash, scheme, category)
+        return record.verify(secret, hash, **kwds)
+
+    def verify_and_update(self, secret, hash, scheme=None, category=None, **kwds):
+        """verify password and re-hash the password if needed, all in a single call.
+
+        This is a convenience method which takes care of all the following:
+        first it verifies the password (:meth:`~CryptContext.verify`), if this is successfull
+        it checks if the hash needs updating (:meth:`~CryptContext.needs_update`), and if so,
+        re-hashes the password (:meth:`~CryptContext.encrypt`), returning the replacement hash.
+        This series of steps is a very common task for applications
+        which wish to update deprecated hashes, and this call takes
+        care of all 3 steps efficiently.
+
+        :type secret: unicode or bytes
+        :arg secret:
+            the secret to verify
+
+        :type secret: unicode or bytes
+        :arg hash:
+            hash string to compare to
+
+        :type scheme: str
+        :param scheme:
+            Optionally force context to use specific scheme.
+            This is usually not needed, as most hashes can be unambiguously
+            identified. Scheme must be one of the ones configured
+            for this context
+            (see the :ref:`schemes <context-schemes-option>` option).
+
+        :type category: str or None
+        :param category:
+            Optional :ref:`user category <user-categories>`.
+            If specified, this will cause any category-specific defaults to
+            be used if the password has to be re-hashed.
+
+        :param \*\*kwds:
+            all additional keywords are passed to the appropriate handler,
+            and should match it's :attr:`context keywords <passlib.hash.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:
+
+            * ``(False, None)`` indicates the secret failed to verify.
+            * ``(True, None)`` indicates the secret verified correctly,
+              and the hash does not need upgrading.
+            * ``(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.
+
+        .. seealso:: the :ref:`context-migration-example` example in the tutorial.
+        """
+        # XXX: have record strip context kwds if scheme doesn't use them?
+        # XXX: could insert normalization to preferred unicode encoding here.
+        record = self._get_or_identify_record(hash, scheme, category)
+        if not record.verify(secret, hash, **kwds):
+            return False, None
+        elif record.needs_update(hash, secret):
+            # NOTE: we re-encrypt with default scheme, not current one.
+            return True, self.encrypt(secret, None, category, **kwds)
+        else:
+            return True, None
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class LazyCryptContext(CryptContext):
+    """CryptContext subclass which doesn't load handlers until needed.
+
+    This is a subclass of CryptContext which takes in a set of arguments
+    exactly like CryptContext, but won't load any handlers
+    (or even parse it's arguments) until
+    the first time one of it's methods is accessed.
+
+    :arg schemes:
+        The first positional argument can be a list of schemes, or omitted,
+        just like CryptContext.
+
+    :param onload:
+
+        If a callable is passed in via this keyword,
+        it will be invoked at lazy-load time
+        with the following signature:
+        ``onload(**kwds) -> kwds``;
+        where ``kwds`` is all the additional kwds passed to LazyCryptContext.
+        It should perform any additional deferred initialization,
+        and return the final dict of options to be passed to CryptContext.
+
+        .. versionadded:: 1.6
+
+    :param create_policy:
+
+        .. deprecated:: 1.6
+            This option will be removed in Passlib 1.8,
+            applications should use ``onload`` instead.
+
+    :param kwds:
+
+        All additional keywords are passed to CryptContext;
+        or to the *onload* function (if provided).
+
+    This is mainly used internally by modules such as :mod:`passlib.apps`,
+    which define a large number of contexts, but only a few of them will be needed
+    at any one time. Use of this class saves the memory needed to import
+    the specified handlers until the context instance is actually accessed.
+    As well, it allows constructing a context at *module-init* time,
+    but using :func:`!onload()` to provide dynamic configuration
+    at *application-run* time.
+    """
+    _lazy_kwds = None
+
+    # NOTE: the way this class works changed in 1.6.
+    #       previously it just called _lazy_init() when ``.policy`` was
+    #       first accessed. now that is done whenever any of the public
+    #       attributes are accessed, and the class itself is changed
+    #       to a regular CryptContext, to remove the overhead once it's unneeded.
+
+    def __init__(self, schemes=None, **kwds):
+        if schemes is not None:
+            kwds['schemes'] = schemes
+        self._lazy_kwds = kwds
+
+    def _lazy_init(self):
+        kwds = self._lazy_kwds
+        if 'create_policy' in kwds:
+            warn("The CryptPolicy class, and LazyCryptContext's "
+                 "``create_policy`` keyword have been deprecated as of "
+                 "Passlib 1.6, and will be removed in Passlib 1.8; "
+                 "please use the ``onload`` keyword instead.",
+                 DeprecationWarning)
+            create_policy = kwds.pop("create_policy")
+            result = create_policy(**kwds)
+            policy = CryptPolicy.from_source(result, _warn=False)
+            kwds = policy._context.to_dict()
+        elif 'onload' in kwds:
+            onload = kwds.pop("onload")
+            kwds = onload(**kwds)
+        del self._lazy_kwds
+        super(LazyCryptContext, self).__init__(**kwds)
+        self.__class__ = CryptContext
+
+    def __getattribute__(self, attr):
+        if (not attr.startswith("_") or attr.startswith("__")) and \
+            self._lazy_kwds is not None:
+                self._lazy_init()
+        return object.__getattribute__(self, attr)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/exc.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,175 @@
+"""passlib.exc -- exceptions & warnings raised by passlib"""
+#=============================================================================
+# exceptions
+#=============================================================================
+class MissingBackendError(RuntimeError):
+    """Error raised if multi-backend handler has no available backends;
+    or if specifically requested backend is not available.
+
+    :exc:`!MissingBackendError` derives
+    from :exc:`RuntimeError`, since it usually indicates
+    lack of an external library or OS feature.
+    This is primarily raised by handlers which depend on
+    external libraries (which is currently just
+    :class:`~passlib.hash.bcrypt`).
+    """
+
+class PasswordSizeError(ValueError):
+    """Error raised if a password exceeds the maximum size allowed
+    by Passlib (4096 characters).
+
+    Many password hash algorithms take proportionately larger amounts of time and/or
+    memory depending on the size of the password provided. This could present
+    a potential denial of service (DOS) situation if a maliciously large
+    password is provided to an application. Because of this, Passlib enforces
+    a maximum size limit, but one which should be *much* larger
+    than any legitimate password. :exc:`!PasswordSizeError` derives
+    from :exc:`!ValueError`.
+
+    .. note::
+        Applications wishing to use a different limit should set the
+        ``PASSLIB_MAX_PASSWORD_SIZE`` environmental variable before
+        Passlib is loaded. The value can be any large positive integer.
+
+    .. versionadded:: 1.6
+    """
+    def __init__(self):
+        ValueError.__init__(self, "password exceeds maximum allowed size")
+
+    # this also prevents a glibc crypt segfault issue, detailed here ...
+    # http://www.openwall.com/lists/oss-security/2011/11/15/1
+
+#=============================================================================
+# warnings
+#=============================================================================
+class PasslibWarning(UserWarning):
+    """base class for Passlib's user warnings.
+
+    .. versionadded:: 1.6
+    """
+
+class PasslibConfigWarning(PasslibWarning):
+    """Warning issued when non-fatal issue is found related to the configuration
+    of a :class:`~passlib.context.CryptContext` instance.
+
+    This occurs primarily in one of two cases:
+
+    * The CryptContext contains rounds limits which exceed the hard limits
+      imposed by the underlying algorithm.
+    * An explicit rounds value was provided which exceeds the limits
+      imposed by the CryptContext.
+
+    In both of these cases, the code will perform correctly & securely;
+    but the warning is issued as a sign the configuration may need updating.
+    """
+
+class PasslibHashWarning(PasslibWarning):
+    """Warning issued when non-fatal issue is found with parameters
+    or hash string passed to a passlib hash class.
+
+    This occurs primarily in one of two cases:
+
+    * A rounds value or other setting was explicitly provided which
+      exceeded the handler's limits (and has been clamped
+      by the :ref:`relaxed<relaxed-keyword>` flag).
+
+    * A malformed hash string was encountered which (while parsable)
+      should be re-encoded.
+    """
+
+class PasslibRuntimeWarning(PasslibWarning):
+    """Warning issued when something unexpected happens during runtime.
+
+    The fact that it's a warning instead of an error means Passlib
+    was able to correct for the issue, but that it's anonmalous enough
+    that the developers would love to hear under what conditions it occurred.
+    """
+
+class PasslibSecurityWarning(PasslibWarning):
+    """Special warning issued when Passlib encounters something
+    that might affect security.
+    """
+
+#=============================================================================
+# error constructors
+#
+# note: these functions are used by the hashes in Passlib to raise common
+# error messages. They are currently just functions which return ValueError,
+# rather than subclasses of ValueError, since the specificity isn't needed
+# yet; and who wants to import a bunch of error classes when catching
+# ValueError will do?
+#=============================================================================
+
+def _get_name(handler):
+    return handler.name if handler else "<unnamed>"
+
+#------------------------------------------------------------------------
+# generic helpers
+#------------------------------------------------------------------------
+def type_name(value):
+    "return pretty-printed string containing name of value's type"
+    cls = value.__class__
+    if cls.__module__ and cls.__module__ not in ["__builtin__", "builtins"]:
+        return "%s.%s" % (cls.__module__, cls.__name__)
+    elif value is None:
+        return 'None'
+    else:
+        return cls.__name__
+
+def ExpectedTypeError(value, expected, param):
+    "error message when param was supposed to be one type, but found another"
+    # NOTE: value is never displayed, since it may sometimes be a password.
+    name = type_name(value)
+    return TypeError("%s must be %s, not %s" % (param, expected, name))
+
+def ExpectedStringError(value, param):
+    "error message when param was supposed to be unicode or bytes"
+    return ExpectedTypeError(value, "unicode or bytes", param)
+
+#------------------------------------------------------------------------
+# encrypt/verify parameter errors
+#------------------------------------------------------------------------
+def MissingDigestError(handler=None):
+    "raised when verify() method gets passed config string instead of hash"
+    name = _get_name(handler)
+    return ValueError("expected %s hash, got %s config string instead" %
+                     (name, name))
+
+def NullPasswordError(handler=None):
+    "raised by OS crypt() supporting hashes, which forbid NULLs in password"
+    name = _get_name(handler)
+    return ValueError("%s does not allow NULL bytes in password" % name)
+
+#------------------------------------------------------------------------
+# errors when parsing hashes
+#------------------------------------------------------------------------
+def InvalidHashError(handler=None):
+    "error raised if unrecognized hash provided to handler"
+    return ValueError("not a valid %s hash" % _get_name(handler))
+
+def MalformedHashError(handler=None, reason=None):
+    "error raised if recognized-but-malformed hash provided to handler"
+    text = "malformed %s hash" % _get_name(handler)
+    if reason:
+        text = "%s (%s)" % (text, reason)
+    return ValueError(text)
+
+def ZeroPaddedRoundsError(handler=None):
+    "error raised if hash was recognized but contained zero-padded rounds field"
+    return MalformedHashError(handler, "zero-padded rounds")
+
+#------------------------------------------------------------------------
+# settings / hash component errors
+#------------------------------------------------------------------------
+def ChecksumSizeError(handler, raw=False):
+    "error raised if hash was recognized, but checksum was wrong size"
+    # TODO: if handler.use_defaults is set, this came from app-provided value,
+    # not from parsing a hash string, might want different error msg.
+    checksum_size = handler.checksum_size
+    unit = "bytes" if raw else "chars"
+    reason = "checksum must be exactly %d %s" % (checksum_size, unit)
+    return MalformedHashError(handler, reason)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/ext/__init__.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/ext/django/__init__.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,6 @@
+"""passlib.ext.django.models -- monkeypatch django hashing framework
+
+this plugin monkeypatches django's hashing framework
+so that it uses a passlib context object, allowing handling of arbitrary
+hashes in Django databases.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/ext/django/models.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,285 @@
+"""passlib.ext.django.models -- monkeypatch django hashing framework"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+from django import VERSION
+from django.conf import settings
+# pkg
+from passlib.context import CryptContext
+from passlib.exc import ExpectedTypeError
+from passlib.ext.django.utils import _PatchManager, hasher_to_passlib_name, \
+                                     get_passlib_hasher, get_preset_config
+from passlib.utils.compat import callable, unicode, bytes
+# local
+__all__ = ["password_context"]
+
+#=============================================================================
+# global attrs
+#=============================================================================
+
+# the context object which this patches contrib.auth to use for password hashing.
+# configuration controlled by ``settings.PASSLIB_CONFIG``.
+password_context = CryptContext()
+
+# function mapping User objects -> passlib user category.
+# may be overridden via ``settings.PASSLIB_GET_CATEGORY``.
+def _get_category(user):
+    """default get_category() implementation"""
+    if user.is_superuser:
+        return "superuser"
+    elif user.is_staff:
+        return "staff"
+    else:
+        return None
+
+# object used to track state of patches applied to django.
+_manager = _PatchManager(log=logging.getLogger(__name__ + "._manager"))
+
+# patch status
+_patched = False
+
+#=============================================================================
+# applying & removing the patches
+#=============================================================================
+def _apply_patch():
+    """monkeypatch django's password handling to use ``passlib_context``,
+    assumes the caller will configure the object.
+    """
+    #
+    # setup constants
+    #
+    log.debug("preparing to monkeypatch 'django.contrib.auth' ...")
+    global _patched
+    assert not _patched, "monkeypatching already applied"
+    HASHERS_PATH = "django.contrib.auth.hashers"
+    MODELS_PATH = "django.contrib.auth.models"
+    USER_PATH = MODELS_PATH + ":User"
+    FORMS_PATH = "django.contrib.auth.forms"
+
+    #
+    # import UNUSUABLE_PASSWORD and is_password_usuable() helpers
+    # (providing stubs for older django versions)
+    #
+    if VERSION < (1,4):
+        has_hashers = False
+        if VERSION < (1,0):
+            UNUSABLE_PASSWORD = "!"
+        else:
+            from django.contrib.auth.models import UNUSABLE_PASSWORD
+
+        def is_password_usable(encoded):
+            return (encoded is not None and encoded != UNUSABLE_PASSWORD)
+
+        def is_valid_secret(secret):
+            return secret is not None
+
+    else:
+        has_hashers = True
+        from django.contrib.auth.hashers import UNUSABLE_PASSWORD, \
+                                                is_password_usable
+
+        def is_valid_secret(secret):
+            # NOTE: changed in 1.4 - empty passwords no longer valid.
+            return bool(secret)
+
+    #
+    # backport ``User.set_unusable_password()`` for Django 0.9
+    # (simplifies rest of the code)
+    #
+    if not hasattr(_manager.getorig(USER_PATH), "set_unusable_password"):
+        assert VERSION < (1,0)
+
+        @_manager.monkeypatch(USER_PATH)
+        def set_unusable_password(user):
+            user.password = UNUSABLE_PASSWORD
+
+        @_manager.monkeypatch(USER_PATH)
+        def has_usable_password(user):
+            return is_password_usable(user.password)
+
+    #
+    # patch ``User.set_password() & ``User.check_password()`` to use
+    # context & get_category (would just leave these as wrappers for hashers
+    # module under django 1.4, but then we couldn't pass User object into
+    # get_category very easily)
+    #
+    @_manager.monkeypatch(USER_PATH)
+    def set_password(user, password):
+        "passlib replacement for User.set_password()"
+        if is_valid_secret(password):
+            # NOTE: pulls _get_category from module globals
+            cat = _get_category(user)
+            user.password = password_context.encrypt(password, category=cat)
+        else:
+            user.set_unusable_password()
+
+    @_manager.monkeypatch(USER_PATH)
+    def check_password(user, password):
+        "passlib replacement for User.check_password()"
+        hash = user.password
+        if not is_valid_secret(password) or not is_password_usable(hash):
+            return False
+        # NOTE: pulls _get_category from module globals
+        cat = _get_category(user)
+        ok, new_hash = password_context.verify_and_update(password, hash,
+                                                          category=cat)
+        if ok and new_hash is not None:
+            # migrate to new hash if needed.
+            user.password = new_hash
+            user.save()
+        return ok
+
+    #
+    # override check_password() with our own implementation
+    #
+    @_manager.monkeypatch(HASHERS_PATH, enable=has_hashers)
+    @_manager.monkeypatch(MODELS_PATH)
+    def check_password(password, encoded, setter=None, preferred="default"):
+        "passlib replacement for check_password()"
+        # XXX: this currently ignores "preferred" keyword, since it's purpose
+        #      was for hash migration, and that's handled by the context.
+        if not is_valid_secret(password) or not is_password_usable(encoded):
+            return False
+        ok = password_context.verify(password, encoded)
+        if ok and setter and password_context.needs_update(encoded):
+            setter(password)
+        return ok
+
+    #
+    # patch the other functions defined in the ``hashers`` module, as well
+    # as any other known locations where they're imported within ``contrib.auth``
+    #
+    if has_hashers:
+        @_manager.monkeypatch(HASHERS_PATH)
+        @_manager.monkeypatch(MODELS_PATH)
+        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:
+                kwds['salt'] = salt
+            if hasher != "default":
+                kwds['scheme'] = hasher_to_passlib_name(hasher)
+            return password_context.encrypt(password, **kwds)
+
+        @_manager.monkeypatch(HASHERS_PATH)
+        @_manager.monkeypatch(FORMS_PATH)
+        def get_hasher(algorithm="default"):
+            "passlib replacement for get_hasher()"
+            if algorithm == "default":
+                scheme = None
+            else:
+                scheme = hasher_to_passlib_name(algorithm)
+            handler = password_context.handler(scheme)
+            return get_passlib_hasher(handler)
+
+        # NOTE: custom helper that doesn't exist in django proper
+        #       (though submitted a patch - https://code.djangoproject.com/ticket/18184)
+        @_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)
+
+    _patched = True
+    log.debug("... finished monkeypatching django")
+
+def _remove_patch():
+    """undo the django monkeypatching done by this module.
+    offered as a last resort if it's ever needed.
+
+    .. warning::
+        This may cause problems if any other Django modules have imported
+        their own copies of the patched functions, though the patched
+        code has been designed to throw an error as soon as possible in
+        this case.
+    """
+    global _patched
+    if _patched:
+        log.debug("removing django monkeypatching...")
+        _manager.unpatch_all(unpatch_conflicts=True)
+        password_context.load({})
+        _patched = False
+        log.debug("...finished removing django monkeypatching")
+        return True
+    if _manager: # pragma: no cover -- sanity check
+        log.warning("reverting partial monkeypatching of django...")
+        _manager.unpatch_all()
+        password_context.load({})
+        log.debug("...finished removing django monkeypatching")
+        return True
+    log.debug("django not monkeypatched")
+    return False
+
+#=============================================================================
+# main code
+#=============================================================================
+def _load():
+    global _get_category
+
+    # TODO: would like to add support for inheriting config from a preset
+    #       (or from existing hasher state) and letting PASSLIB_CONFIG
+    #       be an update, not a replacement.
+
+    # TODO: wrap and import any custom hashers as passlib handlers,
+    #       so they could be used in the passlib config.
+
+    # load config from settings
+    _UNSET = object()
+    config = getattr(settings, "PASSLIB_CONFIG", _UNSET)
+    if config is _UNSET:
+        # XXX: should probably deprecate this alias
+        config = getattr(settings, "PASSLIB_CONTEXT", _UNSET)
+    if config is _UNSET:
+        config = "passlib-default"
+    if config is None:
+        warn("setting PASSLIB_CONFIG=None is deprecated, "
+             "and support will be removed in Passlib 1.8, "
+             "use PASSLIB_CONFIG='disabled' instead.",
+             DeprecationWarning)
+        config = "disabled"
+    elif not isinstance(config, (unicode, bytes, dict)):
+        raise ExpectedTypeError(config, "str or dict", "PASSLIB_CONFIG")
+
+    # load custom category func (if any)
+    get_category = getattr(settings, "PASSLIB_GET_CATEGORY", None)
+    if get_category and not callable(get_category):
+        raise ExpectedTypeError(get_category, "callable", "PASSLIB_GET_CATEGORY")
+
+    # check if we've been disabled
+    if config == "disabled":
+        if _patched: # pragma: no cover -- sanity check
+            log.error("didn't expect monkeypatching would be applied!")
+        _remove_patch()
+        return
+
+    # resolve any preset aliases
+    if isinstance(config, str) and '\n' not in config:
+        config = get_preset_config(config)
+
+    # setup context
+    _apply_patch()
+    password_context.load(config)
+    if get_category:
+        # NOTE: _get_category is module global which is read by
+        #       monkeypatched functions constructed by _apply_patch()
+        _get_category = get_category
+    log.debug("passlib.ext.django loaded")
+
+# wrap load function so we can undo any patching if something goes wrong
+try:
+    _load()
+except:
+    _remove_patch()
+    raise
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/ext/django/utils.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,470 @@
+"""passlib.ext.django.utils - helper functions used by this plugin"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import logging; log = logging.getLogger(__name__)
+from weakref import WeakKeyDictionary
+from warnings import warn
+# site
+try:
+    from django import VERSION as DJANGO_VERSION
+    log.debug("found django %r installation", DJANGO_VERSION)
+except ImportError:
+    log.debug("django installation not found")
+    DJANGO_VERSION = ()
+# pkg
+from passlib.context import CryptContext
+from passlib.exc import PasslibRuntimeWarning
+from passlib.registry import get_crypt_handler, list_crypt_handlers
+from passlib.utils import classproperty
+from passlib.utils.compat import bytes, get_method_function, iteritems
+# local
+__all__ = [
+    "get_preset_config",
+    "get_passlib_hasher",
+]
+
+#=============================================================================
+# default policies
+#=============================================================================
+def get_preset_config(name):
+    """Returns configuration string for one of the preset strings
+    supported by the ``PASSLIB_CONFIG`` setting.
+    Currently supported presets:
+
+    * ``"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-1.0"`` - config used by stock Django 1.0 - 1.3 installs
+    * ``"django-1.4"`` -config used by stock Django 1.4 installs
+    """
+    # TODO: add preset which includes HASHERS + PREFERRED_HASHERS,
+    #       after having imported any custom hashers. "django-current"
+    if name == "django-default":
+        if (0,0) < DJANGO_VERSION < (1,4):
+            name = "django-1.0"
+        else:
+            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()
+    if name == "passlib-default":
+        return PASSLIB_DEFAULT
+    raise ValueError("unknown preset config name: %r" % name)
+
+# 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,
+; and three common modular crypt format hashes.
+schemes =
+    django_pbkdf2_sha256, django_pbkdf2_sha1, django_bcrypt,
+    django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5,
+    sha512_crypt, bcrypt, phpass
+
+; default scheme to use for new hashes
+default = django_pbkdf2_sha256
+
+; hashes using these schemes will automatically be re-hashed
+; when the user logs in (currently all django 1.0 hashes)
+deprecated =
+    django_pbkdf2_sha1, django_salted_sha1, django_salted_md5,
+    django_des_crypt, hex_md5
+
+; sets some common options, including minimum rounds for two primary hashes.
+; if a hash has less than this number of rounds, it will be re-hashed.
+all__vary_rounds = 0.05
+sha512_crypt__min_rounds = 80000
+django_pbkdf2_sha256__min_rounds = 10000
+
+; set somewhat stronger iteration counts for ``User.is_staff``
+staff__sha512_crypt__default_rounds = 100000
+staff__django_pbkdf2_sha256__default_rounds = 12500
+
+; and even stronger ones for ``User.is_superuser``
+superuser__sha512_crypt__default_rounds = 120000
+superuser__django_pbkdf2_sha256__default_rounds = 15000
+"""
+
+#=============================================================================
+# translating passlib names <-> hasher names
+#=============================================================================
+
+# prefix used to shoehorn passlib's handler names into django hasher namespace;
+# allows get_hasher() to be meaningfully called even if passlib handler
+# is the one being used.
+PASSLIB_HASHER_PREFIX = "passlib_"
+
+# prefix all the django-specific hash formats are stored under w/in passlib;
+# all of these hashes should expose their hasher name via ``.django_name``.
+DJANGO_PASSLIB_PREFIX = "django_"
+
+# non-django-specific hashes which also expose ``.django_name``.
+_other_django_hashes = ["hex_md5"]
+
+def passlib_to_hasher_name(passlib_name):
+    "convert passlib handler name -> hasher name"
+    handler = get_crypt_handler(passlib_name)
+    if hasattr(handler, "django_name"):
+        return handler.django_name
+    return PASSLIB_HASHER_PREFIX + passlib_name
+
+def hasher_to_passlib_name(hasher_name):
+    "convert hasher name -> passlib handler name"
+    if hasher_name.startswith(PASSLIB_HASHER_PREFIX):
+        return hasher_name[len(PASSLIB_HASHER_PREFIX):]
+    for name in list_crypt_handlers():
+        if name.startswith(DJANGO_PASSLIB_PREFIX) or name in _other_django_hashes:
+            handler = get_crypt_handler(name)
+            if getattr(handler, "django_name", None) == hasher_name:
+                return name
+    # XXX: this should only happen for custom hashers that have been registered.
+    #      _HasherHandler (below) is work in progress that would fix this.
+    raise ValueError("can't translate hasher name to passlib name: %r" %
+                     hasher_name)
+
+#=============================================================================
+# wrapping passlib handlers as django hashers
+#=============================================================================
+_FAKE_SALT = "--fake-salt--"
+
+class _HasherWrapper(object):
+    """helper for wrapping passlib handlers in Hasher-compatible class."""
+
+    # filled in by subclass, drives the other methods.
+    passlib_handler = None
+
+    @classproperty
+    def algorithm(cls):
+        assert not hasattr(cls.passlib_handler, "django_name")
+        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
+
+    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:
+            kwds['salt'] = salt
+        if iterations is not None:
+            kwds['rounds'] = iterations
+        return self.passlib_handler.encrypt(password, **kwds)
+
+    _translate_kwds = dict(checksum="hash", rounds="iterations")
+
+    def safe_summary(self, encoded):
+        from django.contrib.auth.hashers import mask_hash, _, SortedDict
+        handler = self.passlib_handler
+        items = [
+            # since this is user-facing, we're reporting passlib's name,
+            # without the distracting PASSLIB_HASHER_PREFIX prepended.
+            (_('algorithm'), handler.name),
+        ]
+        if hasattr(handler, "parsehash"):
+            kwds = handler.parsehash(encoded, sanitize=mask_hash)
+            for key, value in iteritems(kwds):
+                key = self._translate_kwds.get(key, key)
+                items.append((_(key), value))
+        return SortedDict(items)
+
+# cache of hasher wrappers generated by get_passlib_hasher()
+_hasher_cache = WeakKeyDictionary()
+
+def get_passlib_hasher(handler):
+    """create *Hasher*-compatible wrapper for specified passlib hash.
+
+    This takes in the name of a passlib hash (or the handler object itself),
+    and returns a wrapper instance which should be compatible with
+    Django 1.4's Hashers framework.
+
+    If the named hash corresponds to one of Django's builtin hashers,
+    an instance of the real hasher class will be returned.
+
+    Note that the format of the handler won't be altered,
+    so will probably not be compatible with Django's algorithm format,
+    so the monkeypatch provided by this plugin must have been applied.
+
+    .. note::
+        This function requires Django 1.4 or later.
+    """
+    if DJANGO_VERSION < (1,4):
+        raise RuntimeError("get_passlib_hasher() requires Django >= 1.4")
+    if isinstance(handler, str):
+        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)
+    if handler.name == "django_disabled":
+        raise ValueError("can't wrap unusable-password handler")
+    try:
+        return _hasher_cache[handler]
+    except KeyError:
+        name = "Passlib_%s_PasswordHasher" % handler.name.title()
+        cls = type(name, (_HasherWrapper,), dict(passlib_handler=handler))
+        hasher = _hasher_cache[handler] = cls()
+        return hasher
+
+def _get_hasher(algorithm):
+    "wrapper to call django.contrib.auth.hashers:get_hasher()"
+    import sys
+    module = sys.modules.get("passlib.ext.django.models")
+    if module is None:
+        # we haven't patched django, so just import directly
+        from django.contrib.auth.hashers import get_hasher
+    else:
+        # we've patched django, so have to use patch manager to retreive
+        # original get_hasher() function...
+        get_hasher = module._manager.getorig("django.contrib.auth.hashers:get_hasher")
+    return get_hasher(algorithm)
+
+#=============================================================================
+# adapting django hashers -> passlib handlers
+#=============================================================================
+# TODO: this code probably halfway works, mainly just needs
+#       a routine to read HASHERS and PREFERRED_HASHER.
+
+##from passlib.registry import register_crypt_handler
+##from passlib.utils import classproperty, to_native_str, to_unicode
+##from passlib.utils.compat import unicode
+##
+##
+##class _HasherHandler(object):
+##    "helper for wrapping Hasher instances as passlib handlers"
+##    # FIXME: this generic wrapper doesn't handle custom settings
+##    # FIXME: genconfig / genhash not supported.
+##
+##    def __init__(self, hasher):
+##        self.django_hasher = hasher
+##        if hasattr(hasher, "iterations"):
+##            # assume encode() accepts an "iterations" parameter.
+##            # fake min/max rounds
+##            self.min_rounds = 1
+##            self.max_rounds = 0xFFFFffff
+##            self.default_rounds = self.django_hasher.iterations
+##            self.setting_kwds += ("rounds",)
+##
+##    # hasher instance - filled in by constructor
+##    django_hasher = None
+##
+##    setting_kwds = ("salt",)
+##    context_kwds = ()
+##
+##    @property
+##    def name(self):
+##        # XXX: need to make sure this wont' collide w/ builtin django hashes.
+##        #      maybe by renaming this to django compatible aliases?
+##        return DJANGO_PASSLIB_PREFIX + self.django_name
+##
+##    @property
+##    def django_name(self):
+##        # expose this so hasher_to_passlib_name() extracts original name
+##        return self.django_hasher.algorithm
+##
+##    @property
+##    def ident(self):
+##        # this should always be correct, as django relies on ident prefix.
+##        return unicode(self.django_name + "$")
+##
+##    @property
+##    def identify(self, hash):
+##        # this should always work, as django relies on ident prefix.
+##        return to_unicode(hash, "latin-1", "hash").startswith(self.ident)
+##
+##    @property
+##    def genconfig(self):
+##        # XXX: not sure how to support this.
+##        return None
+##
+##    @property
+##    def genhash(self, secret, config):
+##        if config is not None:
+##            # XXX: not sure how to support this.
+##            raise NotImplementedError("genhash() for hashers not implemented")
+##        return self.encrypt(secret)
+##
+##    @property
+##    def encrypt(self, secret, salt=None, **kwds):
+##        # NOTE: from how make_password() is coded, all hashers
+##        #       should have salt param. but only some will have
+##        #       'iterations' parameter.
+##        opts = {}
+##        if 'rounds' in self.setting_kwds and 'rounds' in kwds:
+##            opts['iterations'] = kwds.pop("rounds")
+##        if kwds:
+##            raise TypeError("unexpected keyword arguments: %r" % list(kwds))
+##        if isinstance(secret, unicode):
+##            secret = secret.encode("utf-8")
+##        if salt is None:
+##            salt = self.django_hasher.salt()
+##        return to_native_str(self.django_hasher(secret, salt, **opts))
+##
+##    @property
+##    def verify(self, secret, hash):
+##        hash = to_native_str(hash, "utf-8", "hash")
+##        if isinstance(secret, unicode):
+##            secret = secret.encode("utf-8")
+##        return self.django_hasher.verify(secret, hash)
+##
+##def register_hasher(hasher):
+##    handler = _HasherHandler(hasher)
+##    register_crypt_handler(handler)
+##    return handler
+
+#=============================================================================
+# monkeypatch helpers
+#=============================================================================
+# private singleton indicating lack-of-value
+_UNSET = object()
+
+class _PatchManager(object):
+    "helper to manage monkeypatches and run sanity checks"
+
+    # NOTE: this could easily use a dict interface,
+    #       but keeping it distinct to make clear that it's not a dict,
+    #       since it has important side-effects.
+
+    #===================================================================
+    # init and support
+    #===================================================================
+    def __init__(self, log=None):
+        # map of key -> (original value, patched value)
+        # original value may be _UNSET
+        self.log = log or logging.getLogger(__name__ + "._PatchManager")
+        self._state = {}
+
+    # bool value tests if any patches are currently applied.
+    __bool__ = __nonzero__ = lambda self: bool(self._state)
+
+    def _import_path(self, path):
+        "retrieve obj and final attribute name from resource path"
+        name, attr = path.split(":")
+        obj = __import__(name, fromlist=[attr], level=0)
+        while '.' in attr:
+           head, attr = attr.split(".", 1)
+           obj = getattr(obj, head)
+        return obj, attr
+
+    @staticmethod
+    def _is_same_value(left, right):
+        "check if two values are the same (stripping method wrappers, etc)"
+        return get_method_function(left) == get_method_function(right)
+
+    #===================================================================
+    # reading
+    #===================================================================
+    def _get_path(self, key, default=_UNSET):
+        obj, attr = self._import_path(key)
+        return getattr(obj, attr, default)
+
+    def get(self, path, default=None):
+        "return current value for path"
+        return self._get_path(path, default)
+
+    def getorig(self, path, default=None):
+        "return original (unpatched) value for path"
+        try:
+            value, _= self._state[path]
+        except KeyError:
+            value = self._get_path(path)
+        return default if value is _UNSET else value
+
+    def check_all(self, strict=False):
+        """run sanity check on all keys, issue warning if out of sync"""
+        same = self._is_same_value
+        for path, (orig, expected) in iteritems(self._state):
+            if same(self._get_path(path), expected):
+                continue
+            msg = "another library has patched resource: %r" % path
+            if strict:
+                raise RuntimeError(msg)
+            else:
+                warn(msg, PasslibRuntimeWarning)
+
+    #===================================================================
+    # patching
+    #===================================================================
+    def _set_path(self, path, value):
+        obj, attr = self._import_path(path)
+        if value is _UNSET:
+            if hasattr(obj, attr):
+                delattr(obj, attr)
+        else:
+            setattr(obj, attr, value)
+
+    def patch(self, path, value):
+        "monkeypatch object+attr at <path> to have <value>, stores original"
+        assert value != _UNSET
+        current = self._get_path(path)
+        try:
+            orig, expected = self._state[path]
+        except KeyError:
+            self.log.debug("patching resource: %r", path)
+            orig = current
+        else:
+            self.log.debug("modifying resource: %r", path)
+            if not self._is_same_value(current, expected):
+                warn("overridding resource another library has patched: %r"
+                     % path, PasslibRuntimeWarning)
+        self._set_path(path, value)
+        self._state[path] = (orig, value)
+
+    ##def patch_many(self, **kwds):
+    ##    "override specified resources with new values"
+    ##    for path, value in iteritems(kwds):
+    ##        self.patch(path, value)
+
+    def monkeypatch(self, parent, name=None, enable=True):
+        "function decorator which patches function of same name in <parent>"
+        def builder(func):
+            if enable:
+                sep = "." if ":" in parent else ":"
+                path = parent + sep + (name or func.__name__)
+                self.patch(path, func)
+            return func
+        return builder
+
+    #===================================================================
+    # unpatching
+    #===================================================================
+    def unpatch(self, path, unpatch_conflicts=True):
+        try:
+            orig, expected = self._state[path]
+        except KeyError:
+            return
+        current = self._get_path(path)
+        self.log.debug("unpatching resource: %r", path)
+        if not self._is_same_value(current, expected):
+            if unpatch_conflicts:
+                warn("reverting resource another library has patched: %r"
+                     % path, PasslibRuntimeWarning)
+            else:
+                warn("not reverting resource another library has patched: %r"
+                     % path, PasslibRuntimeWarning)
+                del self._state[path]
+                return
+        self._set_path(path, orig)
+        del self._state[path]
+
+    def unpatch_all(self, **kwds):
+        for key in list(self._state):
+            self.unpatch(key, **kwds)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/__init__.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,1 @@
+"""passlib.handlers -- holds implementations of all passlib's builtin hash formats"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/bcrypt.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,335 @@
+"""passlib.bcrypt -- implementation of OpenBSD's BCrypt algorithm.
+
+TODO:
+
+* support 2x and altered-2a hashes?
+  http://www.openwall.com/lists/oss-security/2011/06/27/9
+
+* deal with lack of PY3-compatibile c-ext implementation
+"""
+#=============================================================================
+# imports
+#=============================================================================
+from __future__ import with_statement, absolute_import
+# core
+import os
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+try:
+    from bcrypt import hashpw as pybcrypt_hashpw
+except ImportError: # pragma: no cover
+    pybcrypt_hashpw = 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.compat import bytes, b, u, uascii_to_str, unicode, str_to_uascii
+import passlib.utils.handlers as uh
+
+# local
+__all__ = [
+    "bcrypt",
+]
+
+#=============================================================================
+# support funcs & constants
+#=============================================================================
+_builtin_bcrypt = None
+
+def _load_builtin():
+    global _builtin_bcrypt
+    if _builtin_bcrypt is None:
+        from passlib.utils._blowfish import raw_bcrypt as _builtin_bcrypt
+
+IDENT_2 = u("$2$")
+IDENT_2A = u("$2a$")
+IDENT_2X = u("$2x$")
+IDENT_2Y = u("$2y$")
+_BNULL = b('\x00')
+
+#=============================================================================
+# handler
+#=============================================================================
+class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.GenericHandler):
+    """This class implements the BCrypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 12, must be between 4 and 31, inclusive.
+        This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`
+        -- increasing the rounds by +1 will double the amount of time taken.
+
+    :type ident: str
+    :param ident:
+        Specifies which version of the BCrypt algorithm will be used when creating a new hash.
+        Typically this option is not needed, as the default (``"2a"``) is usually the correct choice.
+        If specified, it must be one of the following:
+
+        * ``"2"`` - the first revision of BCrypt, which suffers from a minor security flaw and is generally not used anymore.
+        * ``"2a"`` - latest revision of the official BCrypt algorithm, and the current default.
+        * ``"2y"`` - format specific to the *crypt_blowfish* BCrypt implementation,
+          identical to ``"2a"`` in all but name.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+
+    .. versionchanged:: 1.6
+        This class now supports ``"2y"`` hashes, and recognizes
+        (but does not support) the broken ``"2x"`` hashes.
+        (see the :ref:`crypt_blowfish bug <crypt-blowfish-bug>`
+        for details).
+
+    .. versionchanged:: 1.6
+        Added a pure-python backend.
+    """
+
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "bcrypt"
+    setting_kwds = ("salt", "rounds", "ident")
+    checksum_size = 31
+    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}
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 22
+    salt_chars = bcrypt64.charmap
+        # NOTE: 22nd salt char must be in bcrypt64._padinfo2[1], not full charmap
+
+    #--HasRounds--
+    default_rounds = 12 # current passlib default
+    min_rounds = 4 # bcrypt spec specified minimum
+    max_rounds = 31 # 32-bit integer limit (since real_rounds=1<<rounds)
+    rounds_cost = "log2"
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    @classmethod
+    def from_string(cls, hash):
+        ident, tail = cls._parse_ident(hash)
+        if ident == IDENT_2X:
+            raise ValueError("crypt_blowfish's buggy '2x' hashes are not "
+                             "currently supported")
+        rounds_str, data = tail.split(u("$"))
+        rounds = int(rounds_str)
+        if rounds_str != u('%02d') % (rounds,):
+            raise uh.exc.MalformedHashError(cls, "malformed cost field")
+        salt, chk = data[:22], data[22:]
+        return cls(
+            rounds=rounds,
+            salt=salt,
+            checksum=chk or None,
+            ident=ident,
+        )
+
+    def to_string(self):
+        hash = u("%s%02d$%s%s") % (self.ident, self.rounds, self.salt,
+                                   self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    def _get_config(self, ident=None):
+        "internal helper to prepare config string for backends"
+        if ident is None:
+            ident = self.ident
+        if ident == IDENT_2Y:
+            ident = IDENT_2A
+        else:
+            assert ident != IDENT_2X
+        config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
+        return uascii_to_str(config)
+
+    #===================================================================
+    # specialized salt generation - fixes passlib issue 25
+    #===================================================================
+
+    @classmethod
+    def _bind_needs_update(cls, **settings):
+        return cls._needs_update
+
+    @classmethod
+    def _needs_update(cls, hash, secret):
+        if isinstance(hash, bytes):
+            hash = hash.decode("ascii")
+        # check for incorrect padding bits (passlib issue 25)
+        if hash.startswith(IDENT_2A) and hash[28] not in bcrypt64._padinfo2[1]:
+            return True
+        # TODO: try to detect incorrect $2x$ hashes using *secret*
+        return False
+
+    @classmethod
+    def normhash(cls, hash):
+        "helper to normalize hash, correcting any bcrypt padding bits"
+        if cls.identify(hash):
+            return cls.from_string(hash).to_string()
+        else:
+            return hash
+
+    def _generate_salt(self, salt_size):
+        # override to correct generate salt bits
+        salt = super(bcrypt, self)._generate_salt(salt_size)
+        return bcrypt64.repair_unused(salt)
+
+    def _norm_salt(self, salt, **kwds):
+        salt = super(bcrypt, self)._norm_salt(salt, **kwds)
+        assert salt is not None, "HasSalt didn't generate new salt!"
+        changed, salt = bcrypt64.check_repair_unused(salt)
+        if changed:
+            # FIXME: if salt was provided by user, this message won't be
+            # correct. not sure if we want to throw error, or use different warning.
+            warn(
+                "encountered a bcrypt salt with incorrectly set padding bits; "
+                "you may want to use bcrypt.normhash() "
+                "to fix this; see Passlib 1.5.3 changelog.",
+                PasslibHashWarning)
+        return salt
+
+    def _norm_checksum(self, checksum):
+        checksum = super(bcrypt, self)._norm_checksum(checksum)
+        if not checksum:
+            return None
+        changed, checksum = bcrypt64.check_repair_unused(checksum)
+        if changed:
+            warn(
+                "encountered a bcrypt hash with incorrectly set padding bits; "
+                "you may want to use bcrypt.normhash() "
+                "to fix this; see Passlib 1.5.3 changelog.",
+                PasslibHashWarning)
+        return checksum
+
+    #===================================================================
+    # primary interface
+    #===================================================================
+    backends = ("pybcrypt", "bcryptor", "os_crypt", "builtin")
+
+    @classproperty
+    def _has_backend_pybcrypt(cls):
+        return pybcrypt_hashpw is not None
+
+    @classproperty
+    def _has_backend_bcryptor(cls):
+        return bcryptor_engine is not None
+
+    @classproperty
+    def _has_backend_builtin(cls):
+        if os.environ.get("PASSLIB_BUILTIN_BCRYPT") not in ["enable","enabled"]:
+            return False
+        # look at it cross-eyed, and it loads itself
+        _load_builtin()
+        return True
+
+    @classproperty
+    def _has_backend_os_crypt(cls):
+        # XXX: what to do if only h2 is supported? h1 is *very* rare.
+        h1 = '$2$04$......................1O4gOrCYaqBG3o/4LnT2ykQUt1wbyju'
+        h2 = '$2a$04$......................qiOQjkB8hxU8OzRhS.GhRMa4VUnkPty'
+        return test_crypt("test",h1) and test_crypt("test", h2)
+
+    @classmethod
+    def _no_backends_msg(cls):
+        return "no bcrypt backends available - please install py-bcrypt"
+
+    def _calc_checksum_os_crypt(self, secret):
+        config = self._get_config()
+        hash = safe_crypt(secret, config)
+        if hash:
+            assert hash.startswith(config) and len(hash) == len(config)+31
+            return hash[-31:]
+        else:
+            # NOTE: it's unlikely any other backend will be available,
+            # but checking before we bail, just in case.
+            for name in self.backends:
+                if name != "os_crypt" and self.has_backend(name):
+                    func = getattr(self, "_calc_checksum_" + name)
+                    return func(secret)
+            raise uh.exc.MissingBackendError(
+                "password can't be handled by os_crypt, "
+                "recommend installing py-bcrypt.",
+                )
+
+    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)
+        config = self._get_config()
+        hash = pybcrypt_hashpw(secret, config)
+        assert hash.startswith(config) and len(hash) == len(config)+31
+        return str_to_uascii(hash[-31:])
+
+    def _calc_checksum_bcryptor(self, secret):
+        # bcryptor behavior:
+        #   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
+            # 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()
+        hash = bcryptor_engine(False).hash_key(secret, config)
+        assert hash.startswith(config) and len(hash) == len(config)+31
+        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")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/cisco.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,217 @@
+"""passlib.handlers.cisco - Cisco password hashes"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from binascii import hexlify, unhexlify
+from hashlib import md5
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import h64, right_pad_string, to_unicode
+from passlib.utils.compat import b, bascii_to_str, bytes, unicode, u, join_byte_values, \
+             join_byte_elems, byte_elem_value, iter_byte_values, uascii_to_str, str_to_uascii
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "cisco_pix",
+    "cisco_type7",
+]
+
+#=============================================================================
+# cisco pix firewall hash
+#=============================================================================
+class cisco_pix(uh.HasUserContext, uh.StaticHandler):
+    """This class implements the password hash used by Cisco PIX firewalls,
+    and follows the :ref:`password-hash-api`.
+    It does a single round of hashing, and relies on the username
+    as the salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
+    have the following extra keyword:
+
+    :type user: str
+    :param user:
+        String containing name of user account this password is associated with.
+
+        This is *required* in order to correctly hash passwords associated
+        with a user account on the Cisco device, as it is used to salt
+        the hash.
+
+        Conversely, this *must* be omitted or set to ``""`` in order to correctly
+        hash passwords which don't have an associated user account
+        (such as the "enable" password).
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "cisco_pix"
+    checksum_size = 16
+    checksum_chars = uh.HASH64_CHARS
+
+    #===================================================================
+    # methods
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            # XXX: no idea what unicode policy is, but all examples are
+            # 7-bit ascii compatible, so using UTF-8
+            secret = secret.encode("utf-8")
+
+        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.
+            if isinstance(user, unicode):
+                user = user.encode("utf-8")
+            secret += user[:4]
+
+        # pad/truncate to 16
+        secret = right_pad_string(secret, 16)
+
+        # md5 digest
+        hash = md5(secret).digest()
+
+        # drop every 4th byte
+        hash = join_byte_elems(c for i,c in enumerate(hash) if i & 3 < 3)
+
+        # encode using Hash64
+        return h64.encode_bytes(hash).decode("ascii")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# type 7
+#=============================================================================
+class cisco_type7(uh.GenericHandler):
+    """This class implements the Type 7 password encoding used by Cisco IOS,
+    and follows the :ref:`password-hash-api`.
+    It has a simple 4-5 bit salt, but is nonetheless a reversible encoding
+    instead of a real hash.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genhash` methods
+    have the following optional keywords:
+
+    :type salt: int
+    :param salt:
+        This may be an optional salt integer drawn from ``range(0,16)``.
+        If omitted, one will be chosen at random.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` values that are out of range.
+
+    Note that while this class outputs digests in upper-case hexidecimal,
+    it will accept lower-case as well.
+
+    This class also provides the following additional method:
+
+    .. automethod:: decode
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "cisco_type7"
+    setting_kwds = ("salt",)
+    checksum_chars = uh.UPPER_HEX_CHARS
+
+    min_salt_value = 0
+    max_salt_value = 52
+
+    #===================================================================
+    # methods
+    #===================================================================
+    @classmethod
+    def genconfig(cls):
+        return None
+
+    @classmethod
+    def genhash(cls, secret, config):
+        # special case to handle ``config=None`` in same style as StaticHandler
+        if config is None:
+            return cls.encrypt(secret)
+        else:
+            return super(cisco_type7, cls).genhash(secret, config)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        if len(hash) < 2:
+            raise uh.exc.InvalidHashError(cls)
+        salt = int(hash[:2]) # may throw ValueError
+        return cls(salt=salt, checksum=hash[2:].upper())
+
+    def __init__(self, salt=None, **kwds):
+        super(cisco_type7, self).__init__(**kwds)
+        self.salt = self._norm_salt(salt)
+
+    def _norm_salt(self, salt):
+        # NOTE: the "salt" for this algorithm is a small integer.
+        # XXX: not entirely sure that values >15 are valid, so for
+        # compatibility we don't output those values but we do accept them.
+        if salt is None:
+            if self.use_defaults:
+                salt = self._generate_salt()
+            else:
+                raise TypeError("no salt specified")
+        if not isinstance(salt, int):
+            raise uh.exc.ExpectedTypeError(salt, "integer", "salt")
+        if salt < 0 or salt > self.max_salt_value:
+            msg = "salt/offset must be in 0..52 range"
+            if self.relaxed:
+                warn(msg, uh.PasslibHashWarning)
+                salt = 0 if salt < 0 else self.max_salt_value
+            else:
+                raise ValueError(msg)
+        return salt
+
+    def _generate_salt(self):
+        return uh.rng.randint(0, 15)
+
+    def to_string(self):
+        return "%02d%s" % (self.salt, uascii_to_str(self.checksum))
+
+    def _calc_checksum(self, secret):
+        # XXX: no idea what unicode policy is, but all examples are
+        # 7-bit ascii compatible, so using UTF-8
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper()
+
+    @classmethod
+    def decode(cls, hash, encoding="utf-8"):
+        """decode hash, returning original password.
+
+        :arg hash: encoded password
+        :param encoding: optional encoding to use (defaults to ``UTF-8``).
+        :returns: password as unicode
+        """
+        self = cls.from_string(hash)
+        tmp = unhexlify(self.checksum.encode("ascii"))
+        raw = self._cipher(tmp, self.salt)
+        return raw.decode(encoding) if encoding else raw
+
+    # type7 uses a xor-based vingere variant, using the following secret key:
+    _key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87")
+
+    @classmethod
+    def _cipher(cls, data, salt):
+        "xor static key against data - encrypts & decrypts"
+        key = cls._key
+        key_size = len(key)
+        return join_byte_values(
+            value ^ ord(key[(salt + idx) % key_size])
+            for idx, value in enumerate(iter_byte_values(data))
+        )
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/des_crypt.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,517 @@
+"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import classproperty, h64, h64big, safe_crypt, test_crypt, to_unicode
+from passlib.utils.compat import b, bytes, byte_elem_value, u, uascii_to_str, unicode
+from passlib.utils.des import des_encrypt_int_block
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "des_crypt",
+    "bsdi_crypt",
+    "bigcrypt",
+    "crypt16",
+]
+
+#=============================================================================
+# pure-python backend for des_crypt family
+#=============================================================================
+_BNULL = b('\x00')
+
+def _crypt_secret_to_key(secret):
+    """convert secret to 64-bit DES key.
+
+    this only uses the first 8 bytes of the secret,
+    and discards the high 8th bit of each byte at that.
+    a null parity bit is inserted after every 7th bit of the output.
+    """
+    # NOTE: this would set the parity bits correctly,
+    # but des_encrypt_int_block() would just ignore them...
+    ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
+    ##           for i, c in enumerate(secret[:8]))
+    return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
+               for i, c in enumerate(secret[:8]))
+
+def _raw_des_crypt(secret, salt):
+    "pure-python backed for des_crypt"
+    assert len(salt) == 2
+
+    # NOTE: some OSes will accept non-HASH64 characters in the salt,
+    # but what value they assign these characters varies wildy,
+    # so just rejecting them outright.
+    # NOTE: the same goes for single-character salts...
+    # some OSes duplicate the char, some insert a '.' char,
+    # and openbsd does something which creates an invalid hash.
+    try:
+        salt_value = h64.decode_int12(salt)
+    except ValueError: # pragma: no cover - always caught by class
+        raise ValueError("invalid chars in salt")
+
+    # gotta do something - no official policy since this predates unicode
+    if isinstance(secret, unicode):
+        secret = secret.encode("utf-8")
+    assert isinstance(secret, bytes)
+
+    # forbidding NULL char because underlying crypt() rejects them too.
+    if _BNULL in secret:
+        raise uh.exc.NullPasswordError(des_crypt)
+
+    # convert first 8 bytes of secret string into an integer
+    key_value = _crypt_secret_to_key(secret)
+
+    # run data through des using input of 0
+    result = des_encrypt_int_block(key_value, 0, salt_value, 25)
+
+    # run h64 encode on result
+    return h64big.encode_int64(result)
+
+def _bsdi_secret_to_key(secret):
+    "covert secret to DES key used by bsdi_crypt"
+    key_value = _crypt_secret_to_key(secret)
+    idx = 8
+    end = len(secret)
+    while idx < end:
+        next = idx+8
+        tmp_value = _crypt_secret_to_key(secret[idx:next])
+        key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
+        idx = next
+    return key_value
+
+def _raw_bsdi_crypt(secret, rounds, salt):
+    "pure-python backend for bsdi_crypt"
+
+    # decode salt
+    try:
+        salt_value = h64.decode_int24(salt)
+    except ValueError: # pragma: no cover - always caught by class
+        raise ValueError("invalid salt")
+
+    # gotta do something - no official policy since this predates unicode
+    if isinstance(secret, unicode):
+        secret = secret.encode("utf-8")
+    assert isinstance(secret, bytes)
+
+    # forbidding NULL char because underlying crypt() rejects them too.
+    if _BNULL in secret:
+        raise uh.exc.NullPasswordError(bsdi_crypt)
+
+    # convert secret string into an integer
+    key_value = _bsdi_secret_to_key(secret)
+
+    # run data through des using input of 0
+    result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
+
+    # run h64 encode on result
+    return h64big.encode_int64(result)
+
+#=============================================================================
+# handlers
+#=============================================================================
+class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
+    """This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "des_crypt"
+    setting_kwds = ("salt",)
+    checksum_chars = uh.HASH64_CHARS
+    checksum_size = 11
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 2
+    salt_chars = uh.HASH64_CHARS
+
+    #===================================================================
+    # formatting
+    #===================================================================
+    # FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
+
+    _hash_regex = re.compile(u(r"""
+        ^
+        (?P<salt>[./a-z0-9]{2})
+        (?P<chk>[./a-z0-9]{11})?
+        $"""), re.X|re.I)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        salt, chk = hash[:2], hash[2:]
+        return cls(salt=salt, checksum=chk or None)
+
+    def to_string(self):
+        hash = u("%s%s") % (self.salt, self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    #===================================================================
+    # backend
+    #===================================================================
+    backends = ("os_crypt", "builtin")
+
+    _has_backend_builtin = True
+
+    @classproperty
+    def _has_backend_os_crypt(cls):
+        return test_crypt("test", 'abgOeLfPimXQo')
+
+    def _calc_checksum_builtin(self, secret):
+        return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
+
+    def _calc_checksum_os_crypt(self, secret):
+        # NOTE: safe_crypt encodes unicode secret -> utf8
+        # no official policy since des-crypt predates unicode
+        hash = safe_crypt(secret, self.salt)
+        if hash:
+            assert hash.startswith(self.salt) and len(hash) == 13
+            return hash[2:]
+        else:
+            return self._calc_checksum_builtin(secret)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
+    """This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 5001, must be between 1 and 16777215, inclusive.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+
+    .. versionchanged:: 1.6
+        :meth:`encrypt` will now issue a warning if an even number of rounds is used
+        (see :ref:`bsdi-crypt-security-issues` regarding weak DES keys).
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "bsdi_crypt"
+    setting_kwds = ("salt", "rounds")
+    checksum_size = 11
+    checksum_chars = uh.HASH64_CHARS
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 4
+    salt_chars = uh.HASH64_CHARS
+
+    #--HasRounds--
+    default_rounds = 5001
+    min_rounds = 1
+    max_rounds = 16777215 # (1<<24)-1
+    rounds_cost = "linear"
+
+    # NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
+    # but that seems to be an OS policy, not a algorithm limitation.
+
+    #===================================================================
+    # parsing
+    #===================================================================
+    _hash_regex = re.compile(u(r"""
+        ^
+        _
+        (?P<rounds>[./a-z0-9]{4})
+        (?P<salt>[./a-z0-9]{4})
+        (?P<chk>[./a-z0-9]{11})?
+        $"""), re.X|re.I)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        rounds, salt, chk = m.group("rounds", "salt", "chk")
+        return cls(
+            rounds=h64.decode_int24(rounds.encode("ascii")),
+            salt=salt,
+            checksum=chk,
+        )
+
+    def to_string(self):
+        hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"),
+                             self.salt, self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    #===================================================================
+    # validation
+    #===================================================================
+
+    # flag so CryptContext won't generate even rounds.
+    _avoid_even_rounds = True
+
+    def _norm_rounds(self, rounds):
+        rounds = super(bsdi_crypt, self)._norm_rounds(rounds)
+        # issue warning if app provided an even rounds value
+        if self.use_defaults and not rounds & 1:
+            warn("bsdi_crypt rounds should be odd, "
+                 "as even rounds may reveal weak DES keys",
+                 uh.exc.PasslibSecurityWarning)
+        return rounds
+
+    @classmethod
+    def _bind_needs_update(cls, **settings):
+        return cls._needs_update
+
+    @classmethod
+    def _needs_update(cls, hash, secret):
+        # mark bsdi_crypt hashes as deprecated if they have even rounds.
+        assert cls.identify(hash)
+        if isinstance(hash, unicode):
+            hash = hash.encode("ascii")
+        rounds = h64.decode_int24(hash[1:5])
+        return not rounds & 1
+
+    #===================================================================
+    # backends
+    #===================================================================
+    backends = ("os_crypt", "builtin")
+
+    _has_backend_builtin = True
+
+    @classproperty
+    def _has_backend_os_crypt(cls):
+        return test_crypt("test", '_/...lLDAxARksGCHin.')
+
+    def _calc_checksum_builtin(self, secret):
+        return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
+
+    def _calc_checksum_os_crypt(self, secret):
+        config = self.to_string()
+        hash = safe_crypt(secret, config)
+        if hash:
+            assert hash.startswith(config[:9]) and len(hash) == 20
+            return hash[-11:]
+        else:
+            return self._calc_checksum_builtin(secret)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class bigcrypt(uh.HasSalt, uh.GenericHandler):
+    """This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "bigcrypt"
+    setting_kwds = ("salt",)
+    checksum_chars = uh.HASH64_CHARS
+    # NOTE: checksum chars must be multiple of 11
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 2
+    salt_chars = uh.HASH64_CHARS
+
+    #===================================================================
+    # internal helpers
+    #===================================================================
+    _hash_regex = re.compile(u(r"""
+        ^
+        (?P<salt>[./a-z0-9]{2})
+        (?P<chk>([./a-z0-9]{11})+)?
+        $"""), re.X|re.I)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        salt, chk = m.group("salt", "chk")
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        hash = u("%s%s") % (self.salt, self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    def _norm_checksum(self, value):
+        value = super(bigcrypt, self)._norm_checksum(value)
+        if value and len(value) % 11:
+            raise uh.exc.InvalidHashError(self)
+        return value
+
+    #===================================================================
+    # backend
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
+        idx = 8
+        end = len(secret)
+        while idx < end:
+            next = idx + 8
+            chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
+            idx = next
+        return chk.decode("ascii")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class crypt16(uh.HasSalt, uh.GenericHandler):
+    """This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "crypt16"
+    setting_kwds = ("salt",)
+    checksum_size = 22
+    checksum_chars = uh.HASH64_CHARS
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 2
+    salt_chars = uh.HASH64_CHARS
+
+    #===================================================================
+    # internal helpers
+    #===================================================================
+    _hash_regex = re.compile(u(r"""
+        ^
+        (?P<salt>[./a-z0-9]{2})
+        (?P<chk>[./a-z0-9]{22})?
+        $"""), re.X|re.I)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        salt, chk = m.group("salt", "chk")
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        hash = u("%s%s") % (self.salt, self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    #===================================================================
+    # backend
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+
+        # parse salt value
+        try:
+            salt_value = h64.decode_int12(self.salt.encode("ascii"))
+        except ValueError: # pragma: no cover - caught by class
+            raise ValueError("invalid chars in salt")
+
+        # convert first 8 byts of secret string into an integer,
+        key1 = _crypt_secret_to_key(secret)
+
+        # run data through des using input of 0
+        result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
+
+        # convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
+        key2 = _crypt_secret_to_key(secret[8:16])
+
+        # run data through des using input of 0
+        result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
+
+        # done
+        chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
+        return chk.decode("ascii")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/digests.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,144 @@
+"""passlib.handlers.digests - plain hash digests
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import hashlib
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_native_str, to_bytes, render_bytes, consteq
+from passlib.utils.compat import bascii_to_str, bytes, unicode, str_to_uascii
+import passlib.utils.handlers as uh
+from passlib.utils.md4 import md4
+# local
+__all__ = [
+    "create_hex_hash",
+    "hex_md4",
+    "hex_md5",
+    "hex_sha1",
+    "hex_sha256",
+    "hex_sha512",
+]
+
+#=============================================================================
+# helpers for hexidecimal hashes
+#=============================================================================
+class HexDigestHash(uh.StaticHandler):
+    "this provides a template for supporting passwords stored as plain hexidecimal hashes"
+    #===================================================================
+    # class attrs
+    #===================================================================
+    _hash_func = None # hash function to use - filled in by create_hex_hash()
+    checksum_size = None # filled in by create_hex_hash()
+    checksum_chars = uh.HEX_CHARS
+
+    #===================================================================
+    # methods
+    #===================================================================
+    @classmethod
+    def _norm_hash(cls, hash):
+        return hash.lower()
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return str_to_uascii(self._hash_func(secret).hexdigest())
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+def create_hex_hash(hash, digest_name, module=__name__):
+    # NOTE: could set digest_name=hash.name for cpython, but not for some other platforms.
+    h = hash()
+    name = "hex_" + digest_name
+    return type(name, (HexDigestHash,), dict(
+        name=name,
+        __module__=module, # so ABCMeta won't clobber it
+        _hash_func=staticmethod(hash), # sometimes it's a function, sometimes not. so wrap it.
+        checksum_size=h.digest_size*2,
+        __doc__="""This class implements a plain hexidecimal %s hash, and follows the :ref:`password-hash-api`.
+
+It supports no optional or contextual keywords.
+""" % (digest_name,)
+    ))
+
+#=============================================================================
+# predefined handlers
+#=============================================================================
+hex_md4     = create_hex_hash(md4,              "md4")
+hex_md5     = create_hex_hash(hashlib.md5,      "md5")
+hex_md5.django_name = "unsalted_md5"
+hex_sha1    = create_hex_hash(hashlib.sha1,     "sha1")
+hex_sha256  = create_hex_hash(hashlib.sha256,   "sha256")
+hex_sha512  = create_hex_hash(hashlib.sha512,   "sha512")
+
+#=============================================================================
+# htdigest
+#=============================================================================
+class htdigest(uh.PasswordHash):
+    """htdigest hash function.
+
+    .. todo::
+        document this hash
+    """
+    name = "htdigest"
+    setting_kwds = ()
+    context_kwds = ("user", "realm", "encoding")
+    default_encoding = "utf-8"
+
+    @classmethod
+    def encrypt(cls, secret, user, realm, encoding=None):
+        # NOTE: this was deliberately written so that raw bytes are passed through
+        # unchanged, the encoding kwd is only used to handle unicode values.
+        if not encoding:
+            encoding = cls.default_encoding
+        uh.validate_secret(secret)
+        if isinstance(secret, unicode):
+            secret = secret.encode(encoding)
+        user = to_bytes(user, encoding, "user")
+        realm = to_bytes(realm, encoding, "realm")
+        data = render_bytes("%s:%s:%s", user, realm, secret)
+        return hashlib.md5(data).hexdigest()
+
+    @classmethod
+    def _norm_hash(cls, hash):
+        "normalize hash to native string, and validate it"
+        hash = to_native_str(hash, param="hash")
+        if len(hash) != 32:
+            raise uh.exc.MalformedHashError(cls, "wrong size")
+        for char in hash:
+            if char not in uh.LC_HEX_CHARS:
+                raise uh.exc.MalformedHashError(cls, "invalid chars in hash")
+        return hash
+
+    @classmethod
+    def verify(cls, secret, hash, user, realm, encoding="utf-8"):
+        hash = cls._norm_hash(hash)
+        other = cls.encrypt(secret, user, realm, encoding)
+        return consteq(hash, other)
+
+    @classmethod
+    def identify(cls, hash):
+        try:
+            cls._norm_hash(hash)
+        except ValueError:
+            return False
+        return True
+
+    @classmethod
+    def genconfig(cls):
+        return None
+
+    @classmethod
+    def genhash(cls, secret, config, user, realm, encoding="utf-8"):
+        if config is not None:
+            cls._norm_hash(config)
+        return cls.encrypt(secret, user, realm, encoding)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/django.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,377 @@
+"""passlib.handlers.django- Django password hash support"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from base64 import b64encode
+from hashlib import md5, sha1
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+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
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "django_salted_sha1",
+    "django_salted_md5",
+    "django_bcrypt",
+    "django_pbkdf2_sha1",
+    "django_pbkdf2_sha256",
+    "django_des_crypt",
+    "django_disabled",
+]
+
+#=============================================================================
+# lazy imports & constants
+#=============================================================================
+des_crypt = None
+
+def _import_des_crypt():
+    global des_crypt
+    if des_crypt is None:
+        from passlib.hash import des_crypt
+    return des_crypt
+
+# django 1.4's salt charset
+SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
+
+#=============================================================================
+# salted hashes
+#=============================================================================
+class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler):
+    """base class providing common code for django hashes"""
+    # name, ident, checksum_size must be set by subclass.
+    # ident must include "$" suffix.
+    setting_kwds = ("salt", "salt_size")
+
+    min_salt_size = 0
+        # NOTE: django 1.0-1.3 would accept empty salt strings.
+        #       django 1.4 won't, but this appears to be regression
+        #       (https://code.djangoproject.com/ticket/18144)
+        #       so presumably it will be fixed in a later release.
+    default_salt_size = 12
+    max_salt_size = None
+    salt_chars = SALT_CHARS
+
+    checksum_chars = uh.LOWER_HEX_CHARS
+
+    @classproperty
+    def _stub_checksum(cls):
+        return cls.checksum_chars[0] * cls.checksum_size
+
+    @classmethod
+    def from_string(cls, hash):
+        salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        return uh.render_mc2(self.ident, self.salt,
+                             self.checksum or self._stub_checksum)
+
+class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
+    """base class providing common code for django hashes w/ variable rounds"""
+    setting_kwds = DjangoSaltedHash.setting_kwds + ("rounds",)
+
+    min_rounds = 1
+
+    @classmethod
+    def from_string(cls, hash):
+        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
+        return cls(rounds=rounds, salt=salt, checksum=chk)
+
+    def to_string(self):
+        return uh.render_mc3(self.ident, self.rounds, self.salt,
+                             self.checksum or self._stub_checksum)
+
+class django_salted_sha1(DjangoSaltedHash):
+    """This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and uses a single round of SHA1.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, a 12 character one will be autogenerated (this is recommended).
+        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of characters to use when autogenerating new salts.
+        Defaults to 12, but can be any positive value.
+
+    This should be compatible with Django 1.4's :class:`!SHA1PasswordHasher` class.
+
+    .. versionchanged: 1.6
+        This class now generates 12-character salts instead of 5,
+        and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
+        the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
+        generates these hashes; but hashes generated in this manner will still be
+        correctly interpreted by earlier versions of Django.
+    """
+    name = "django_salted_sha1"
+    django_name = "sha1"
+    ident = u("sha1$")
+    checksum_size = 40
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest())
+
+class django_salted_md5(DjangoSaltedHash):
+    """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and uses a single round of MD5.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, a 12 character one will be autogenerated (this is recommended).
+        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of characters to use when autogenerating new salts.
+        Defaults to 12, but can be any positive value.
+
+    This should be compatible with the hashes generated by
+    Django 1.4's :class:`!MD5PasswordHasher` class.
+
+    .. versionchanged: 1.6
+        This class now generates 12-character salts instead of 5,
+        and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
+        the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
+        generates these hashes; but hashes generated in this manner will still be
+        correctly interpreted by earlier versions of Django.
+    """
+    name = "django_salted_md5"
+    django_name = "md5"
+    ident = u("md5$")
+    checksum_size = 32
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
+
+django_bcrypt = uh.PrefixWrapper("django_bcrypt", "bcrypt",
+    prefix=u('bcrypt$'), ident=u("bcrypt$"),
+    # NOTE: this docstring is duplicated in the docs, since sphinx
+    # seems to be having trouble reading it via autodata::
+    doc="""This class implements Django 1.4's BCrypt wrapper, and follows the :ref:`password-hash-api`.
+
+    This is identical to :class:`!bcrypt` itself, but with
+    the Django-specific prefix ``"bcrypt$"`` prepended.
+
+    See :doc:`/lib/passlib.hash.bcrypt` for more details,
+    the usage and behavior is identical.
+
+    This should be compatible with the hashes generated by
+    Django 1.4's :class:`!BCryptPasswordHasher` class.
+
+    .. versionadded:: 1.6
+    """)
+django_bcrypt.django_name = "bcrypt"
+
+class django_pbkdf2_sha256(DjangoVariableHash):
+    """This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, a 12 character one will be autogenerated (this is recommended).
+        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of characters to use when autogenerating new salts.
+        Defaults to 12, but can be any positive value.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 10000, but must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+    This should be compatible with the hashes generated by
+    Django 1.4's :class:`!PBKDF2PasswordHasher` class.
+
+    .. versionadded:: 1.6
+    """
+    name = "django_pbkdf2_sha256"
+    django_name = "pbkdf2_sha256"
+    ident = u('pbkdf2_sha256$')
+    min_salt_size = 1
+    max_rounds = 0xffffffff # setting at 32-bit limit for now
+    checksum_chars = uh.PADDED_BASE64_CHARS
+    checksum_size = 44 # 32 bytes -> base64
+    default_rounds = 10000 # NOTE: using django default here
+    _prf = "hmac-sha256"
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        hash = pbkdf2(secret, self.salt.encode("ascii"), self.rounds,
+                      keylen=None, prf=self._prf)
+        return b64encode(hash).rstrip().decode("ascii")
+
+class django_pbkdf2_sha1(django_pbkdf2_sha256):
+    """This class implements Django's PBKDF2-HMAC-SHA1 hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, a 12 character one will be autogenerated (this is recommended).
+        If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of characters to use when autogenerating new salts.
+        Defaults to 12, but can be any positive value.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 10000, but must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+    This should be compatible with the hashes generated by
+    Django 1.4's :class:`!PBKDF2SHA1PasswordHasher` class.
+
+    .. versionadded:: 1.6
+    """
+    name = "django_pbkdf2_sha1"
+    django_name = "pbkdf2_sha1"
+    ident = u('pbkdf2_sha1$')
+    checksum_size = 28 # 20 bytes -> base64
+    _prf = "hmac-sha1"
+
+#=============================================================================
+# other
+#=============================================================================
+class django_des_crypt(uh.HasSalt, uh.GenericHandler):
+    """This class implements Django's :class:`des_crypt` wrapper, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    This should be compatible with the hashes generated by
+    Django 1.4's :class:`!CryptPasswordHasher` class.
+    Note that Django only supports this hash on Unix systems
+    (though :class:`!django_des_crypt` is available cross-platform
+    under Passlib).
+
+    .. versionchanged:: 1.6
+        This class will now accept hashes with empty salt strings,
+        since Django 1.4 generates them this way.
+    """
+    name = "django_des_crypt"
+    django_name = "crypt"
+    setting_kwds = ("salt", "salt_size")
+    ident = u("crypt$")
+    checksum_chars = salt_chars = uh.HASH64_CHARS
+    checksum_size = 11
+    min_salt_size = default_salt_size = 2
+    _stub_checksum = u('.')*11
+
+    @classmethod
+    def from_string(cls, hash):
+        salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
+        if chk:
+            # chk should be full des_crypt hash
+            if not salt:
+                # django 1.4 always uses empty salt field,
+                # so extract salt from des_crypt hash <chk>
+                salt = chk[:2]
+            elif salt[:2] != chk[:2]:
+                # django 1.0 stored 5 chars in salt field, and duplicated
+                # the first two chars in <chk>. we keep the full salt,
+                # but make sure the first two chars match as sanity check.
+                raise uh.exc.MalformedHashError(cls,
+                    "first two digits of salt and checksum must match")
+            # in all cases, strip salt chars from <chk>
+            chk = chk[2:]
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        # 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)
+
+    def _calc_checksum(self, secret):
+        # NOTE: we lazily import des_crypt,
+        #       since most django deploys won't use django_des_crypt
+        global des_crypt
+        if des_crypt is None:
+            _import_des_crypt()
+        return des_crypt(salt=self.salt[:2])._calc_checksum(secret)
+
+class django_disabled(uh.StaticHandler):
+    """This class provides disabled password behavior for Django, and follows the :ref:`password-hash-api`.
+
+    This class does not implement a hash, but instead
+    claims the special hash string ``"!"`` which Django uses
+    to indicate an account's password has been disabled.
+
+    * newly encrypted passwords will hash to ``!``.
+    * it rejects all passwords.
+    """
+    name = "django_disabled"
+
+    @classmethod
+    def identify(cls, hash):
+        hash = uh.to_unicode_for_identify(hash)
+        return hash == u("!")
+
+    def _calc_checksum(self, secret):
+        return u("!")
+
+    @classmethod
+    def verify(cls, secret, hash):
+        uh.validate_secret(secret)
+        if not cls.identify(hash):
+            raise uh.exc.InvalidHashError(cls)
+        return False
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/fshp.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,206 @@
+"""passlib.handlers.fshp
+"""
+
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from base64 import b64encode, b64decode
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_unicode
+import passlib.utils.handlers as uh
+from passlib.utils.compat import b, bytes, bascii_to_str, iteritems, u,\
+                                 unicode
+from passlib.utils.pbkdf2 import pbkdf1
+# local
+__all__ = [
+    'fshp',
+]
+#=============================================================================
+# sha1-crypt
+#=============================================================================
+class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements the FSHP password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :param salt:
+        Optional raw salt string.
+        If not specified, one will be autogenerated (this is recommended).
+
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 16 bytes, but can be any non-negative value.
+
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 50000, must be between 1 and 4294967295, inclusive.
+
+    :param variant:
+        Optionally specifies variant of FSHP to use.
+
+        * ``0`` - uses SHA-1 digest (deprecated).
+        * ``1`` - uses SHA-2/256 digest (default).
+        * ``2`` - uses SHA-2/384 digest.
+        * ``3`` - uses SHA-2/512 digest.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "fshp"
+    setting_kwds = ("salt", "salt_size", "rounds", "variant")
+    checksum_chars = uh.PADDED_BASE64_CHARS
+    ident = u("{FSHP")
+    # checksum_size is property() that depends on variant
+
+    #--HasRawSalt--
+    default_salt_size = 16 # current passlib default, FSHP uses 8
+    min_salt_size = 0
+    max_salt_size = None
+
+    #--HasRounds--
+    # FIXME: should probably use different default rounds
+    # based on the variant. setting for default variant (sha256) for now.
+    default_rounds = 50000 # current passlib default, FSHP uses 4096
+    min_rounds = 1 # set by FSHP
+    max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
+    rounds_cost = "linear"
+
+    #--variants--
+    default_variant = 1
+    _variant_info = {
+        # variant: (hash name, digest size)
+        0: ("sha1",     20),
+        1: ("sha256",   32),
+        2: ("sha384",   48),
+        3: ("sha512",   64),
+        }
+    _variant_aliases = dict(
+        [(unicode(k),k) for k in _variant_info] +
+        [(v[0],k) for k,v in iteritems(_variant_info)]
+        )
+
+    #===================================================================
+    # instance attrs
+    #===================================================================
+    variant = None
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, variant=None, **kwds):
+        # NOTE: variant must be set first, since it controls checksum size, etc.
+        self.use_defaults = kwds.get("use_defaults") # load this early
+        self.variant = self._norm_variant(variant)
+        super(fshp, self).__init__(**kwds)
+
+    def _norm_variant(self, variant):
+        if variant is None:
+            if not self.use_defaults:
+                raise TypeError("no variant specified")
+            variant = self.default_variant
+        if isinstance(variant, bytes):
+            variant = variant.decode("ascii")
+        if isinstance(variant, unicode):
+            try:
+                variant = self._variant_aliases[variant]
+            except KeyError:
+                raise ValueError("invalid fshp variant")
+        if not isinstance(variant, int):
+            raise TypeError("fshp variant must be int or known alias")
+        if variant not in self._variant_info:
+            raise ValueError("invalid fshp variant")
+        return variant
+
+    @property
+    def checksum_alg(self):
+        return self._variant_info[self.variant][0]
+
+    @property
+    def checksum_size(self):
+        return self._variant_info[self.variant][1]
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    _hash_regex = re.compile(u(r"""
+            ^
+            \{FSHP
+            (\d+)\| # variant
+            (\d+)\| # salt size
+            (\d+)\} # rounds
+            ([a-zA-Z0-9+/]+={0,3}) # digest
+            $"""), re.X)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        variant, salt_size, rounds, data = m.group(1,2,3,4)
+        variant = int(variant)
+        salt_size = int(salt_size)
+        rounds = int(rounds)
+        try:
+            data = b64decode(data.encode("ascii"))
+        except TypeError:
+            raise uh.exc.MalformedHashError(cls)
+        salt = data[:salt_size]
+        chk = data[salt_size:]
+        return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)
+
+    @property
+    def _stub_checksum(self):
+        return b('\x00') * self.checksum_size
+
+    def to_string(self):
+        chk = self.checksum or self._stub_checksum
+        salt = self.salt
+        data = bascii_to_str(b64encode(salt+chk))
+        return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)
+
+    #===================================================================
+    # backend
+    #===================================================================
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        # NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed.
+        #       this has only a minimal impact on security,
+        #       but it is worth noting this deviation.
+        return pbkdf1(
+            secret=self.salt,
+            salt=secret,
+            rounds=self.rounds,
+            keylen=self.checksum_size,
+            hash=self.checksum_alg,
+            )
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/ldap_digests.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,270 @@
+"""passlib.handlers.digests - plain hash digests
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from base64 import b64encode, b64decode
+from hashlib import md5, sha1
+import logging; log = logging.getLogger(__name__)
+import re
+from warnings import warn
+# site
+# pkg
+from passlib.handlers.misc import plaintext
+from passlib.utils import to_native_str, unix_crypt_schemes, \
+                          classproperty, to_unicode
+from passlib.utils.compat import b, bytes, uascii_to_str, unicode, u
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "ldap_plaintext",
+    "ldap_md5",
+    "ldap_sha1",
+    "ldap_salted_md5",
+    "ldap_salted_sha1",
+
+    ##"get_active_ldap_crypt_schemes",
+    "ldap_des_crypt",
+    "ldap_bsdi_crypt",
+    "ldap_md5_crypt",
+    "ldap_sha1_crypt"
+    "ldap_bcrypt",
+    "ldap_sha256_crypt",
+    "ldap_sha512_crypt",
+]
+
+#=============================================================================
+# ldap helpers
+#=============================================================================
+class _Base64DigestHelper(uh.StaticHandler):
+    "helper for ldap_md5 / ldap_sha1"
+    # XXX: could combine this with hex digests in digests.py
+
+    ident = None # required - prefix identifier
+    _hash_func = None # required - hash function
+    _hash_regex = None # required - regexp to recognize hash
+    checksum_chars = uh.PADDED_BASE64_CHARS
+
+    @classproperty
+    def _hash_prefix(cls):
+        "tell StaticHandler to strip ident from checksum"
+        return cls.ident
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        chk = self._hash_func(secret).digest()
+        return b64encode(chk).decode("ascii")
+
+class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    "helper for ldap_salted_md5 / ldap_salted_sha1"
+    setting_kwds = ("salt", "salt_size")
+    checksum_chars = uh.PADDED_BASE64_CHARS
+
+    ident = None # required - prefix identifier
+    checksum_size = None # required
+    _hash_func = None # required - hash function
+    _hash_regex = None # required - regexp to recognize hash
+    _stub_checksum = None # required - default checksum to plug in
+    min_salt_size = max_salt_size = 4
+
+    # NOTE: openldap implementation uses 4 byte salt,
+    # but it's been reported (issue 30) that some servers use larger salts.
+    # the semi-related rfc3112 recommends support for up to 16 byte salts.
+    min_salt_size = 4
+    default_salt_size = 4
+    max_salt_size = 16
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        try:
+            data = b64decode(m.group("tmp").encode("ascii"))
+        except TypeError:
+            raise uh.exc.MalformedHashError(cls)
+        cs = cls.checksum_size
+        assert cs
+        return cls(checksum=data[:cs], salt=data[cs:])
+
+    def to_string(self):
+        data = (self.checksum or self._stub_checksum) + self.salt
+        hash = self.ident + b64encode(data).decode("ascii")
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return self._hash_func(secret + self.salt).digest()
+
+#=============================================================================
+# implementations
+#=============================================================================
+class ldap_md5(_Base64DigestHelper):
+    """This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
+    """
+    name = "ldap_md5"
+    ident = u("{MD5}")
+    _hash_func = md5
+    _hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$"))
+
+class ldap_sha1(_Base64DigestHelper):
+    """This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
+    """
+    name = "ldap_sha1"
+    ident = u("{SHA}")
+    _hash_func = sha1
+    _hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$"))
+
+class ldap_salted_md5(_SaltedBase64DigestHelper):
+    """This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`.
+
+    It supports a 4-16 byte salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it may be any 4-16 byte string.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 4 bytes for compatibility with the LDAP spec,
+        but some systems use larger salts, and Passlib supports
+        any value between 4-16.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+
+    .. versionchanged:: 1.6
+        This format now supports variable length salts, instead of a fix 4 bytes.
+    """
+    name = "ldap_salted_md5"
+    ident = u("{SMD5}")
+    checksum_size = 16
+    _hash_func = md5
+    _hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
+    _stub_checksum = b('\x00') * 16
+
+class ldap_salted_sha1(_SaltedBase64DigestHelper):
+    """This class stores passwords using LDAP's salted SHA1 format, and follows the :ref:`password-hash-api`.
+
+    It supports a 4-16 byte salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it may be any 4-16 byte string.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 4 bytes for compatibility with the LDAP spec,
+        but some systems use larger salts, and Passlib supports
+        any value between 4-16.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+
+    .. versionchanged:: 1.6
+        This format now supports variable length salts, instead of a fix 4 bytes.
+    """
+    name = "ldap_salted_sha1"
+    ident = u("{SSHA}")
+    checksum_size = 20
+    _hash_func = sha1
+    _hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
+    _stub_checksum = b('\x00') * 20
+
+class ldap_plaintext(plaintext):
+    """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
+
+    This class acts much like the generic :class:`!passlib.hash.plaintext` handler,
+    except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix
+    used by RFC2307 passwords.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+    following additional contextual keyword:
+
+    :type encoding: str
+    :param encoding:
+        This controls the character encoding to use (defaults to ``utf-8``).
+
+        This encoding will be used to encode :class:`!unicode` passwords
+        under Python 2, and decode :class:`!bytes` hashes under Python 3.
+
+    .. versionchanged:: 1.6
+        The ``encoding`` keyword was added.
+    """
+    # NOTE: this subclasses plaintext, since all it does differently
+    # is override identify()
+
+    name = "ldap_plaintext"
+    _2307_pat = re.compile(u(r"^\{\w+\}.*$"))
+
+    @classmethod
+    def identify(cls, hash):
+        # NOTE: identifies all strings EXCEPT those with {XXX} prefix
+        hash = uh.to_unicode_for_identify(hash)
+        return bool(hash) and cls._2307_pat.match(hash) is None
+
+#=============================================================================
+# {CRYPT} wrappers
+# the following are wrappers around the base crypt algorithms,
+# which add the ldap required {CRYPT} prefix
+#=============================================================================
+ldap_crypt_schemes = [ 'ldap_' + name for name in unix_crypt_schemes ]
+
+def _init_ldap_crypt_handlers():
+    # NOTE: I don't like to implicitly modify globals() like this,
+    #       but don't want to write out all these handlers out either :)
+    g = globals()
+    for wname in unix_crypt_schemes:
+        name = 'ldap_' + wname
+        g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True)
+    del g
+_init_ldap_crypt_handlers()
+
+##_lcn_host = None
+##def get_host_ldap_crypt_schemes():
+##    global _lcn_host
+##    if _lcn_host is None:
+##        from passlib.hosts import host_context
+##        schemes = host_context.schemes()
+##        _lcn_host = [
+##            "ldap_" + name
+##            for name in unix_crypt_names
+##            if name in schemes
+##        ]
+##    return _lcn_host
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/md5_crypt.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,333 @@
+"""passlib.handlers.md5_crypt - md5-crypt algorithm"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from hashlib import md5
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import classproperty, h64, safe_crypt, test_crypt, repeat_string
+from passlib.utils.compat import b, bytes, irange, unicode, u
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "md5_crypt",
+    "apr_md5_crypt",
+]
+
+#=============================================================================
+# pure-python backend
+#=============================================================================
+_BNULL = b("\x00")
+_MD5_MAGIC = b("$1$")
+_APR_MAGIC = b("$apr1$")
+
+# pre-calculated offsets used to speed up C digest stage (see notes below).
+# sequence generated using the following:
+    ##perms_order = "p,pp,ps,psp,sp,spp".split(",")
+    ##def offset(i):
+    ##    key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
+    ##        ("p" if i % 7 else "") + ("" if i % 2 else "p"))
+    ##    return perms_order.index(key)
+    ##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
+_c_digest_offsets = (
+    (0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
+    (4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
+    (4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
+    )
+
+# map used to transpose bytes when encoding final digest
+_transpose_map = (12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11)
+
+def _raw_md5_crypt(pwd, salt, use_apr=False):
+    """perform raw md5-crypt calculation
+
+    this function provides a pure-python implementation of the internals
+    for the MD5-Crypt algorithms; it doesn't handle any of the
+    parsing/validation of the hash strings themselves.
+
+    :arg pwd: password chars/bytes to encrypt
+    :arg salt: salt chars to use
+    :arg use_apr: use apache variant
+
+    :returns:
+        encoded checksum chars
+    """
+    # NOTE: regarding 'apr' format:
+    # really, apache? you had to invent a whole new "$apr1$" format,
+    # when all you did was change the ident incorporated into the hash?
+    # would love to find webpage explaining why just using a portable
+    # implementation of $1$ wasn't sufficient. *nothing* else was changed.
+
+    #===================================================================
+    # init & validate inputs
+    #===================================================================
+
+    # validate secret
+    # XXX: not sure what official unicode policy is, using this as default
+    if isinstance(pwd, unicode):
+        pwd = pwd.encode("utf-8")
+    assert isinstance(pwd, bytes), "pwd not unicode or bytes"
+    if _BNULL in pwd:
+        raise uh.exc.NullPasswordError(md5_crypt)
+    pwd_len = len(pwd)
+
+    # validate salt - should have been taken care of by caller
+    assert isinstance(salt, unicode), "salt not unicode"
+    salt = salt.encode("ascii")
+    assert len(salt) < 9, "salt too large"
+        # NOTE: spec says salts larger than 8 bytes should be truncated,
+        # instead of causing an error. this function assumes that's been
+        # taken care of by the handler class.
+
+    # load APR specific constants
+    if use_apr:
+        magic = _APR_MAGIC
+    else:
+        magic = _MD5_MAGIC
+
+    #===================================================================
+    # digest B - used as subinput to digest A
+    #===================================================================
+    db = md5(pwd + salt + pwd).digest()
+
+    #===================================================================
+    # digest A - used to initialize first round of digest C
+    #===================================================================
+    # start out with pwd + magic + salt
+    a_ctx = md5(pwd + magic + salt)
+    a_ctx_update = a_ctx.update
+
+    # add pwd_len bytes of b, repeating b as many times as needed.
+    a_ctx_update(repeat_string(db, pwd_len))
+
+    # add null chars & first char of password
+        # NOTE: this may have historically been a bug,
+        # where they meant to use db[0] instead of B_NULL,
+        # but the original code memclear'ed db,
+        # and now all implementations have to use this.
+    i = pwd_len
+    evenchar = pwd[:1]
+    while i:
+        a_ctx_update(_BNULL if i & 1 else evenchar)
+        i >>= 1
+
+    # finish A
+    da = a_ctx.digest()
+
+    #===================================================================
+    # digest C - for a 1000 rounds, combine A, S, and P
+    #            digests in various ways; in order to burn CPU time.
+    #===================================================================
+
+    # NOTE: the original MD5-Crypt implementation performs the C digest
+    # calculation using the following loop:
+    #
+    ##dc = da
+    ##i = 0
+    ##while i < rounds:
+    ##    tmp_ctx = md5(pwd if i & 1 else dc)
+    ##    if i % 3:
+    ##        tmp_ctx.update(salt)
+    ##    if i % 7:
+    ##        tmp_ctx.update(pwd)
+    ##    tmp_ctx.update(dc if i & 1 else pwd)
+    ##    dc = tmp_ctx.digest()
+    ##    i += 1
+    #
+    # The code Passlib uses (below) implements an equivalent algorithm,
+    # it's just been heavily optimized to pre-calculate a large number
+    # of things beforehand. It works off of a couple of observations
+    # about the original algorithm:
+    #
+    # 1. each round is a combination of 'dc', 'salt', and 'pwd'; determined
+    #    by the whether 'i' a multiple of 2,3, and/or 7.
+    # 2. since lcm(2,3,7)==42, the series of combinations will repeat
+    #    every 42 rounds.
+    # 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
+    #    while odd rounds 1-41 consist of hash(round-specific-constant + dc)
+    #
+    # Using these observations, the following code...
+    # * calculates the round-specific combination of 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.
+    #
+    # this cuts out a lot of the control overhead incurred when running the
+    # original loop 40,000+ times in python, resulting in ~20% increase in
+    # speed under CPython (though still 2x slower than glibc crypt)
+
+    # prepare the 6 combinations of pwd & salt which are needed
+    # (order of 'perms' must match how _c_digest_offsets was generated)
+    pwd_pwd = pwd+pwd
+    pwd_salt = pwd+salt
+    perms = [pwd, pwd_pwd, pwd_salt, pwd_salt+pwd, salt+pwd, salt+pwd_pwd]
+
+    # build up list of even-round & odd-round constants,
+    # and store in 21-element list as (even,odd) pairs.
+    data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
+
+    # perform 23 blocks of 42 rounds each (for a total of 966 rounds)
+    dc = da
+    blocks = 23
+    while blocks:
+        for even, odd in data:
+            dc = md5(odd + md5(dc + even).digest()).digest()
+        blocks -= 1
+
+    # perform 17 more pairs of rounds (34 more rounds, for a total of 1000)
+    for even, odd in data[:17]:
+        dc = md5(odd + md5(dc + even).digest()).digest()
+
+    #===================================================================
+    # encode digest using appropriate transpose map
+    #===================================================================
+    return h64.encode_transposed_bytes(dc, _transpose_map).decode("ascii")
+
+#=============================================================================
+# handler
+#=============================================================================
+class _MD5_Common(uh.HasSalt, uh.GenericHandler):
+    "common code for md5_crypt and apr_md5_crypt"
+    #===================================================================
+    # class attrs
+    #===================================================================
+    # name - set in subclass
+    setting_kwds = ("salt", "salt_size")
+    # ident - set in subclass
+    checksum_size = 22
+    checksum_chars = uh.HASH64_CHARS
+
+    min_salt_size = 0
+    max_salt_size = 8
+    salt_chars = uh.HASH64_CHARS
+
+    #===================================================================
+    # methods
+    #===================================================================
+
+    @classmethod
+    def from_string(cls, hash):
+        salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        return uh.render_mc2(self.ident, self.salt, self.checksum)
+
+    # _calc_checksum() - provided by subclass
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class md5_crypt(uh.HasManyBackends, _MD5_Common):
+    """This class implements the MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of characters to use when autogenerating new salts.
+        Defaults to 8, but can be any value between 0 and 8.
+        (This is mainly needed when generating Cisco-compatible hashes,
+        which require ``salt_size=4``).
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "md5_crypt"
+    ident = u("$1$")
+
+    #===================================================================
+    # methods
+    #===================================================================
+    # FIXME: can't find definitive policy on how md5-crypt handles non-ascii.
+    #        all backends currently coerce -> utf-8
+
+    backends = ("os_crypt", "builtin")
+
+    _has_backend_builtin = True
+
+    @classproperty
+    def _has_backend_os_crypt(cls):
+        return test_crypt("test", '$1$test$pi/xDtU5WFVRqYS6BMU8X/')
+
+    def _calc_checksum_builtin(self, secret):
+        return _raw_md5_crypt(secret, self.salt)
+
+    def _calc_checksum_os_crypt(self, secret):
+        config = self.ident + self.salt
+        hash = safe_crypt(secret, config)
+        if hash:
+            assert hash.startswith(config) and len(hash) == len(config) + 23
+            return hash[-22:]
+        else:
+            return self._calc_checksum_builtin(secret)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+class apr_md5_crypt(_MD5_Common):
+    """This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "apr_md5_crypt"
+    ident = u("$apr1$")
+
+    #===================================================================
+    # methods
+    #===================================================================
+    def _calc_checksum(self, secret):
+        return _raw_md5_crypt(secret, self.salt, use_apr=True)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/misc.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,242 @@
+"""passlib.handlers.misc - misc generic handlers
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import sys
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_native_str, consteq
+from passlib.utils.compat import bytes, unicode, u, b, base_string_types
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "unix_disabled",
+    "unix_fallback",
+    "plaintext",
+]
+
+#=============================================================================
+# handler
+#=============================================================================
+class unix_fallback(uh.StaticHandler):
+    """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`.
+
+    This class does not implement a hash, but instead provides fallback
+    behavior as found in /etc/shadow on most unix variants.
+    If used, should be the last scheme in the context.
+
+    * this class will positive identify all hash strings.
+    * for security, newly encrypted passwords will hash to ``!``.
+    * it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used).
+    * by default it rejects all passwords if the hash is an empty string,
+      but if ``enable_wildcard=True`` is passed to verify(),
+      all passwords will be allowed through if the hash is an empty string.
+
+    .. deprecated:: 1.6
+        This has been deprecated due to it's "wildcard" feature,
+        and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
+    """
+    name = "unix_fallback"
+    context_kwds = ("enable_wildcard",)
+
+    @classmethod
+    def identify(cls, hash):
+        if isinstance(hash, base_string_types):
+            return True
+        else:
+            raise uh.exc.ExpectedStringError(hash, "hash")
+
+    def __init__(self, enable_wildcard=False, **kwds):
+        warn("'unix_fallback' is deprecated, "
+             "and will be removed in Passlib 1.8; "
+             "please use 'unix_disabled' instead.",
+             DeprecationWarning)
+        super(unix_fallback, self).__init__(**kwds)
+        self.enable_wildcard = enable_wildcard
+
+    @classmethod
+    def genhash(cls, secret, config):
+        # override default to preserve checksum
+        if config is None:
+            return cls.encrypt(secret)
+        else:
+            uh.validate_secret(secret)
+            self = cls.from_string(config)
+            self.checksum = self._calc_checksum(secret)
+            return self.to_string()
+
+    def _calc_checksum(self, secret):
+        if self.checksum:
+            # NOTE: hash will generally be "!", but we want to preserve
+            # it in case it's something else, like "*".
+            return self.checksum
+        else:
+            return u("!")
+
+    @classmethod
+    def verify(cls, secret, hash, enable_wildcard=False):
+        uh.validate_secret(secret)
+        if not isinstance(hash, base_string_types):
+            raise uh.exc.ExpectedStringError(hash, "hash")
+        elif hash:
+            return False
+        else:
+            return enable_wildcard
+
+_MARKER_CHARS = u("*!")
+_MARKER_BYTES = b("*!")
+
+class unix_disabled(uh.PasswordHash):
+    """This class provides disabled password behavior for unix shadow files,
+    and follows the :ref:`password-hash-api`.
+
+    This class does not implement a hash, but instead matches the "disabled account"
+    strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
+    will simply return the disabled account marker. It will reject all passwords,
+    no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.encrypt`
+    method supports one optional keyword:
+
+    :type marker: str
+    :param marker:
+        Optional marker string which overrides the platform default
+        used to indicate a disabled account.
+
+        If not specified, this will default to ``"*"`` on BSD systems,
+        and use the Linux default ``"!"`` for all other platforms.
+        (:attr:`!unix_disabled.default_marker` will contain the default value)
+
+    .. versionadded:: 1.6
+        This class was added as a replacement for the now-deprecated
+        :class:`unix_fallback` class, which had some undesirable features.
+    """
+    name = "unix_disabled"
+    setting_kwds = ("marker",)
+    context_kwds = ()
+
+    if 'bsd' in sys.platform: # pragma: no cover -- runtime detection
+        default_marker = u("*")
+    else:
+        # use the linux default for other systems
+        # (glibc also supports adding old hash after the marker
+        # so it can be restored later).
+        default_marker = u("!")
+
+    @classmethod
+    def identify(cls, hash):
+        # NOTE: technically, anything in the /etc/shadow password field
+        #       which isn't valid crypt() output counts as "disabled".
+        #       but that's rather ambiguous, and it's hard to predict what
+        #       valid output is for unknown crypt() implementations.
+        #       so to be on the safe side, we only match things *known*
+        #       to be disabled field indicators, and will add others
+        #       as they are found. things beginning w/ "$" should *never* match.
+        #
+        # things currently matched:
+        #       * linux uses "!"
+        #       * bsd uses "*"
+        #       * linux may use "!" + hash to disable but preserve original hash
+        #       * linux counts empty string as "any password"
+        if isinstance(hash, unicode):
+            start = _MARKER_CHARS
+        elif isinstance(hash, bytes):
+            start = _MARKER_BYTES
+        else:
+            raise uh.exc.ExpectedStringError(hash, "hash")
+        return not hash or hash[0] in start
+
+    @classmethod
+    def encrypt(cls, secret, marker=None):
+        return cls.genhash(secret, None, marker)
+
+    @classmethod
+    def verify(cls, secret, hash):
+        uh.validate_secret(secret)
+        if not cls.identify(hash): # handles typecheck
+            raise uh.exc.InvalidHashError(cls)
+        return False
+
+    @classmethod
+    def genconfig(cls):
+        return None
+
+    @classmethod
+    def genhash(cls, secret, config, marker=None):
+        uh.validate_secret(secret)
+        if config is not None and not cls.identify(config): # handles typecheck
+            raise uh.exc.InvalidHashError(cls)
+        if config:
+            # we want to preserve the existing str,
+            # since it might contain a disabled password hash ("!" + hash)
+            return to_native_str(config, param="config")
+        # if None or empty string, replace with marker
+        if marker:
+            if not cls.identify(marker):
+                raise ValueError("invalid marker: %r" % marker)
+        else:
+            marker = cls.default_marker
+            assert marker and cls.identify(marker)
+        return to_native_str(marker, param="marker")
+
+class plaintext(uh.PasswordHash):
+    """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+    following additional contextual keyword:
+
+    :type encoding: str
+    :param encoding:
+        This controls the character encoding to use (defaults to ``utf-8``).
+
+        This encoding will be used to encode :class:`!unicode` passwords
+        under Python 2, and decode :class:`!bytes` hashes under Python 3.
+
+    .. versionchanged:: 1.6
+        The ``encoding`` keyword was added.
+    """
+    # NOTE: this is subclassed by ldap_plaintext
+
+    name = "plaintext"
+    setting_kwds = ()
+    context_kwds = ("encoding",)
+    default_encoding = "utf-8"
+
+    @classmethod
+    def identify(cls, hash):
+        if isinstance(hash, base_string_types):
+            return True
+        else:
+            raise uh.exc.ExpectedStringError(hash, "hash")
+
+    @classmethod
+    def encrypt(cls, secret, encoding=None):
+        uh.validate_secret(secret)
+        if not encoding:
+            encoding = cls.default_encoding
+        return to_native_str(secret, encoding, "secret")
+
+    @classmethod
+    def verify(cls, secret, hash, encoding=None):
+        if not encoding:
+            encoding = cls.default_encoding
+        hash = to_native_str(hash, encoding, "hash")
+        if not cls.identify(hash):
+            raise uh.exc.InvalidHashError(cls)
+        return consteq(cls.encrypt(secret, encoding), hash)
+
+    @classmethod
+    def genconfig(cls):
+        return None
+
+    @classmethod
+    def genhash(cls, secret, hash, encoding=None):
+        if hash is not None and not cls.identify(hash):
+            raise uh.exc.InvalidHashError(cls)
+        return cls.encrypt(secret, encoding)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/mssql.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,246 @@
+"""passlib.handlers.mssql - MS-SQL Password Hash
+
+Notes
+=====
+MS-SQL has used a number of hash algs over the years,
+most of which were exposed through the undocumented
+'pwdencrypt' and 'pwdcompare' sql functions.
+
+Known formats
+-------------
+6.5
+    snefru hash, ascii encoded password
+    no examples found
+
+7.0
+    snefru hash, unicode (what encoding?)
+    saw ref that these blobs were 16 bytes in size
+    no examples found
+
+2000
+    byte string using displayed as 0x hex, using 0x0100 prefix.
+    contains hashes of password and upper-case password.
+
+2007
+    same as 2000, but without the upper-case hash.
+
+refs
+----------
+https://blogs.msdn.com/b/lcris/archive/2007/04/30/sql-server-2005-about-login-password-hashes.aspx?Redirected=true
+http://us.generation-nt.com/securing-passwords-hash-help-35429432.html
+http://forum.md5decrypter.co.uk/topic230-mysql-and-mssql-get-password-hashes.aspx
+http://www.theregister.co.uk/2002/07/08/cracking_ms_sql_server_passwords/
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from binascii import hexlify, unhexlify
+from hashlib import sha1
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import consteq
+from passlib.utils.compat import b, bytes, bascii_to_str, unicode, u
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "mssql2000",
+    "mssql2005",
+]
+
+#=============================================================================
+# mssql 2000
+#=============================================================================
+def _raw_mssql(secret, salt):
+    assert isinstance(secret, unicode)
+    assert isinstance(salt, bytes)
+    return sha1(secret.encode("utf-16-le") + salt).digest()
+
+BIDENT = b("0x0100")
+##BIDENT2 = b("\x01\x00")
+UIDENT = u("0x0100")
+
+def _ident_mssql(hash, csize, bsize):
+    "common identify for mssql 2000/2005"
+    if isinstance(hash, unicode):
+        if len(hash) == csize and hash.startswith(UIDENT):
+            return True
+    elif isinstance(hash, bytes):
+        if len(hash) == csize and hash.startswith(BIDENT):
+            return True
+        ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
+        ##    return True
+    else:
+        raise uh.exc.ExpectedStringError(hash, "hash")
+    return False
+
+def _parse_mssql(hash, csize, bsize, handler):
+    "common parser for mssql 2000/2005; returns 4 byte salt + checksum"
+    if isinstance(hash, unicode):
+        if len(hash) == csize and hash.startswith(UIDENT):
+            try:
+                return unhexlify(hash[6:].encode("utf-8"))
+            except TypeError: # throw when bad char found
+                pass
+    elif isinstance(hash, bytes):
+        # assumes ascii-compat encoding
+        assert isinstance(hash, bytes)
+        if len(hash) == csize and hash.startswith(BIDENT):
+            try:
+                return unhexlify(hash[6:])
+            except TypeError: # throw when bad char found
+                pass
+        ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
+        ##    return hash[2:]
+    else:
+        raise uh.exc.ExpectedStringError(hash, "hash")
+    raise uh.exc.InvalidHashError(handler)
+
+class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 4 bytes in length.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+    """
+    #===================================================================
+    # algorithm information
+    #===================================================================
+    name = "mssql2000"
+    setting_kwds = ("salt",)
+    checksum_size = 40
+    min_salt_size = max_salt_size = 4
+    _stub_checksum = b("\x00") * 40
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    # 0100 - 2 byte identifier
+    # 4 byte salt
+    # 20 byte checksum
+    # 20 byte checksum
+    # = 46 bytes
+    # encoded '0x' + 92 chars = 94
+
+    @classmethod
+    def identify(cls, hash):
+        return _ident_mssql(hash, 94, 46)
+
+    @classmethod
+    def from_string(cls, hash):
+        data = _parse_mssql(hash, 94, 46, cls)
+        return cls(salt=data[:4], checksum=data[4:])
+
+    def to_string(self):
+        raw = self.salt + (self.checksum or self._stub_checksum)
+        # raw bytes format - BIDENT2 + raw
+        return "0x0100" + bascii_to_str(hexlify(raw).upper())
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, bytes):
+            secret = secret.decode("utf-8")
+        salt = self.salt
+        return _raw_mssql(secret, salt) + _raw_mssql(secret.upper(), salt)
+
+    @classmethod
+    def verify(cls, secret, hash):
+        # NOTE: we only compare against the upper-case hash
+        # XXX: add 'full' just to verify both checksums?
+        uh.validate_secret(secret)
+        self = cls.from_string(hash)
+        chk = self.checksum
+        if chk is None:
+            raise uh.exc.MissingDigestError(cls)
+        if isinstance(secret, bytes):
+            secret = secret.decode("utf-8")
+        result = _raw_mssql(secret.upper(), self.salt)
+        return consteq(result, chk[20:])
+
+#=============================================================================
+# handler
+#=============================================================================
+class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 4 bytes in length.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+    """
+    #===================================================================
+    # algorithm information
+    #===================================================================
+    name = "mssql2005"
+    setting_kwds = ("salt",)
+
+    checksum_size = 20
+    min_salt_size = max_salt_size = 4
+    _stub_checksum = b("\x00") * 20
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    # 0x0100 - 2 byte identifier
+    # 4 byte salt
+    # 20 byte checksum
+    # = 26 bytes
+    # encoded '0x' + 52 chars = 54
+
+    @classmethod
+    def identify(cls, hash):
+        return _ident_mssql(hash, 54, 26)
+
+    @classmethod
+    def from_string(cls, hash):
+        data = _parse_mssql(hash, 54, 26, cls)
+        return cls(salt=data[:4], checksum=data[4:])
+
+    def to_string(self):
+        raw = self.salt + (self.checksum or self._stub_checksum)
+        # raw bytes format - BIDENT2 + raw
+        return "0x0100" + bascii_to_str(hexlify(raw)).upper()
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, bytes):
+            secret = secret.decode("utf-8")
+        return _raw_mssql(secret, self.salt)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/mysql.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,128 @@
+"""passlib.handlers.mysql
+
+MySQL 3.2.3 / OLD_PASSWORD()
+
+    This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1.
+
+    See :mod:`passlib.handlers.mysql_41` for the new algorithm was put in place in version 4.1
+
+    This algorithm is known to be very insecure, and should only be used to verify existing password hashes.
+
+    http://djangosnippets.org/snippets/1508/
+
+MySQL 4.1.1 / NEW PASSWORD
+    This implements Mysql new PASSWORD algorithm, introduced in version 4.1.
+
+    This function is unsalted, and therefore not very secure against rainbow attacks.
+    It should only be used when dealing with mysql passwords,
+    for all other purposes, you should use a salted hash function.
+
+    Description taken from http://dev.mysql.com/doc/refman/6.0/en/password-hashing.html
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from hashlib import sha1
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_native_str
+from passlib.utils.compat import b, bascii_to_str, bytes, unicode, u, \
+                                 byte_elem_value, str_to_uascii
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    'mysql323',
+    'mysq41',
+]
+
+#=============================================================================
+# backend
+#=============================================================================
+class mysql323(uh.StaticHandler):
+    """This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`.
+
+    It has no salt and a single fixed round.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "mysql323"
+    checksum_size = 16
+    checksum_chars = uh.HEX_CHARS
+
+    #===================================================================
+    # methods
+    #===================================================================
+    @classmethod
+    def _norm_hash(cls, hash):
+        return hash.lower()
+
+    def _calc_checksum(self, secret):
+        # FIXME: no idea if mysql has a policy about handling unicode passwords
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+
+        MASK_32 = 0xffffffff
+        MASK_31 = 0x7fffffff
+        WHITE = b(' \t')
+
+        nr1 = 0x50305735
+        nr2 = 0x12345671
+        add = 7
+        for c in secret:
+            if c in WHITE:
+                continue
+            tmp = byte_elem_value(c)
+            nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32
+            nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32
+            add = (add+tmp) & MASK_32
+        return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# handler
+#=============================================================================
+class mysql41(uh.StaticHandler):
+    """This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`.
+
+    It has no salt and a single fixed round.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    name = "mysql41"
+    _hash_prefix = u("*")
+    checksum_chars = uh.HEX_CHARS
+    checksum_size = 40
+
+    #===================================================================
+    # methods
+    #===================================================================
+    @classmethod
+    def _norm_hash(cls, hash):
+        return hash.upper()
+
+    def _calc_checksum(self, secret):
+        # FIXME: no idea if mysql has a policy about handling unicode passwords
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper()
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/oracle.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,175 @@
+"""passlib.handlers.oracle - Oracle DB Password Hashes"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from binascii import hexlify, unhexlify
+from hashlib import sha1
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_unicode, to_native_str, xor_bytes
+from passlib.utils.compat import b, bytes, bascii_to_str, irange, u, \
+                                 uascii_to_str, unicode, str_to_uascii
+from passlib.utils.des import des_encrypt_block
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "oracle10g",
+    "oracle11g"
+]
+
+#=============================================================================
+# oracle10
+#=============================================================================
+def des_cbc_encrypt(key, value, iv=b('\x00') * 8, pad=b('\x00')):
+    """performs des-cbc encryption, returns only last block.
+
+    this performs a specific DES-CBC encryption implementation
+    as needed by the Oracle10 hash. it probably won't be useful for
+    other purposes as-is.
+
+    input value is null-padded to multiple of 8 bytes.
+
+    :arg key: des key as bytes
+    :arg value: value to encrypt, as bytes.
+    :param iv: optional IV
+    :param pad: optional pad byte
+
+    :returns: last block of DES-CBC encryption of all ``value``'s byte blocks.
+    """
+    value += pad * (-len(value) % 8) # null pad to multiple of 8
+    hash = iv # start things off
+    for offset in irange(0,len(value),8):
+        chunk = xor_bytes(hash, value[offset:offset+8])
+        hash = des_encrypt_block(key, chunk)
+    return hash
+
+# magic string used as initial des key by oracle10
+ORACLE10_MAGIC = b("\x01\x23\x45\x67\x89\xAB\xCD\xEF")
+
+class oracle10(uh.HasUserContext, uh.StaticHandler):
+    """This class implements the password hash used by Oracle up to version 10g, and follows the :ref:`password-hash-api`.
+
+    It does a single round of hashing, and relies on the username as the salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+    following additional contextual keywords:
+
+    :type user: str
+    :param user: name of oracle user account this password is associated with.
+    """
+    #===================================================================
+    # algorithm information
+    #===================================================================
+    name = "oracle10"
+    checksum_chars = uh.HEX_CHARS
+    checksum_size = 16
+
+    #===================================================================
+    # methods
+    #===================================================================
+    @classmethod
+    def _norm_hash(cls, hash):
+        return hash.upper()
+
+    def _calc_checksum(self, secret):
+        # FIXME: not sure how oracle handles unicode.
+        #        online docs about 10g hash indicate it puts ascii chars
+        #        in a 2-byte encoding w/ the high byte set to null.
+        #        they don't say how it handles other chars, or what encoding.
+        #
+        #        so for now, encoding secret & user to utf-16-be,
+        #        since that fits, and if secret/user is bytes,
+        #        we assume utf-8, and decode first.
+        #
+        #        this whole mess really needs someone w/ an oracle system,
+        #        and some answers :)
+        if isinstance(secret, bytes):
+            secret = secret.decode("utf-8")
+        user = to_unicode(self.user, "utf-8", param="user")
+        input = (user+secret).upper().encode("utf-16-be")
+        hash = des_cbc_encrypt(ORACLE10_MAGIC, input)
+        hash = des_cbc_encrypt(hash, input)
+        return hexlify(hash).decode("ascii").upper()
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# oracle11
+#=============================================================================
+class oracle11(uh.HasSalt, uh.GenericHandler):
+    """This class implements the Oracle11g password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 20 hexidecimal characters.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "oracle11"
+    setting_kwds = ("salt",)
+    checksum_size = 40
+    checksum_chars = uh.UPPER_HEX_CHARS
+
+    _stub_checksum = u('0') * 40
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 20
+    salt_chars = uh.UPPER_HEX_CHARS
+
+
+    #===================================================================
+    # methods
+    #===================================================================
+    _hash_regex = re.compile(u("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$"), re.I)
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        salt, chk = m.group("salt", "chk")
+        return cls(salt=salt, checksum=chk.upper())
+
+    def to_string(self):
+        chk = (self.checksum or self._stub_checksum)
+        hash = u("S:%s%s") % (chk.upper(), self.salt.upper())
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest()
+        return str_to_uascii(chk).upper()
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/pbkdf2.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,488 @@
+"""passlib.handlers.pbkdf - PBKDF2 based hashes"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from binascii import hexlify, unhexlify
+from base64 import b64encode, b64decode
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import ab64_decode, ab64_encode, to_unicode
+from passlib.utils.compat import b, bytes, str_to_bascii, u, uascii_to_str, unicode
+from passlib.utils.pbkdf2 import pbkdf2
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "pbkdf2_sha1",
+    "pbkdf2_sha256",
+    "pbkdf2_sha512",
+    "cta_pbkdf2_sha1",
+    "dlitz_pbkdf2_sha1",
+    "grub_pbkdf2_sha512",
+]
+
+#=============================================================================
+#
+#=============================================================================
+class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    "base class for various pbkdf2_{digest} algorithms"
+    #===================================================================
+    # class attrs
+    #===================================================================
+
+    #--GenericHandler--
+    setting_kwds = ("salt", "salt_size", "rounds")
+    checksum_chars = uh.HASH64_CHARS
+
+    #--HasSalt--
+    default_salt_size = 16
+    min_salt_size = 0
+    max_salt_size = 1024
+
+    #--HasRounds--
+    default_rounds = None # set by subclass
+    min_rounds = 1
+    max_rounds = 0xffffffff # setting at 32-bit limit for now
+    rounds_cost = "linear"
+
+    #--this class--
+    _prf = None # subclass specified prf identifier
+
+    # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check.
+    #       the underlying pbkdf2 specifies no bounds for either.
+
+    # NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends...
+    #       >8 bytes of entropy in salt, >1000 rounds
+    #       increased due to time since rfc established
+
+    #===================================================================
+    # methods
+    #===================================================================
+
+    @classmethod
+    def from_string(cls, hash):
+        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
+        salt = ab64_decode(salt.encode("ascii"))
+        if chk:
+            chk = ab64_decode(chk.encode("ascii"))
+        return cls(rounds=rounds, salt=salt, checksum=chk)
+
+    def to_string(self, withchk=True):
+        salt = ab64_encode(self.salt).decode("ascii")
+        if withchk and self.checksum:
+            chk = ab64_encode(self.checksum).decode("ascii")
+        else:
+            chk = None
+        return uh.render_mc3(self.ident, self.rounds, salt, chk)
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return pbkdf2(secret, self.salt, self.rounds, self.checksum_size, self._prf)
+
+def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
+    "create new Pbkdf2DigestHandler subclass for a specific hash"
+    name = 'pbkdf2_' + hash_name
+    if ident is None:
+        ident = u("$pbkdf2-%s$") % (hash_name,)
+    prf = "hmac-%s" % (hash_name,)
+    base = Pbkdf2DigestHandler
+    return type(name, (base,), dict(
+        __module__=module, # so ABCMeta won't clobber it.
+        name=name,
+        ident=ident,
+        _prf = prf,
+        default_rounds=rounds,
+        checksum_size=digest_size,
+        encoded_checksum_size=(digest_size*4+2)//3,
+        __doc__="""This class implements a generic ``PBKDF2-%(prf)s``-based password hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt bytes.
+        If specified, the length must be between 0-1024 bytes.
+        If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended).
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 16 bytes, but can be any value between 0 and 1024.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to %(dr)d, but must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=rounds)
+    ))
+
+#------------------------------------------------------------------------
+# 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)
+
+ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
+ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
+ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True)
+
+#=============================================================================
+# cryptacular's pbkdf2 hash
+#=============================================================================
+
+# bytes used by cta hash for base64 values 63 & 64
+CTA_ALTCHARS = b("-_")
+
+class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements Cryptacular's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt bytes.
+        If specified, it may be any length.
+        If not specified, a one will be autogenerated (this is recommended).
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 16 bytes, but can be any value between 0 and 1024.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 60000, must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "cta_pbkdf2_sha1"
+    setting_kwds = ("salt", "salt_size", "rounds")
+    ident = u("$p5k2$")
+
+    # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
+    #       sanity check. underlying algorithm (and reference implementation)
+    #       allows effectively unbounded values for both of these parameters.
+
+    #--HasSalt--
+    default_salt_size = 16
+    min_salt_size = 0
+    max_salt_size = 1024
+
+    #--HasRounds--
+    default_rounds = 60000
+    min_rounds = 1
+    max_rounds = 0xffffffff # setting at 32-bit limit for now
+    rounds_cost = "linear"
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    # hash       $p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0=
+    # ident      $p5k2$
+    # rounds     1000
+    # salt       ZxK4ZBJCfQg=
+    # chk        jJZVscWtO--p1-xIZl6jhO2LKR0=
+    # NOTE: rounds in hex
+
+    @classmethod
+    def from_string(cls, hash):
+        # NOTE: passlib deviation - forbidding zero-padded rounds
+        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16, handler=cls)
+        salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS)
+        if chk:
+            chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS)
+        return cls(rounds=rounds, salt=salt, checksum=chk)
+
+    def to_string(self, withchk=True):
+        salt = b64encode(self.salt, CTA_ALTCHARS).decode("ascii")
+        if withchk and self.checksum:
+            chk = b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")
+        else:
+            chk = None
+        return uh.render_mc3(self.ident, self.rounds, salt, chk, rounds_base=16)
+
+    #===================================================================
+    # backend
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return pbkdf2(secret, self.salt, self.rounds, 20, "hmac-sha1")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# dlitz's pbkdf2 hash
+#=============================================================================
+class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
+    """This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If specified, it may be any length, but must use the characters in the regexp range ``[./0-9A-Za-z]``.
+        If not specified, a 16 character salt will be autogenerated (this is recommended).
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 16 bytes, but can be any value between 0 and 1024.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 60000, must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "dlitz_pbkdf2_sha1"
+    setting_kwds = ("salt", "salt_size", "rounds")
+    ident = u("$p5k2$")
+
+    # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
+    #       sanity check. underlying algorithm (and reference implementation)
+    #       allows effectively unbounded values for both of these parameters.
+
+    #--HasSalt--
+    default_salt_size = 16
+    min_salt_size = 0
+    max_salt_size = 1024
+    salt_chars = uh.HASH64_CHARS
+
+    #--HasRounds--
+    default_rounds = 60000
+    min_rounds = 1
+    max_rounds = 0xffffffff # setting at 32-bit limit for now
+    rounds_cost = "linear"
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    # hash       $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
+    # ident      $p5k2$
+    # rounds     c
+    # salt       u9HvcT4d
+    # chk        Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
+    # rounds in lowercase hex, no zero padding
+
+    @classmethod
+    def from_string(cls, hash):
+        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16,
+                                         default_rounds=400, handler=cls)
+        return cls(rounds=rounds, salt=salt, checksum=chk)
+
+    def to_string(self, withchk=True):
+        rounds = self.rounds
+        if rounds == 400:
+            rounds = None # omit rounds measurement if == 400
+        return uh.render_mc3(self.ident, rounds, self.salt,
+                             checksum=self.checksum if withchk else None,
+                             rounds_base=16)
+
+    #===================================================================
+    # backend
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        salt = str_to_bascii(self.to_string(withchk=False))
+        result = pbkdf2(secret, salt, self.rounds, 24, "hmac-sha1")
+        return ab64_encode(result).decode("ascii")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# crowd
+#=============================================================================
+class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements the PBKDF2 hash used by Atlassian.
+
+    It supports a fixed-length salt, and a fixed number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt bytes.
+        If specified, the length must be exactly 16 bytes.
+        If not specified, a salt will be autogenerated (this is recommended).
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include
+        ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    #--GenericHandler--
+    name = "atlassian_pbkdf2_sha1"
+    setting_kwds =("salt",)
+    ident = u("{PKCS5S2}")
+    checksum_size = 32
+
+    _stub_checksum = b("\x00") * 32
+
+    #--HasRawSalt--
+    min_salt_size = max_salt_size = 16
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        ident = cls.ident
+        if not hash.startswith(ident):
+            raise uh.exc.InvalidHashError(cls)
+        data = b64decode(hash[len(ident):].encode("ascii"))
+        salt, chk = data[:16], data[16:]
+        return cls(salt=salt, checksum=chk)
+
+    def to_string(self):
+        data = self.salt + (self.checksum or self._stub_checksum)
+        hash = self.ident + b64encode(data).decode("ascii")
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        # TODO: find out what crowd's policy is re: unicode
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        # crowd seems to use a fixed number of rounds.
+        return pbkdf2(secret, self.salt, 10000, 32, "hmac-sha1")
+
+#=============================================================================
+# grub
+#=============================================================================
+class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class implements Grub's pbkdf2-hmac-sha512 hash, and follows the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt bytes.
+        If specified, the length must be between 0-1024 bytes.
+        If not specified, a 64 byte salt will be autogenerated (this is recommended).
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 64 bytes, but can be any value between 0 and 1024.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 12000, but must be within ``range(1,1<<32)``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+    name = "grub_pbkdf2_sha512"
+    setting_kwds = ("salt", "salt_size", "rounds")
+
+    ident = u("grub.pbkdf2.sha512.")
+
+    # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
+    #       sanity check. the underlying pbkdf2 specifies no bounds for either,
+    #       and it's not clear what grub specifies.
+
+    default_salt_size = 64
+    min_salt_size = 0
+    max_salt_size = 1024
+
+    default_rounds = 12000
+    min_rounds = 1
+    max_rounds = 0xffffffff # setting at 32-bit limit for now
+    rounds_cost = "linear"
+
+    @classmethod
+    def from_string(cls, hash):
+        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."),
+                                         handler=cls)
+        salt = unhexlify(salt.encode("ascii"))
+        if chk:
+            chk = unhexlify(chk.encode("ascii"))
+        return cls(rounds=rounds, salt=salt, checksum=chk)
+
+    def to_string(self, withchk=True):
+        salt = hexlify(self.salt).decode("ascii").upper()
+        if withchk and self.checksum:
+            chk = hexlify(self.checksum).decode("ascii").upper()
+        else:
+            chk = None
+        return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u("."))
+
+    def _calc_checksum(self, secret):
+        # TODO: find out what grub's policy is re: unicode
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        return pbkdf2(secret, self.salt, self.rounds, 64, "hmac-sha512")
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/phpass.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,137 @@
+"""passlib.handlers.phpass - PHPass Portable Crypt
+
+phppass located - http://www.openwall.com/phpass/
+algorithm described - http://www.openwall.com/articles/PHP-Users-Passwords
+
+phpass context - blowfish, bsdi_crypt, phpass
+"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from hashlib import md5
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import h64
+from passlib.utils.compat import b, bytes, u, uascii_to_str, unicode
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "phpass",
+]
+
+#=============================================================================
+# phpass
+#=============================================================================
+class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
+    """This class implements the PHPass Portable Hash, and follows the :ref:`password-hash-api`.
+
+    It supports a fixed-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: str
+    :param salt:
+        Optional salt string.
+        If not specified, one will be autogenerated (this is recommended).
+        If specified, it must be 8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 16, must be between 7 and 30, inclusive.
+        This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
+
+    :type ident: str
+    :param ident:
+        phpBB3 uses ``H`` instead of ``P`` for it's identifier,
+        this may be set to ``H`` in order to generate phpBB3 compatible hashes.
+        it defaults to ``P``.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+    """
+
+    #===================================================================
+    # class attrs
+    #===================================================================
+    #--GenericHandler--
+    name = "phpass"
+    setting_kwds = ("salt", "rounds", "ident")
+    checksum_chars = uh.HASH64_CHARS
+
+    #--HasSalt--
+    min_salt_size = max_salt_size = 8
+    salt_chars = uh.HASH64_CHARS
+
+    #--HasRounds--
+    default_rounds = 16
+    min_rounds = 7
+    max_rounds = 30
+    rounds_cost = "log2"
+
+    #--HasManyIdents--
+    default_ident = u("$P$")
+    ident_values = [u("$P$"), u("$H$")]
+    ident_aliases = {u("P"):u("$P$"), u("H"):u("$H$")}
+
+    #===================================================================
+    # formatting
+    #===================================================================
+
+    #$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0
+    # $P$
+    # 9
+    # IQRaTwmf
+    # eRo7ud9Fh4E2PdI0S3r.L0
+
+    @classmethod
+    def from_string(cls, hash):
+        ident, data = cls._parse_ident(hash)
+        rounds, salt, chk = data[0], data[1:9], data[9:]
+        return cls(
+            ident=ident,
+            rounds=h64.decode_int6(rounds.encode("ascii")),
+            salt=salt,
+            checksum=chk or None,
+        )
+
+    def to_string(self):
+        hash = u("%s%s%s%s") % (self.ident,
+                              h64.encode_int6(self.rounds).decode("ascii"),
+                              self.salt,
+                              self.checksum or u(''))
+        return uascii_to_str(hash)
+
+    #===================================================================
+    # backend
+    #===================================================================
+    def _calc_checksum(self, secret):
+        # FIXME: can't find definitive policy on how phpass handles non-ascii.
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        real_rounds = 1<<self.rounds
+        result = md5(self.salt.encode("ascii") + secret).digest()
+        r = 0
+        while r < real_rounds:
+            result = md5(result + secret).digest()
+            r += 1
+        return h64.encode_bytes(result).decode("ascii")
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/postgres.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,57 @@
+"""passlib.handlers.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from hashlib import md5
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.utils import to_bytes
+from passlib.utils.compat import b, bytes, str_to_uascii, unicode, u
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "postgres_md5",
+]
+
+#=============================================================================
+# handler
+#=============================================================================
+class postgres_md5(uh.HasUserContext, uh.StaticHandler):
+    """This class implements the Postgres MD5 Password hash, and follows the :ref:`password-hash-api`.
+
+    It does a single round of hashing, and relies on the username as the salt.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+    following additional contextual keywords:
+
+    :type user: str
+    :param user: name of postgres user account this password is associated with.
+    """
+    #===================================================================
+    # algorithm information
+    #===================================================================
+    name = "postgres_md5"
+    _hash_prefix = u("md5")
+    checksum_chars = uh.HEX_CHARS
+    checksum_size = 32
+
+    #===================================================================
+    # primary interface
+    #===================================================================
+    def _calc_checksum(self, secret):
+        if isinstance(secret, unicode):
+            secret = secret.encode("utf-8")
+        user = to_bytes(self.user, "utf-8", param="user")
+        return str_to_uascii(md5(secret + user).hexdigest())
+
+    #===================================================================
+    # eoc
+    #===================================================================
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/roundup.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,29 @@
+"""passlib.handlers.roundup - Roundup issue tracker hashes"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import logging; log = logging.getLogger(__name__)
+# site
+# pkg
+import passlib.utils.handlers as uh
+from passlib.utils.compat import u
+# local
+__all__ = [
+    "roundup_plaintext",
+    "ldap_hex_md5",
+    "ldap_hex_sha1",
+]
+#=============================================================================
+#
+#=============================================================================
+roundup_plaintext = uh.PrefixWrapper("roundup_plaintext", "plaintext",
+                                     prefix=u("{plaintext}"), lazy=True)
+
+# NOTE: these are here because they're currently only known to be used by roundup
+ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u("{MD5}"), lazy=True)
+ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u("{SHA}"), lazy=True)
+
+#=============================================================================
+# eof
+#=============================================================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/passlib/handlers/scram.py	Fri Jan 18 01:38:07 2013 +0100
@@ -0,0 +1,576 @@
+"""passlib.handlers.scram - hash for SCRAM credential storage"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+from binascii import hexlify, unhexlify
+from base64 import b64encode, b64decode
+import hashlib
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+# site
+# pkg
+from passlib.exc import PasslibHashWarning
+from passlib.utils import ab64_decode, ab64_encode, consteq, saslprep, \
+                          to_native_str, xor_bytes, splitcomma
+from passlib.utils.compat import b, bytes, bascii_to_str, iteritems, \
+                                 PY3, u, unicode
+from passlib.utils.pbkdf2 import pbkdf2, get_prf, norm_hash_name
+import passlib.utils.handlers as uh
+# local
+__all__ = [
+    "scram",
+]
+
+#=============================================================================
+# scram credentials hash
+#=============================================================================
+class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
+    """This class provides a format for storing SCRAM passwords, and follows
+    the :ref:`password-hash-api`.
+
+    It supports a variable-length salt, and a variable number of rounds.
+
+    The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+
+    :type salt: bytes
+    :param salt:
+        Optional salt bytes.
+        If specified, the length must be between 0-1024 bytes.
+        If not specified, a 12 byte salt will be autogenerated
+        (this is recommended).
+
+    :type salt_size: int
+    :param salt_size:
+        Optional number of bytes to use when autogenerating new salts.
+        Defaults to 12 bytes, but can be any value between 0 and 1024.
+
+    :type rounds: int
+    :param rounds:
+        Optional number of rounds to use.
+        Defaults to 6400, but must be within ``range(1,1<<32)``.
+
+    :type algs: list of strings
+    :param algs:
+        Specify list of digest algorithms to use.
+
+        By default each scram hash will contain digests for SHA-1,
+        SHA-256, and SHA-512. This can be overridden by specify either be a
+        list such as ``["sha-1", "sha-256"]``, or a comma-separated string
+        such as ``"sha-1, sha-256"``. Names are case insensitive, and may
+        use :mod:`!hashlib` or `IANA <http://www.iana.org/assignments/hash-function-text-names>`_
+        hash names.
+
+    :type relaxed: bool
+    :param relaxed:
+        By default, providing an invalid value for one of the other
+        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
+        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
+        will be issued instead. Correctable errors include ``rounds``
+        that are too small or too large, and ``salt`` strings that are too long.
+
+        .. versionadded:: 1.6
+
+    In addition to the standard :ref:`password-hash-api` methods,
+    this class also provides the following methods for manipulating Passlib
+    scram hashes in ways useful for pluging into a SCRAM protocol stack:
+
+    .. automethod:: extract_digest_info
+    .. automethod:: extract_digest_algs
+    .. automethod:: derive_digest
+    """
+    #===================================================================
+    # class attrs
+    #===================================================================
+
+    # NOTE: unlike most GenericHandler classes, the 'checksum' attr of
+    # ScramHandler is actually a map from digest_name -> digest, so
+    # many of the standard methods have been overridden.
+
+    # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide
+    # a sanity check; the underlying pbkdf2 specifies no bounds for either.
+
+    #--GenericHandler--
+    name = "scram"
+    setting_kwds = ("salt", "salt_size", "rounds", "algs")
+    ident = u("$scram$")
+
+    #--HasSalt--
+    default_salt_size = 12
+    min_salt_size = 0
+    max_salt_size = 1024
+
+    #--HasRounds--
+    default_rounds = 6400
+    min_rounds = 1
+    max_rounds = 2**32-1
+    rounds_cost = "linear"
+
+    #--custom--
+
+    # default algorithms when creating new hashes.
+    default_algs = ["sha-1", "sha-256", "sha-512"]
+
+    # list of algs verify prefers to use, in order.
+    _verify_algs = ["sha-256", "sha-512", "sha-224", "sha-384", "sha-1"]
+
+    #===================================================================
+    # instance attrs
+    #===================================================================
+
+    # 'checksum' is different from most GenericHandler subclasses,
+    # in that it contains a dict mapping from alg -> digest,
+    # or None if no checksum present.
+
+    # list of algorithms to create/compare digests for.
+    algs = None
+
+    #===================================================================
+    # scram frontend helpers
+    #===================================================================
+    @classmethod
+    def extract_digest_info(cls, hash, alg):
+        """return (salt, rounds, digest) for specific hash algorithm.
+
+        :type hash: str
+        :arg hash:
+            :class:`!scram` hash stored for desired user
+
+        :type alg: str
+        :arg alg:
+            Name of digest algorithm (e.g. ``"sha-1"``) requested by client.
+
+            This value is run through :func:`~passlib.utils.pbkdf2.norm_hash_name`,
+            so it is case-insensitive, and can be the raw SCRAM
+            mechanism name (e.g. ``"SCRAM-SHA-1"``), the IANA name,
+            or the hashlib name.
+
+        :raises KeyError:
+            If the hash does not contain an entry for the requested digest
+            algorithm.
+
+        :returns:
+            A tuple containing ``(salt, rounds, digest)``,
+            where *digest* matches the raw bytes returned by
+            SCRAM's :func:`Hi` function for the stored password,
+            the provided *salt*, and the iteration count (*rounds*).
+            *salt* and *digest* are both raw (unencoded) bytes.
+        """
+        # XXX: this could be sped up by writing custom parsing routine
+        # that just picks out relevant digest, and doesn't bother
+        # with full structure validation each time it's called.
+        alg = norm_hash_name(alg, 'iana')
+        self = cls.from_string(hash)
+        chkmap = self.checksum
+        if not chkmap:
+            raise ValueError("scram hash contains no digests")
+        return self.salt, self.rounds, chkmap[alg]
+
+    @classmethod
+    def extract_digest_algs(cls, hash, format="iana"):
+        """Return names of all algorithms stored in a given hash.
+
+        :type hash: str
+        :arg hash:
+            The :class:`!scram` hash to parse
+
+        :type format: str
+        :param format:
+            This changes the naming convention used by the
+            returned algorithm names. By default the names
+            are IANA-compatible; see :func:`~passlib.utils.pbkdf2.norm_hash_name`
+            for possible values.
+
+        :returns:
+            Returns a list of digest algorithms; e.g. ``["sha-1"]``
+        """
+        # XXX: this could be sped up by writing custom parsing routine
+        # that just picks out relevant names, and doesn't bother
+        # with full structure validation each time it's called.
+        algs = cls.from_string(hash).algs
+        if format == "iana":
+            return algs
+        else:
+            return [norm_hash_name(alg, format) for alg in algs]
+
+    @classmethod
+    def derive_digest(cls, password, salt, rounds, alg):
+        """helper to create SaltedPassword digest for SCRAM.
+
+        This performs the step in the SCRAM protocol described as::
+
+            SaltedPassword  := Hi(Normalize(password), salt, i)
+
+        :type password: unicode or utf-8 bytes
+        :arg password: password to run through digest
+
+        :type salt: bytes
+        :arg salt: raw salt data
+
+        :type rounds: int
+        :arg rounds: number of iterations.
+
+        :type alg: str
+        :arg alg: name of digest to use (e.g. ``"sha-1"``).
+
+        :returns:
+            raw bytes of ``SaltedPassword``
+        """
+        if isinstance(password, bytes):
+            password = password.decode("utf-8")
+        password = saslprep(password).encode("utf-8")
+        if not isinstance(salt, bytes):
+            raise TypeError("salt must be bytes")
+        if rounds < 1:
+            raise ValueError("rounds must be >= 1")
+        alg = norm_hash_name(alg, "hashlib")
+        return pbkdf2(password, salt, rounds, None, "hmac-" + alg)
+
+    #===================================================================
+    # serialization
+    #===================================================================
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_native_str(hash, "ascii", "hash")
+        if not hash.startswith("$scram$"):
+            raise uh.exc.InvalidHashError(cls)
+        parts = hash[7:].split("$")
+        if len(parts) != 3:
+            raise uh.exc.MalformedHashError(cls)
+        rounds_str, salt_str, chk_str = parts
+
+        # decode rounds
+        rounds = int(rounds_str)
+        if rounds_str != str(rounds): # forbid zero padding, etc.
+            raise uh.exc.MalformedHashError(cls)
+
+        # decode salt
+        try:
+            salt = ab64_decode(salt_str.encode("ascii"))
+        except TypeError:
+            raise uh.exc.MalformedHashError(cls)
+
+        # decode algs/digest list
+        if not chk_str:
+            # scram hashes MUST have something here.
+            raise uh.exc.MalformedHashError(cls)
+        elif "=" in chk_str:
+            # comma-separated list of 'alg=digest' pairs
+            algs = None
+            chkmap = {}
+            for pair in chk_str.split(","):
+                alg, digest = pair.split("=")
+                try:
+                    chkmap[alg] = ab64_decode(digest.encode("ascii"))
+                except TypeError:
+                    raise uh.exc.MalformedHashError(cls)
+        else:
+            # comma-separated list of alg names, no digests
+            algs = chk_str
+            chkmap = None
+
+        # return new object
+        return cls(
+            rounds=rounds,
+            salt=salt,
+            checksum=chkmap,
+            algs=algs,
+        )
+
+    def to_string(self, withchk=True):
+        salt = bascii_to_str(ab64_encode(self.salt))
+        chkmap = self.checksum
+        if withchk and chkmap:
+            chk_str = ",".join(
+                "%s=%s" % (alg, bascii_to_str(ab64_encode(chkmap[alg])))
+                for alg in self.algs
+            )
+        else:
+            chk_str = ",".join(self.algs)
+        return '$scram$%d$%s$%s' % (self.rounds, salt, chk_str)
+
+    #===================================================================
+    # init
+    #===================================================================
+    def __init__(self, algs=None, **kwds):
+        super(scram, self).__init__(**kwds)
+        self.algs = self._norm_algs(algs)
+
+    def _norm_checksum(self, checksum):
+        if checksum is None:
+            return None
+        for alg, digest in iteritems(checksum):
+            if alg != norm_hash_name(alg, 'iana'):
+                raise ValueError("malformed algorithm name in scram hash: %r" %
+                                 (alg,))
+            if len(alg) > 9:
+                raise ValueError("SCRAM limits algorithm names to "
+                                 "9 characters: %r" % (alg,))
+            if not isinstance(digest, bytes):
+                raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests")
+            # TODO: verify digest size (if digest is known)
+        if 'sha-1' not in checksum:
+            # NOTE: required because of SCRAM spec.
+            raise ValueError("sha-1 must be in algorithm list of scram hash")
+        return checksum
+
+    def _norm_algs(self, algs):
+        "normalize algs parameter"
+        # determine default algs value
+        if algs is None:
+            # derive algs list from checksum (if present).
+            chk = self.checksum
+            if chk is not None:
+                return sorted(chk)
+            elif self.use_defaults:
+                return list(self.default_algs)
+            else:
+                raise TypeError("no algs list specified")
+        elif self.checksum is not None:
+            raise RuntimeError("checksum & algs kwds are mutually exclusive")
+
+        # parse args value
+        if isinstance(algs, str):
+            algs = splitcomma(algs)
+        algs = sorted(norm_hash_name(alg, 'iana') for alg in algs)
+        if any(len(alg)>9 for alg in algs):
+            raise ValueError("SCRAM limits alg names to max of 9 characters")
+        if 'sha-1' not in algs:
+            # NOTE: required because of SCRAM spec (rfc 5802)
+            raise ValueError("sha-1 must be in algorithm list of scram hash")
+        return algs
+
+    #===================================================================
+    # digest methods
+    #===================================================================
+
+    @classmethod
+    def _bind_needs_update(cls, **settings):
+        "generate a deprecation detector for CryptContext to use"
+        # generate deprecation hook which marks hashes as deprecated
+        # if they don't support a superset of current algs.
+        algs = frozenset(cls(use_defaults=True, **settings).algs)
+        def detector(hash, secret):
+            return not algs.issubset(cls.from_string(hash).algs)
+        return detector
+
+    def _calc_checksum(self, secret, alg=None):
+        rounds = self.rounds
+        salt = self.salt
+        hash = self.derive_digest
+        if alg:
+            # if requested, generate digest for specific alg
+            return hash(secret, salt, rounds, alg)
+        else:
+            # by default, return dict containing digests for all algs
+            return dict(
+                (alg, hash(secret, salt, rounds, alg))
+                for alg in self.algs
+            )
+
+    @classmethod
+    def verify(cls, secret, hash, full=False):
+        uh.validate_secret(secret)
+        self = cls.from_string(hash)
+        chkmap = self.checksum
+        if not chkmap:
+            raise ValueError("expected %s hash, got %s config string instead" %
+                             (cls.name, cls.name))
+
+        # NOTE: to make the verify method efficient, we just calculate hash
+        # of shortest digest by default. apps can pass in "full=True" to
+        # check entire hash for consistency.
+        if full:
+            correct = failed = False
+            for alg, digest in iteritems(chkmap):
+                other = self._calc_checksum(secret, alg)
+                # NOTE: could do this length check in norm_algs(),
+                # but don't need to be that strict, and want to be able
+                # to parse hashes containing algs not supported by platform.
+                # it's fine if we fail here though.
+                if len(digest) != len(other):
+                    raise ValueError("mis-sized %s digest in scram hash: %r != %r"
+                                     % (alg, len(digest), len(other)))
+                if consteq(other, digest):
+                    correct = True
+                else:
+                    failed = True
+            if correct and failed:
+                raise ValueError("scram hash verified inconsistently, "
+                                 "may be corrupted")
+            else:
+                return correct
+        else:
+            # XXX: should this just always use sha1 hash? would be faster.
+            # otherwise only verify against one hash, pick one w/ best security.
+            for alg in self._verify_algs:
+                if alg in chkmap:
+                    other = self._calc_checksum(secret, alg)
+                    return consteq(other, chkmap[alg])
+            # there should always be sha-1 at the very least,
+            # or something went wrong inside _norm_algs()
+            raise AssertionError("sha-1 digest not found!")
+
+    #===================================================================
+    #
+    #===================================================================
+
+#=============================================================================
+# code used for testing scram against protocol examples during development.
+#=============================================================================
+##def _test_reference_scram():
+##    "quick hack testing scram reference vectors"
+##    # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801
+##    from passlib.utils.compat import print_
+##
+##    engine = _scram_engine(
+##        alg="sha-1",
+##        salt='QSXCR+Q6sek8bf92'.decode("base64"),
+##        rounds=4096,
+##        password=u("pencil"),
+##    )
+##    print_(engine.digest.encode("base64").rstrip())
+##
+##    msg = engine.format_auth_msg(
+##        username="user",
+##        client_nonce = "fyko+d2lbbFgONRv9qkxdawL",
+##        server_nonce = "3rfcNHYJY1ZVvWVs7j",
+##        header='c=biws',
+##    )
+##
+##    cp = engine.get_encoded_client_proof(msg)
+##    assert cp == "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", cp
+##
+##    ss = engine.get_encoded_server_sig(msg)
+##    assert ss == "rmF9pqV8S7suAoZWja4dJRkFsKQ=", ss
+##
+##class _scram_engine(object):
+##    """helper class for verifying scram hash behavior
+##    against SCRAM protocol examples. not officially part of Passlib.
+##
+##    takes in alg, salt, rounds, and a digest or password.
+##
+##    can calculate the various keys & messages of the scram protocol.
+##
+##    """
+##    #=========================================================
+##    # init
+##    #=========================================================
+##
+##    @classmethod
+##    def from_string(cls, hash, alg):
+##        "create record from scram hash, for given alg"
+##        return cls(alg, *scram.extract_digest_info(hash, alg))
+##
+##    def __init__(self, alg, salt, rounds, digest=None, password=None):
+##        self.alg = norm_hash_name(alg)
+##        self.salt = salt
+##        self.rounds = rounds
+##        self.password = password
+##        if password:
+##            data = scram.derive_digest(password, salt, rounds, alg)
+##            if digest and data != digest:
+##                raise ValueError("password doesn't match digest")
+##            else:
+##                digest = data
+##        elif not digest:
+##            raise TypeError("must provide password or digest")
+##        self.digest = digest
+##
+##    #=========================================================
+##    # frontend methods
+##    #=========================================================
+##    def get_hash(self, data):
+##        "return hash of raw data"
+##        return hashlib.new(iana_to_hashlib(self.alg), data).digest()
+##
+##    def get_client_proof(self, msg):
+##        "return client proof of specified auth msg text"
+##        return xor_bytes(self.client_key, self.get_client_sig(msg))
+##
+##    def get_encoded_client_proof(self, msg):
+##        return self.get_client_proof(msg).encode("base64").rstrip()
+##
+##    def get_client_sig(self, msg):
+##        "return client signature of specified auth msg text"
+##        return self.get_hmac(self.stored_key, msg)
+##
+##    def get_server_sig(self, msg):
+##        "return server signature of specified auth msg text"
+##        return self.get_hmac(self.server_key, msg)
+##
+##    def get_encoded_server_sig(self, msg):
+##        return self.get_server_sig(msg).encode("base64").rstrip()
+##
+##    def format_server_response(self, client_nonce, server_nonce):
+##        return 'r={client_nonce}{server_nonce},s={salt},i={rounds}'.format(
+##            client_nonce=client_nonce,
+##            server_nonce=server_nonce,
+##            rounds=self.rounds,
+##            salt=self.encoded_salt,
+##            )
+##
+##    def format_auth_msg(self, username, client_nonce, server_nonce,
+##                        header='c=biws'):
+##        return (
+##            'n={username},r={client_nonce}'
+##                ','
+##            'r={client_nonce}{server_nonce},s={salt},i={rounds}'
+##                ','
+##            '{header},r={client_nonce}{server_nonce}'
+##            ).format(
+##                username=username,
+##                client_nonce=client_nonce,
+##                server_nonce=server_nonce,
+##                salt=self.encoded_salt,
+##                rounds=self.rounds,
+##                header=header,
+##                )
+##
+##    #=========================================================
+##    # helpers to calculate & cache constant data