summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2013-01-23 14:18:42 -0500
committerEli Collins <elic@assurancetechnologies.com>2013-01-23 14:18:42 -0500
commit22ed9b953d3a4f6bff0999fe7e344a1b1034ab3d (patch)
tree51a712529f55727826618026f5ee71df214fcb3d
parent951ac1222a827846d3e598fa39f1d1af94022ec4 (diff)
parent4c08f92f9caa64140e0010eae88179f260a28704 (diff)
downloadpasslib-22ed9b953d3a4f6bff0999fe7e344a1b1034ab3d.tar.gz
Merge from stable
-rw-r--r--CHANGES8
-rw-r--r--MANIFEST.in3
-rw-r--r--admin/benchmarks.py18
-rw-r--r--docs/_static/masthead.pngbin6296 -> 8103 bytes
-rw-r--r--docs/_static/masthead.svg142
-rw-r--r--docs/conf.py33
-rw-r--r--docs/index.rst24
-rw-r--r--docs/install.rst2
-rw-r--r--docs/lib/passlib.context-tutorial.rst10
-rw-r--r--docs/lib/passlib.context.rst61
-rw-r--r--docs/lib/passlib.hash.lmhash.rst61
-rw-r--r--docs/lib/passlib.hash.md5_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.rst4
-rw-r--r--docs/lib/passlib.registry.rst4
-rw-r--r--docs/lib/passlib.utils.handlers.rst9
-rw-r--r--docs/lib/passlib.utils.rst12
-rw-r--r--docs/modular_crypt_format.rst66
-rw-r--r--docs/password_hash_api.rst85
-rw-r--r--passlib/context.py82
-rw-r--r--passlib/exc.py11
-rw-r--r--passlib/handlers/bcrypt.py19
-rw-r--r--passlib/handlers/cisco.py14
-rw-r--r--passlib/handlers/md5_crypt.py14
-rw-r--r--passlib/utils/__init__.py24
-rw-r--r--setup.py9
25 files changed, 479 insertions, 238 deletions
diff --git a/CHANGES b/CHANGES
index 6fec062..7cd2b8c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -42,18 +42,18 @@ Todo
* *bugfix*: FreeBSD 8.3 added native support for :class:`~passlib.hash.sha256_crypt` --
updated Passlib's unittests and documentation accordingly (:issue:`35`).
- * *bugfix:* Fixed bug which caused passlib.apache unittest to fail
+ * *bugfix:* Fixed bug which caused some :mod:`!passlib.apache` unittests to fail
if mtime resolution >= 1 second (:issue:`35`).
- * Various bugfixes for Python 3.3 compatibility.
+ * *bugfix:* Fixed minor bug in :mod:`!passlib.registry`, should now work correctly under Python 3.3.
* Various documentation updates and corrections.
+.. _whats-new:
+
**1.6** (2012-05-01)
====================
-.. _whats-new:
-
Overview
--------
diff --git a/MANIFEST.in b/MANIFEST.in
index 902f79d..447028c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,2 @@
recursive-include docs *
-include LICENSE README CHANGES passlib/*.cfg passlib/tests/*.cfg tox.ini setup.cfg
-prune *.komodoproject
+include LICENSE README CHANGES passlib/tests/*.cfg tox.ini setup.cfg
diff --git a/admin/benchmarks.py b/admin/benchmarks.py
index 94f8610..dfe672a 100644
--- a/admin/benchmarks.py
+++ b/admin/benchmarks.py
@@ -6,7 +6,6 @@ parsing was being sped up. it could definitely be improved.
#=============================================================================
# init script env
#=============================================================================
-from binascii import hexlify
import re
import os, sys
root = os.path.join(os.path.dirname(__file__), os.path.pardir)
@@ -16,6 +15,7 @@ sys.path.insert(0, os.curdir)
# imports
#=============================================================================
# core
+from binascii import hexlify
import logging; log = logging.getLogger(__name__)
import os
import warnings
@@ -65,13 +65,9 @@ class benchmark:
kwds = defaults.copy()
kwds.update(options)
if mode == "ctor":
- itr = obj()
- if not hasattr(itr, next_method_attr):
- itr = [itr]
- for func in itr:
- # TODO: per function name & options
- secs, precision = cls.measure(func, None, **kwds)
- yield name, secs, precision
+ func = obj()
+ secs, precision = cls.measure(func, None, **kwds)
+ yield name, secs, precision
else:
raise ValueError("invalid mode: %r" % (mode,))
@@ -225,7 +221,7 @@ def test_md5_crypt_builtin():
hash = md5_crypt.encrypt(SECRET)
md5_crypt.verify(SECRET, hash)
md5_crypt.verify(OTHER, hash)
- yield helper
+ return helper
@benchmark.constructor()
def test_ldap_salted_md5():
@@ -235,7 +231,7 @@ def test_ldap_salted_md5():
hash = handler.encrypt(SECRET, salt='....')
handler.verify(SECRET, hash)
handler.verify(OTHER, hash)
- yield helper
+ return helper
@benchmark.constructor()
def test_phpass():
@@ -246,7 +242,7 @@ def test_phpass():
hash = handler.encrypt(SECRET, **kwds)
handler.verify(SECRET, hash)
handler.verify(OTHER, hash)
- yield helper
+ return helper
#=============================================================================
# crypto utils
diff --git a/docs/_static/masthead.png b/docs/_static/masthead.png
index 45e9015..677fcb1 100644
--- a/docs/_static/masthead.png
+++ b/docs/_static/masthead.png
Binary files differ
diff --git a/docs/_static/masthead.svg b/docs/_static/masthead.svg
index 5c0b2ed..e02eca8 100644
--- a/docs/_static/masthead.svg
+++ b/docs/_static/masthead.svg
@@ -14,10 +14,10 @@
height="52"
id="svg2383"
sodipodi:version="0.32"
- inkscape:version="0.48.0 r9654"
+ inkscape:version="0.48.3.1 r9886"
sodipodi:docname="masthead.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
- inkscape:export-filename="/home/biscuit/dev/libs/passlib/trunk/docs/_static/masthead.png"
+ inkscape:export-filename="/home/biscuit/dev/libs/passlib/stable/docs/_static/masthead.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
version="1.0"
@@ -25,6 +25,18 @@
<defs
id="defs2385">
<linearGradient
+ inkscape:collect="always"
+ id="linearGradient3910">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3912" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3914" />
+ </linearGradient>
+ <linearGradient
id="linearGradient4140">
<stop
style="stop-color:#ffe50a;stop-opacity:1;"
@@ -186,6 +198,28 @@
stdDeviation="5.9143996"
id="feGaussianBlur3885" />
</filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3910"
+ id="radialGradient3916"
+ cx="25.454468"
+ cy="44.810959"
+ fx="25.454468"
+ fy="44.810959"
+ r="21.542249"
+ gradientTransform="matrix(2.0471236,-0.05164693,0.02652595,1.0514061,-25.117708,-21.747692)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3910"
+ id="radialGradient4270"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1.9278492,0.04803928,-0.03632715,-1.4578329,160.96275,83.618729)"
+ cx="67.387276"
+ cy="44.127342"
+ fx="67.387276"
+ fy="44.127342"
+ r="21.542249" />
</defs>
<sodipodi:namedview
id="base"
@@ -194,17 +228,17 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
- inkscape:zoom="3.5"
- inkscape:cx="-15.023835"
- inkscape:cy="23.805612"
- inkscape:current-layer="layer8"
+ inkscape:zoom="2.4748737"
+ inkscape:cx="68.856099"
+ inkscape:cy="26.997913"
+ inkscape:current-layer="layer6"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
- inkscape:window-height="1003"
+ inkscape:window-height="1021"
inkscape:window-x="0"
- inkscape:window-y="24"
+ inkscape:window-y="0"
borderlayer="true"
inkscape:window-maximized="1" />
<metadata
@@ -234,6 +268,98 @@
sodipodi:ry="8.485281"
d="m 55.356359,38.302536 a 30.910667,8.485281 0 1 1 -61.8213344,0 30.910667,8.485281 0 1 1 61.8213344,0 z"
transform="matrix(0.43660868,0,0,0.53092582,10.777999,23.937337)" />
+ <text
+ xml:space="preserve"
+ style="font-size:6.01588392000000027px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;opacity:0.34213125000000000;fill:url(#radialGradient4270);fill-opacity:1;stroke:none;display:inline;font-family:Sans"
+ x="-10.452676"
+ y="-45.764187"
+ id="text3028"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-45.764187"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4268">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-38.244331"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4316">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-30.724478"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4318">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-23.204622"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4320">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-15.684768"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4322">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-8.1649122"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4324">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="-0.64505744"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4326">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="6.8747973"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4328">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="14.394652"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4330">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="21.914507"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4332">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="29.434362"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4334">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="36.954216"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4336">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="44.474072"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4338">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="51.993927"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4340">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="59.513783"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4342">0111010001101111011011110010000001101101011000010110111001111001</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="67.033638"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4344">0010000001110011011001010110001101110010011001010111010001110011</tspan><tspan
+ sodipodi:role="line"
+ x="-10.452676"
+ y="74.55349"
+ style="fill:url(#radialGradient4270);fill-opacity:1"
+ id="tspan4346" /></text>
</g>
<g
inkscape:groupmode="layer"
diff --git a/docs/conf.py b/docs/conf.py
index 27b26f4..61f9b5a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
-Sphinx configuration file for Passlib documentation.
+Sphinx configuration file for the Passlib documentation.
This file is execfile()d with the current directory set to its containing dir.
Note that not all possible configuration values are present in this
@@ -12,21 +12,20 @@ commented out serve to show the default.
#=============================================================================
import sys, os
-# make sure passlib in sys.path
-doc_root = os.path.abspath(os.path.dirname(__file__))
-source_root = os.path.dirname(doc_root)
-sys.path.insert(0, source_root)
-
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
##sys.path.insert(0, os.path.abspath('.'))
+# make sure root of source dir in sys.path
+sys.path.insert(0, os.path.abspath(os.pardir))
+
#=============================================================================
# imports
#=============================================================================
-# load build option flags
+# build option flags:
+# "for-pypi" -- enable analytics tracker for pypi documentation
options = os.environ.get("PASSLIB_DOCS", "").split(",")
# building the docs requires the Cloud Sphinx theme & extensions (>= v1.4),
@@ -66,6 +65,9 @@ extensions = [
# add "issue" role
'cloud_sptheme.ext.issue_tracker',
+
+ # allow table column alignment styling
+ 'cloud_sptheme.ext.table_styling',
]
# Add any paths that contain templates here, relative to this directory.
@@ -85,7 +87,8 @@ index_doc = 'index'
# General information about the project.
project = 'Passlib'
-copyright = '2008-2012, Assurance Technologies, LLC'
+author = "Assurance Technologies, LLC"
+copyright = "2008-2012, " + author
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -149,14 +152,14 @@ issue_tracker_url = "gc:passlib"
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'redcloud'
+html_theme = os.environ.get("SPHINX_THEME") or 'redcloud'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {}
if csp.is_cloud_theme(html_theme):
- html_theme_options.update(roottarget=index_doc)
+ html_theme_options.update(roottarget=index_doc, issueicon=None)
if 'for-pypi' in options:
html_theme_options.update(
googleanalytics_id = 'UA-22302196-2',
@@ -189,7 +192,7 @@ html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-html_last_updated_fmt = '%b %d, %Y'
+##html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
@@ -244,8 +247,8 @@ htmlhelp_basename = project + 'Doc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- (index_doc, project + '.tex', project + ' Documentation',
- 'Assurance Technologies, LLC', 'manual'),
+ (master_doc, project + '.tex', project + ' Documentation',
+ author, 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -278,8 +281,8 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (index_doc, project, project + ' Documentation',
- ['Assurance Technologies, LLC'], 1)
+ (master_doc, project, project + ' Documentation',
+ [author], 1)
]
#=============================================================================
diff --git a/docs/index.rst b/docs/index.rst
index 1d8641b..270fcba 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -107,13 +107,17 @@ Other Documents
Online Resources
================
- ================ ===================================================
- Homepage: `<http://passlib.googlecode.com>`_
- Online Docs: `<http://packages.python.org/passlib>`_
- Discussion: `<http://groups.google.com/group/passlib-users>`_
- ---------------- ---------------------------------------------------
- ---------------- ---------------------------------------------------
- PyPI: `<http://pypi.python.org/pypi/passlib>`_
- Downloads: `<http://code.google.com/p/passlib/downloads>`_
- Source: `<http://code.google.com/p/passlib/source>`_
- ================ ===================================================
+ .. table::
+ :class: fullwidth
+ :column-alignment: lr
+
+ ================ ===================================================
+ Homepage: `<http://passlib.googlecode.com>`_
+ Online Docs: `<http://packages.python.org/passlib>`_
+ Discussion: `<http://groups.google.com/group/passlib-users>`_
+ ---------------- ---------------------------------------------------
+ ---------------- ---------------------------------------------------
+ PyPI: `<http://pypi.python.org/pypi/passlib>`_
+ Downloads: `<http://code.google.com/p/passlib/downloads>`_
+ Source: `<http://code.google.com/p/passlib/source>`_
+ ================ ===================================================
diff --git a/docs/install.rst b/docs/install.rst
index c4b728c..c9ec13e 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -87,7 +87,7 @@ If you wish to generate your own copy of the documentation,
you will need to:
1. Install `Sphinx <http://sphinx.pocoo.org/>`_ (1.1 or newer)
-2. Install the `Cloud Sphinx Theme <http://packages.python.org/cloud_sptheme>`_ (1.4 or newer).
+2. Install the `Cloud Sphinx Theme <http://packages.python.org/cloud_sptheme>`_ (1.5 or newer).
3. Download the Passlib source
4. From the Passlib source directory, run :samp:`python setup.py build_sphinx`.
5. Once Sphinx completes it's run, point a web browser to the file at :samp:`{SOURCE}/build/sphinx/html/index.html`
diff --git a/docs/lib/passlib.context-tutorial.rst b/docs/lib/passlib.context-tutorial.rst
index e6d3b1d..e3c3ee9 100644
--- a/docs/lib/passlib.context-tutorial.rst
+++ b/docs/lib/passlib.context-tutorial.rst
@@ -31,6 +31,8 @@ with some simple examples, and working up to a complex "full-integration" exampl
.. index:: CryptContext; usage examples
+.. rst-class:: emphasize-children
+
Tutorial / Walkthrough
======================
* `Basic Usage`_
@@ -46,8 +48,6 @@ Tutorial / Walkthrough
.. _context-basic-example:
-.. rst-class:: emphasized
-
Basic Usage
-----------
At it's base, the :class:`!CryptContext` class is just a list of
@@ -111,8 +111,6 @@ which probably provide a better argument for *why* you'd want to use it.
.. _context-default-settings-example:
-.. rst-class:: emphasized
-
Using Default Settings
----------------------
While encrypting and verifying hashes is useful enough, it's not much
@@ -166,8 +164,6 @@ These is done by passing the CryptContext constructor a keyword with the format
.. _context-serialization-example:
-.. rst-class:: emphasized
-
Loading & Saving a CryptContext
-------------------------------
The previous example built up a :class:`!CryptContext` instance
@@ -234,8 +230,6 @@ policies from the code, and into a configuration file with other security settin
.. _context-migration-example:
-.. rst-class:: emphasized
-
Deprecation & Hash Migration
----------------------------
The final and possibly most useful feature of the :class:`CryptContext` class
diff --git a/docs/lib/passlib.context.rst b/docs/lib/passlib.context.rst
index fad4b1f..96a6b69 100644
--- a/docs/lib/passlib.context.rst
+++ b/docs/lib/passlib.context.rst
@@ -16,6 +16,8 @@ and helper utilities.
* :ref:`CryptContext Overview & Tutorial <context-overview>` --
overview of this class and walkthrough of how to use it.
+.. rst-class:: emphasize-children
+
The CryptContext Class
======================
.. class:: CryptContext(schemes=None, \*\*kwds)
@@ -39,10 +41,11 @@ The CryptContext Class
* `Changing the Configuration`_ -- altering the configuration of an existing context.
* `Examining the Configuration`_ -- programmatically examining the context's settings.
* `Saving the Configuration`_ -- exporting the context's current configuration.
+ * `Configuration Errors`_ -- overview of errors that may be thrown by :class:`!CryptContext` constructor
.. index:: CryptContext; keyword options
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Constructor Keywords
--------------------
@@ -145,6 +148,9 @@ Options which directly affect the behavior of the CryptContext instance:
``["auto"]``, which will configure the CryptContext instance
to deprecate *all* supported schemes except for the default scheme.
+ .. versionadded:: 1.6
+ Added support for the ``["auto"]`` value.
+
.. seealso:: :ref:`context-migration-example` in the tutorial
.. _context-min-verify-time-option:
@@ -334,7 +340,7 @@ For example, a CryptContext could be set up as follows::
'$5$rounds=88000$w7XIdKfTI9.YLwmA$MIzGvs6NU1QOQuuDHhICLmDsdW/t94Bbdfxdh/6NJl7'
^^^^^
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Primary Methods
---------------
@@ -358,7 +364,7 @@ style methods provided by all the :class:`~passlib.ifc.PasswordHash` objects:
.. automethod:: CryptContext.genhash
.. automethod:: CryptContext.genconfig
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Hash Migration
--------------
@@ -370,7 +376,7 @@ hashes will want to use one of the following methods:
.. automethod:: CryptContext.needs_update
.. automethod:: CryptContext.hash_needs_update
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Alternate Constructors
----------------------
@@ -381,7 +387,7 @@ as a set of keywords, there are the following alternate constructors:
.. automethod:: CryptContext.from_path
.. automethod:: CryptContext.copy
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Changing the Configuration
--------------------------
@@ -393,7 +399,7 @@ This is done through three methods:
.. automethod:: CryptContext.load
.. automethod:: CryptContext.load_path
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Examining the Configuration
---------------------------
@@ -404,7 +410,7 @@ current configuration:
.. automethod:: CryptContext.default_scheme
.. automethod:: CryptContext.handler
-.. rst-class:: html-toggle expanded emphasized
+.. rst-class:: html-toggle expanded
Saving the Configuration
------------------------
@@ -414,6 +420,47 @@ using one of the serialization methods:
.. automethod:: CryptContext.to_dict
.. automethod:: CryptContext.to_string
+Configuration Errors
+--------------------
+The following errors may be raised when creating a :class:`!CryptContext` instance
+via any of it's constructors, or when updating the configuration of an existing
+instance:
+
+:raises ValueError:
+
+ * If a configuration option contains an invalid value
+ (e.g. ``all__vary_rounds=-1``).
+
+ * If the configuration contains valid but incompatible options
+ (e.g. listing a scheme as both :ref:`default <context-default-option>`
+ and :ref:`deprecated <context-deprecated-option>`).
+
+:raises KeyError:
+
+ * If the configuration contains an unknown or forbidden option
+ (e.g. :samp:`{scheme}__salt`).
+
+ * If the :ref:`schemes <context-schemes-option>`,
+ :ref:`default <context-default-option>`, or
+ :ref:`deprecated <context-deprecated-option>` options reference an unknown
+ hash scheme (e.g. ``schemes=['xxx']``)
+
+:raises TypeError:
+
+ * If a configuration value has the wrong type (e.g. ``schemes=123``).
+
+ Note that this error shouldn't occur when loading configurations
+ from a file/string (e.g. using :meth:`CryptContext.from_string`).
+
+Additionally, a :exc:`~passlib.exc.PasslibConfigWarning` may be issued
+if any invalid-but-correctable values are encountered
+(e.g. if :samp:`sha256_crypt__min_rounds` is set to less than
+:class:`~passlib.hash.sha256_crypt` 's minimum of 1000).
+
+.. versionchanged:: 1.6
+ Previous releases issued a generic :exc:`UserWarning` instead
+ of the more specific :exc:`PasslibConfigWarning`.
+
Other Helpers
=============
.. autoclass:: LazyCryptContext([schemes=None,] \*\*kwds [, onload=None])
diff --git a/docs/lib/passlib.hash.lmhash.rst b/docs/lib/passlib.hash.lmhash.rst
index 04012b9..efc0613 100644
--- a/docs/lib/passlib.hash.lmhash.rst
+++ b/docs/lib/passlib.hash.lmhash.rst
@@ -63,30 +63,31 @@ which encode the 16 byte digest. An example hash (of ``password``) is
The digest is calculated as follows:
-1. First the password should be converted to uppercase, and encoded
- to bytes using the "OEM Codepage" used [#cp]_ by the specific release of
- Windows that the host or target server is running.
+1. First, the password should be converted to uppercase, and encoded
+ using the "OEM Codepage" of the Windows release that the host / target
+ server is running [#cp]_.
- For pure-ASCII passwords, this step can be performed as normal
- using the ``us-ascii`` encoding. For passwords with non-ASCII
- characters, this step is fraught with compatibility issues
- and border cases (see `Deviations`_ for details).
+ For pure-ASCII passwords, this step can be performed
+ using the ``us-ascii`` encoding (as most OEM Codepages are ASCII-compatible).
+ However, for passwords with non-ASCII characters, this step is fraught
+ with compatibility issues and border cases (see `Deviations`_ for details).
-2. The password is then truncated or NULL padded to 14 bytes, as appropriate.
+2. The password is then truncated to 14 bytes,
+ or the end NULL padded to 14 bytes; as appropriate.
-3. The first 7 bytes of the password in step 2 are used as a key,
+3. The first 7 bytes of the truncated password from step 2 are used as a key
to DES encrypt the constant ``KGS!@#$%``, resulting
in the first 8 bytes of the final digest.
-4. Step 4 is repeated using the second 7 bytes of the password from step 2,
+4. Step 3 is repeated using the second 7 bytes of the password from step 2,
resulting in the second 8 bytes of the final digest.
5. The combined digests from 3 and 4 are then encoded to hexidecimal.
Security Issues
===============
-Due to this myriad of flaws, high-speed password cracking software
-dedicated to LMHASH exists, and the algorithm should be considered broken:
+Due to a myriad of flaws, and the existence high-speed password cracking software
+dedicated to LMHASH, this algorithm should be considered broken. The major flaws include:
* It has no salt, making hashes easily pre-computable.
@@ -106,8 +107,7 @@ dedicated to LMHASH exists, and the algorithm should be considered broken:
Deviations
==========
Passlib's implementation differs from others in a few ways, all related to
-the handling of non-ASCII characters. Future releases of Passlib may update
-the implementation as new information comes up.
+the handling of non-ASCII characters.
* Unicode Policy:
@@ -118,10 +118,11 @@ the implementation as new information comes up.
of XP), and ``cp866`` (used by many Eastern European editions of XP).
Complicating matters further, some third-party implementations are known
to use encodings such as ``latin-1`` and ``utf-8``, which cause
- the non-ASCII characters to have different hashes entirely.
+ non-ASCII characters to hash in a manner incompatible with the canonical
+ MS Windows implementation.
- Thus the application must decide which encoding to use, if it wants
- to provide support for non-ASCII passwords. Passlib uses ``cp437`` as a
+ Thus if an application wishes to provide support for non-ASCII passwords,
+ it must decide which encoding to use. Passlib uses ``cp437`` as a
default, but this may need to be overridden via
``lmhash.encrypt(secret, encoding="some-other-codec")``.
All known encodings are ``us-ascii``-compatible, so for ASCII passwords,
@@ -129,28 +130,32 @@ the implementation as new information comes up.
* Upper Case Conversion:
+ .. note::
+
+ Future releases of Passlib may change this behavior
+ as new information and code is integrated.
+
Once critical step in the LMHASH algorithm is converting the password
- to upper case. While ASCII characters are converted to uppercase as normal,
- non-ASCII characters are converted in implementation dependant ways:
+ to upper case. While ASCII characters are uppercased as normal,
+ non-ASCII characters are converted in implementation-dependant ways:
Windows systems encode the password first, and then
- convert it to uppercase using a codepage-dependant table.
- For the most part these tables appear to agree with the Unicode specification,
+ convert it to uppercase using an codepage-specific table.
+ For the most part these tables seem to agree with the Unicode specification,
but there are some codepoints where they deviate (for example,
- Unicode uppercases U+00B5 -> U+039C, but ``cp437`` leaves it unchanged
- [#uc]_).
+ Unicode uppercases U+00B5 -> U+039C, but ``cp437`` leaves it unchanged [#uc]_).
- Most third-party implementations (Passlib included) choose to uppercase
- non-ASCII characters according to the Unicode specification, and then
- encode the password; despite the border cases where the hash would not match
- the official windows hash.
+ In contrast, most third-party implementations (Passlib included)
+ perform the uppercase conversion first using the Unicode specification,
+ and then encode the password second; despite the non-ASCII border cases where the
+ resulting hash would not match the official Windows hash.
.. rubric:: Footnotes
.. [#] Article used as reference for algorithm -
`<http://www.linuxjournal.com/article/2717>`_.
-.. [#cp] The OEM codepage used by specific Window XP (and earlier releases)
+.. [#cp] The OEM codepage used by specific Window XP (and earlier) releases
can be found at `<http://msdn.microsoft.com/nl-nl/goglobal/cc563921%28en-us%29.aspx>`_.
.. [#uc] Online discussion dealing with upper-case encoding issues -
diff --git a/docs/lib/passlib.hash.md5_crypt.rst b/docs/lib/passlib.hash.md5_crypt.rst
index e10afcc..e730d20 100644
--- a/docs/lib/passlib.hash.md5_crypt.rst
+++ b/docs/lib/passlib.hash.md5_crypt.rst
@@ -161,8 +161,6 @@ Security Issues
MD5-Crypt has a couple of issues which have weakened severely:
* It relies on the MD5 message digest, for which theoretical pre-image attacks exist [#f2]_.
- However, not only is this attack still only theoretical, but none of MD5's weaknesses
- have been show to affect MD5-Crypt's security.
* More seriously, it's fixed number of rounds (combined with the availability
of high-throughput MD5 implementations) means this algorithm
diff --git a/docs/lib/passlib.hash.rst b/docs/lib/passlib.hash.rst
index 14cb7e9..906f301 100644
--- a/docs/lib/passlib.hash.rst
+++ b/docs/lib/passlib.hash.rst
@@ -10,8 +10,8 @@ Overview
The :mod:`!passlib.hash` module contains all the password hash algorithms built into Passlib.
While each hash has it's own options and output format, they all share a common interface,
documented in detail in the :ref:`password-hash-api`. The following pages
-first describe the common interface, and then each hash in detail (
-it's including format, algorithm, and known security issues).
+describe the common interface, and then describe each hash in detail
+(including it's format, underlying algorithm, and known security issues).
.. seealso:: :doc:`Quickstart Guide </new_app_quickstart>` -- advice on
choosing an appropriately secure hash for your new application.
diff --git a/docs/lib/passlib.registry.rst b/docs/lib/passlib.registry.rst
index 6db21c4..9aadca9 100644
--- a/docs/lib/passlib.registry.rst
+++ b/docs/lib/passlib.registry.rst
@@ -24,10 +24,10 @@ querying Passlib to detect what algorithms are available.
Interface
=========
-.. autofunction:: get_crypt_handler
+.. autofunction:: get_crypt_handler(name[, default])
.. autofunction:: list_crypt_handlers
.. autofunction:: register_crypt_handler_path
-.. autofunction:: register_crypt_handler
+.. autofunction:: register_crypt_handler(handler, force=False)
.. note::
diff --git a/docs/lib/passlib.utils.handlers.rst b/docs/lib/passlib.utils.handlers.rst
index 6b4799a..be948af 100644
--- a/docs/lib/passlib.utils.handlers.rst
+++ b/docs/lib/passlib.utils.handlers.rst
@@ -31,15 +31,16 @@ should look like (if the implementation even uses them).
That said, most of the handlers built into Passlib are based around the :class:`GenericHandler`
class, and it's associated mixin classes. While deriving from this class is not required,
-doing so will greatly reduce the amount of addition code that is needed for
+doing so will greatly reduce the amount of additional code that is needed for
all but the most convoluted password hash schemes.
Once a handler has been written, it may be used explicitly, passed into
-an application's custom :class:`CryptContext` directly, or registered
+a :class:`CryptContext` constructor, or registered
globally with Passlib via the :mod:`passlib.registry` module.
-See :ref:`testing-hash-handlers` for details about how to test
-custom handlers against Passlib's unittest suite.
+.. seealso::
+ :ref:`testing-hash-handlers` for details about how to test
+ custom handlers against Passlib's unittest suite.
The GenericHandler Class
========================
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst
index c3f2739..6427111 100644
--- a/docs/lib/passlib.utils.rst
+++ b/docs/lib/passlib.utils.rst
@@ -5,10 +5,6 @@
.. module:: passlib.utils
:synopsis: internal helpers for implementing password hashes
-This module contains a number of utility functions used by Passlib
-to implement the builtin hashes and other internals.
-They may also be useful when implementing custom handlers for existing legacy formats.
-
.. warning::
This module is primarily used as an internal support module.
@@ -16,6 +12,14 @@ They may also be useful when implementing custom handlers for existing legacy fo
between major releases of Passlib, as the internal code is cleaned up
and simplified.
+This module primarily contains utility functions used interally by Passlib.
+However, end-user applications may find some of the functions useful,
+in particular:
+
+ * :func:`consteq`
+ * :func:`saslprep`
+ * :func:`generate_password`
+
Constants
=========
diff --git a/docs/modular_crypt_format.rst b/docs/modular_crypt_format.rst
index a5566a5..55e9aa3 100644
--- a/docs/modular_crypt_format.rst
+++ b/docs/modular_crypt_format.rst
@@ -129,32 +129,37 @@ OS Defined Hashes
The following table lists of all the major MCF hashes supported by Passlib,
and indicates which operating systems offer native support:
-==================================== ==================== =========== =========== =========== =========== =======
-Scheme Prefix Linux FreeBSD NetBSD OpenBSD Solaris
-==================================== ==================== =========== =========== =========== =========== =======
-:class:`~passlib.hash.des_crypt` y y y y y
-:class:`~passlib.hash.bsdi_crypt` ``_`` y y y
-:class:`~passlib.hash.md5_crypt` ``$1$`` y y y y y
-:class:`~passlib.hash.bcrypt` ``$2$``, ``$2a$``,
- ``$2x$``, ``$2y$`` y y y y
-:class:`~passlib.hash.bsd_nthash` ``$3$`` y
-:class:`~passlib.hash.sha256_crypt` ``$5$`` y 8.3+ y
-:class:`~passlib.hash.sha512_crypt` ``$6$`` y 8.3+ y
-:class:`~passlib.hash.sun_md5_crypt` ``$md5$``, ``$md5,`` y
-:class:`~passlib.hash.sha1_crypt` ``$sha1$`` y
-==================================== ==================== =========== =========== =========== =========== =======
+.. table::
+ :column-alignment: llccccc
+ :column-wrapping: nn
+
+ ==================================== ==================== =========== =========== =========== =========== =======
+ Scheme Prefix Linux FreeBSD NetBSD OpenBSD Solaris
+ ==================================== ==================== =========== =========== =========== =========== =======
+ :class:`~passlib.hash.des_crypt` y y y y y
+ :class:`~passlib.hash.bsdi_crypt` ``_`` y y y
+ :class:`~passlib.hash.md5_crypt` ``$1$`` y y y y y
+ :class:`~passlib.hash.bcrypt` ``$2$``, ``$2a$``,
+ ``$2x$``, ``$2y$`` y y y y
+ :class:`~passlib.hash.bsd_nthash` ``$3$`` y
+ :class:`~passlib.hash.sha256_crypt` ``$5$`` y 8.3+ y
+ :class:`~passlib.hash.sha512_crypt` ``$6$`` y 8.3+ y
+ :class:`~passlib.hash.sun_md5_crypt` ``$md5$``, ``$md5,`` y
+ :class:`~passlib.hash.sha1_crypt` ``$sha1$`` y
+ ==================================== ==================== =========== =========== =========== =========== =======
Additional Platforms
--------------------
The modular crypt format is also supported to some degree
by the following operating systems and platforms:
-.. rst-class:: html-plain-table
+.. rst-class:: plain
===================== ==============================================================
**MacOS X** Darwin's native :func:`!crypt` provides limited functionality,
supporting only :class:`~passlib.hash.des_crypt` and
- :class:`~passlib.hash.bsdi_crypt`.
+ :class:`~passlib.hash.bsdi_crypt`. OS X uses a separate
+ system for it's own password hashes.
**Google App Engine** As of 2011-08-19, Google App Engine's :func:`!crypt`
implementation appears to match that of a typical Linux
@@ -167,18 +172,23 @@ The following table lists the other MCF hashes supported by Passlib.
These hashes can be found in various libraries and applications
(and are not natively supported by any known OS):
-=========================================== =================== ===========================
-Scheme Prefix Primary Use (if known)
-=========================================== =================== ===========================
-:class:`~passlib.hash.apr_md5_crypt` ``$apr1$`` Apache htdigest files
-:class:`~passlib.hash.phpass` ``$P$``, ``$H$`` PHPass-based applications
-:class:`~passlib.hash.pbkdf2_sha1` ``$pbkdf2$`` Passlib-specific
-:class:`~passlib.hash.pbkdf2_sha256` ``$pbkdf2-sha256$`` Passlib-specific
-:class:`~passlib.hash.pbkdf2_sha512` ``$pbkdf2-sha512$`` Passlib-specific
-:class:`~passlib.hash.scram` ``$scram$`` Passlib-specific
-:class:`~passlib.hash.cta_pbkdf2_sha1` ``$p5k2$`` [#cta]_
-:class:`~passlib.hash.dlitz_pbkdf2_sha1` ``$p5k2$`` [#cta]_
-=========================================== =================== ===========================
+.. table::
+ :class: fullwidth
+ :widths: 1 1 2
+ :column-wrapping: nn
+
+ =========================================== =================== ===========================
+ Scheme Prefix Primary Use (if known)
+ =========================================== =================== ===========================
+ :class:`~passlib.hash.apr_md5_crypt` ``$apr1$`` Apache htdigest files
+ :class:`~passlib.hash.phpass` ``$P$``, ``$H$`` PHPass-based applications
+ :class:`~passlib.hash.pbkdf2_sha1` ``$pbkdf2$`` Passlib-specific
+ :class:`~passlib.hash.pbkdf2_sha256` ``$pbkdf2-sha256$`` Passlib-specific
+ :class:`~passlib.hash.pbkdf2_sha512` ``$pbkdf2-sha512$`` Passlib-specific
+ :class:`~passlib.hash.scram` ``$scram$`` Passlib-specific
+ :class:`~passlib.hash.cta_pbkdf2_sha1` ``$p5k2$`` [#cta]_
+ :class:`~passlib.hash.dlitz_pbkdf2_sha1` ``$p5k2$`` [#cta]_
+ =========================================== =================== ===========================
.. rubric:: Footnotes
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index 19a2b8d..d2ec5d5 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -150,9 +150,9 @@ and hash comparison.
Digest password using format-specific algorithm,
returning resulting hash string.
- For most hashes supported by Passlib, this string will include
- an algorithm identifier, a copy of the salt (if applicable),
- and any other configuration information required to verify the password later.
+ For most hashes supported by Passlib, the returned string will contain:
+ an algorithm identifier, a cost parameter, the salt string,
+ and finally the password digest itself.
:type secret: unicode or bytes
:arg secret: string containing the password to encode.
@@ -166,28 +166,27 @@ and hash comparison.
Examples of common keywords include ``rounds`` and ``salt_size``.
:returns:
- Resulting hash of password, using an algorithm-specific format.
-
+ Resulting password hash, encoded in an algorithm-specific format.
This will always be an instance of :class:`!str`
(i.e. :class:`unicode` under Python 3, ``ascii``-encoded :class:`bytes` under Python 2).
:raises ValueError:
- * If a keyword's value is invalid (e.g. if a ``salt`` string
+ * If a ``kwd``'s value is invalid (e.g. if a ``salt`` string
is too small, or a ``rounds`` value is out of range).
- * If the ``secret`` contains characters forbidden by the handler
+ * If ``secret`` contains characters forbidden by the hash algorithm
(e.g. :class:`!des_crypt` forbids NULL characters).
:raises TypeError:
- * if ``secret`` is not unicode or bytes.
- * if a keyword argument had an incorrect type.
- * if a required keyword was not provided.
+ * if ``secret`` is not :class:`!unicode` or :class:`bytes`.
+ * if a ``kwd`` argument has an incorrect type.
+ * if an algorithm-specific required ``kwd`` is not provided.
- *(Note that the name of this method is a misnomer, password hashes
- are typically based on irreversible cryptographic operations,
- see* :issue:`21` *).*
+ *(Note that the name of this method is a misnomer: nearly all
+ password hashes use an irreversible cryptographic digest,
+ rather than a reversible cipher. see* :issue:`21` *).*
.. versionchanged:: 1.6
Hashes now raise :exc:`TypeError` if a required keyword is missing,
@@ -237,23 +236,23 @@ and hash comparison.
``True`` if the secret matches, otherwise ``False``.
:raises TypeError:
- * if either *secret* or *hash* is not a unicode or bytes instance.
- * if the hash requires additional keywords which are not provided,
- or have the wrong type.
+ * if either ``secret`` or ``hash`` is not a unicode or bytes instance.
+ * if the hash requires additional ``kwds`` which are not provided,
+ * if a ``kwd`` argument has the wrong type.
:raises ValueError:
- * if *hash* does not match this algorithm's format.
- * if the secret contains forbidden characters (see
+ * if ``hash`` does not match this algorithm's format.
+ * if the ``secret`` contains forbidden characters (see
:meth:`~PasswordHash.encrypt`).
* if a configuration/salt string generated by :meth:`~PasswordHash.genconfig`
- is passed in as the value for *hash* (these strings look
+ is passed in as the value for ``hash`` (these strings look
similar to a full hash, but typically lack the digest portion
needed to verify a password).
.. versionchanged:: 1.6
- This function now raises :exc:`ValueError` if a ``None`` or config string is provided
- instead of a proper hash; previous releases were inconsistent
- in their handling of these cases.
+ This function now raises :exc:`ValueError` if ``None`` or a config string is provided
+ instead of a properly-formed hash; previous releases were inconsistent
+ in their handling of these two border cases.
.. _hash-unicode-behavior:
@@ -398,14 +397,16 @@ There is currently one additional support method, :meth:`~PasswordHash.identify`
.. note::
- Hashes which lack a reliable method of identification may incorrectly
- identify each-other's hashes (e.g. both :class:`~passlib.hash.lmhash`
- and :class:`~passlib.hash.nthash` hash consist 32 hexidecimal characters).
+ A small number of the hashes supported by Passlib lack a reliable
+ method of identification (e.g. :class:`~passlib.hash.lmhash`
+ and :class:`~passlib.hash.nthash` both consist of 32 hexidecimal characters,
+ with no distinguishing features). For such hashes, this method
+ may return false positives.
.. seealso::
If you are considering using this method to select from multiple
- algorithms in order to verify a password, you may be better served
+ algorithms (e.g. in order to verify a password), you will be better served
by the :ref:`CryptContext <context-overview>` class.
..
@@ -518,17 +519,19 @@ the hashes in passlib:
.. _relaxed-keyword:
``relaxed``
- By default, passing :meth:`~PasswordHash.encrypt` an invalid
- value will result in a :exc:`ValueError`. However, if ``relaxed=True``,
- Passlib will attempt to correct the error, and if successful,
+ By default, passing an invalid value to :meth:`~PasswordHash.encrypt`
+ will result in a :exc:`ValueError`. However, if ``relaxed=True``
+ then Passlib will attempt to correct the error and (if successful)
issue a :exc:`~passlib.exc.PasslibHashWarning` instead.
This warning may then be filtered if desired.
- Correctable errors include (but aren not limited to): ``rounds``
+ Correctable errors include (but are not limited to): ``rounds``
and ``salt_size`` values that are too low or too high, ``salt``
- strings that are too large, etc.
+ strings that are too large.
This option is supported by most of the hashes in Passlib.
+ .. versionadded:: 1.6
+
.. attribute:: PasswordHash.context_kwds
Tuple listing the keywords supported by :meth:`~PasswordHash.encrypt`,
@@ -585,7 +588,7 @@ and the following attributes should be defined:
.. attribute:: PasswordHash.min_salt_size
The minimum number of bytes/characters required for the salt.
- Must be an integer between 0 or :attr:`~PasswordHash.max_salt_size`.
+ Must be an integer between 0 and :attr:`~PasswordHash.max_salt_size`.
.. attribute:: PasswordHash.default_salt_size
@@ -619,8 +622,8 @@ and the following attributes should be defined:
Rounds Information
------------------
-For schemes which support a variable number of iterations to adjust their time-cost,
-``"rounds"`` should be listed in :attr:`~PasswordHash.setting_kwds`,
+For schemes which support a variable time-cost parameter,
+``"rounds"`` should be listed in their :attr:`~PasswordHash.setting_kwds`,
and the following attributes should be defined:
.. attribute:: PasswordHash.max_rounds
@@ -717,23 +720,25 @@ and the following attributes should be defined:
Choosing the right rounds value
===============================
-Passlib's default rounds settings attempt to be secure enough for
+For hash algorithms which support a variable time-cost,
+Passlib's default ``rounds`` choices attempt to be secure enough for
the average [#avgsys]_ system. But the "right" value for a given hash
is dependant on the server, it's cpu, it's expected load, and it's users.
Since larger values mean increased work for an attacker,
-**the right** ``rounds`` **value for a given server should be the largest
-possible value that doesn't cause intolerable delay for your users**.
+*the right* ``rounds`` *value for a given hash & server should be the largest
+possible value that doesn't cause intolerable delay for your users*.
+
For most public facing services, you can generally have signin
take upwards of 250ms - 400ms before users start getting annoyed.
For superuser accounts, it should take as much time as the admin can stand
(usually ~4x more delay than a regular account).
-Passlib's ``default_rounds`` values are retuned every major release (at a minimum)
+Passlib's ``default_rounds`` values are retuned periodically
by taking a rough estimate of what an "average" system is capable of,
-and setting all the ``default_rounds`` values to take ~300ms on such a system.
+and then setting all :samp:`{hash}.default_rounds` values to take ~300ms on such a system.
However, some older algorithms (e.g. :class:`~passlib.hash.bsdi_crypt`) are weak enough that
a tradeoff must be made, choosing "secure but intolerably slow" over "fast but unacceptably insecure".
-For this reason, it is strongly recommended to not use a value lower than Passlib's default.
+For this reason, it is strongly recommended to not use a value much lower than Passlib's default.
.. [#avgsys] For Passlib 1.6, all hashes were retuned to take ~250ms on a
system with a 3 ghz 64 bit CPU.
diff --git a/passlib/context.py b/passlib/context.py
index df338e2..4f7ec13 100644
--- a/passlib/context.py
+++ b/passlib/context.py
@@ -1653,7 +1653,7 @@ class CryptContext(object):
>>> ctx2.default_scheme()
"md5_crypt"
- .. versionchanged:: 1.6
+ .. versionadded:: 1.6
This method was previously named :meth:`!replace`. That alias
has been deprecated, and will be removed in Passlib 1.8.
@@ -1767,7 +1767,7 @@ class CryptContext(object):
def load_path(self, path, update=False, section="passlib", encoding="utf-8"):
"""Load new configuration into CryptContext from a local file.
- This function is a wrapper for :meth:`load`, which
+ This function is a wrapper for :meth:`load` which
loads a configuration string from the local file *path*,
instead of an in-memory source. It's behavior and options
are otherwise identical to :meth:`!load` when provided with
@@ -1842,7 +1842,7 @@ class CryptContext(object):
.. note::
- If an error occurs during a :meth:`!load` call, the :class`!CryptContext`
+ If an error occurs during a :meth:`!load` call, the :class:`!CryptContext`
instance will be restored to the configuration it was in before
the :meth:`!load` call was made; this is to ensure it is
*never* left in an inconsistent state due to a load error.
@@ -2293,25 +2293,37 @@ class CryptContext(object):
:type secret: unicode, bytes, or None
:param secret:
- Optionally, the secret associated with the hash.
- This is not required, or in fact useful for any current purpose,
- and can be safely omitted. It's mainly present to allow the
- development of future deprecation checks which might need this information.
+ Optional secret associated with the provided ``hash``.
+ This is not required, or even currently used for anything...
+ it's for forward-compatibility with any future
+ update checks that might need this information.
+ If provided, Passlib assumes the secret has already been
+ verified successfully against the hash.
+
+ .. versionadded:: 1.6
:returns: ``True`` if hash should be replaced, otherwise ``False``.
- .. versionchanged:: 1.6
- The *secret* argument was added, and this method was renamed
- from the longer alias ``hash_needs_update``.
+ :raises ValueError:
+ If the hash did not match any of the configured :meth:`schemes`.
+
+ .. versionadded:: 1.6
+ This method was previously named :meth:`hash_needs_update`.
.. seealso:: the :ref:`context-migration-example` example in the tutorial.
"""
record = self._get_or_identify_record(hash, scheme, category)
return record.needs_update(hash, secret)
- @deprecated_method(deprecated="1.6", removed="1.8", replacement="CryptContext.needs_update()")
+ @deprecated_method(deprecated="1.6", removed="2.0", replacement="CryptContext.needs_update()")
def hash_needs_update(self, hash, scheme=None, category=None):
- """legacy alias for :meth:`needs_update`"""
+ """Legacy alias for :meth:`needs_update`.
+
+ .. deprecated:: 1.6
+ This method was renamed to :meth:`!needs_update` in version 1.6.
+ This alias will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.5.
+ """
return self.needs_update(hash, scheme, category)
def genconfig(self, scheme=None, category=None, **settings):
@@ -2466,16 +2478,16 @@ class CryptContext(object):
:param \*\*kwds:
All other keyword options are passed to the selected algorithm's
- :meth:`~passlib.ifc.PasswordHash.encrypt` method.
+ :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
:returns:
The secret as encoded by the specified algorithm and options.
The return value will always be a :class:`!str`.
:raises TypeError, ValueError:
- * if any of the arguments have an invalid type or value.
- * if the selected algorithm's underlying :meth:`~passlib.ifc.PasswordHash.encrypt`
- method throws an error based on *secret* or the provided *kwds*.
+ * If any of the arguments have an invalid type or value.
+ This includes any keywords passed to the underlying hash's
+ :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
.. seealso:: the :ref:`context-basic-example` example in the tutorial
"""
@@ -2517,14 +2529,19 @@ class CryptContext(object):
and should match it's :attr:`~passlib.ifc.PasswordHash.context_kwds`.
:returns:
- ``True`` if the password matched hash, else ``False``.
+ ``True`` if the password matched the 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 did not match any of the configured :meth:`schemes`.
- :raises ValueError: if the hash could not be identified.
+ * if any of the arguments have an invalid value (this includes
+ any keywords passed to the underlying hash's
+ :meth:`PasswordHash.verify() <passlib.ifc.PasswordHash.verify>` method).
+
+ :raises TypeError:
+ * if any of the arguments have an invalid type (this includes
+ any keywords passed to the underlying hash's
+ :meth:`PasswordHash.verify() <passlib.ifc.PasswordHash.verify>` method).
.. seealso:: the :ref:`context-basic-example` example in the tutorial
"""
@@ -2569,20 +2586,25 @@ class CryptContext(object):
:param \*\*kwds:
all additional keywords are passed to the appropriate handler,
- and should match it's :attr:`context keywords <passlib.hash.PasswordHash.context_kwds>`.
+ and should match that hash's
+ :attr:`PasswordHash.context_kwds <passlib.ifc.PasswordHash.context_kwds>`.
:returns:
This function returns a tuple containing two elements:
- the first indicates whether the password verified,
- and the second whether the existing hash needs to be replaced.
- The return value will always match one of the following 3 cases:
+ ``(verified, replacement_hash)``. The first is a boolean
+ flag indicating whether the password verified,
+ and the second an optional replacement hash.
+ The tuple will always match one of the following 3 cases:
* ``(False, None)`` indicates the secret failed to verify.
* ``(True, None)`` indicates the secret verified correctly,
- and the hash does not need upgrading.
+ and the hash does not need updating.
* ``(True, str)`` indicates the secret verified correctly,
- and the existing hash needs to be updated. the :class:`!str`
- will be the freshly generated hash to replace the old one with.
+ but the current hash needs to be updated. The :class:`!str`
+ will be the freshly generated hash, to replace the old one.
+
+ :raises TypeError, ValueError:
+ For the same reasons as :meth:`verify`.
.. seealso:: the :ref:`context-migration-example` example in the tutorial.
"""
@@ -2643,6 +2665,8 @@ class LazyCryptContext(CryptContext):
As well, it allows constructing a context at *module-init* time,
but using :func:`!onload()` to provide dynamic configuration
at *application-run* time.
+
+ .. versionadded:: 1.4
"""
_lazy_kwds = None
diff --git a/passlib/exc.py b/passlib/exc.py
index bdcbc5e..8d872a7 100644
--- a/passlib/exc.py
+++ b/passlib/exc.py
@@ -43,7 +43,8 @@ class PasswordSizeError(ValueError):
# warnings
#=============================================================================
class PasslibWarning(UserWarning):
- """base class for Passlib's user warnings.
+ """base class for Passlib's user warnings,
+ derives from the builtin :exc:`UserWarning`.
.. versionadded:: 1.6
"""
@@ -61,6 +62,8 @@ class PasslibConfigWarning(PasslibWarning):
In both of these cases, the code will perform correctly & securely;
but the warning is issued as a sign the configuration may need updating.
+
+ .. versionadded:: 1.6
"""
class PasslibHashWarning(PasslibWarning):
@@ -75,6 +78,8 @@ class PasslibHashWarning(PasslibWarning):
* A malformed hash string was encountered which (while parsable)
should be re-encoded.
+
+ .. versionadded:: 1.6
"""
class PasslibRuntimeWarning(PasslibWarning):
@@ -83,11 +88,15 @@ class PasslibRuntimeWarning(PasslibWarning):
The fact that it's a warning instead of an error means Passlib
was able to correct for the issue, but that it's anonmalous enough
that the developers would love to hear under what conditions it occurred.
+
+ .. versionadded:: 1.6
"""
class PasslibSecurityWarning(PasslibWarning):
"""Special warning issued when Passlib encounters something
that might affect security.
+
+ .. versionadded:: 1.6
"""
#=============================================================================
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 0e76524..61b0829 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -117,9 +117,9 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
checksum_chars = bcrypt64.charmap
#--HasManyIdents--
- default_ident = u("$2a$")
- ident_values = (u("$2$"), IDENT_2A, IDENT_2X, IDENT_2Y)
- ident_aliases = {u("2"): u("$2$"), u("2a"): IDENT_2A, u("2y"): IDENT_2Y}
+ default_ident = IDENT_2A
+ ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y)
+ ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A, u("2y"): IDENT_2Y}
#--HasSalt--
min_salt_size = max_salt_size = 22
@@ -128,7 +128,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
#--HasRounds--
default_rounds = 12 # current passlib default
- min_rounds = 4 # bcrypt spec specified minimum
+ min_rounds = 4 # minimum from bcrypt specification
max_rounds = 31 # 32-bit integer limit (since real_rounds=1<<rounds)
rounds_cost = "log2"
@@ -164,8 +164,13 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
if ident is None:
ident = self.ident
if ident == IDENT_2Y:
+ # none of passlib's backends suffered from crypt_blowfish's
+ # buggy "2a" hash, which means we can safely implement
+ # crypt_blowfish's "2y" hash by passing "2a" to the backends.
ident = IDENT_2A
else:
+ # no backends currently support 2x, but that should have
+ # been caught earlier in from_string()
assert ident != IDENT_2X
config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
return uascii_to_str(config)
@@ -197,7 +202,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
return hash
def _generate_salt(self, salt_size):
- # override to correct generate salt bits
+ # generate random salt as normal,
+ # but repair last char so the padding bits always decode to zero.
salt = super(bcrypt, self)._generate_salt(salt_size)
return bcrypt64.repair_unused(salt)
@@ -251,7 +257,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
@classproperty
def _has_backend_os_crypt(cls):
- # XXX: what to do if only h2 is supported? h1 is *very* rare.
+ # XXX: what to do if "2" isn't supported, but "2a" is?
+ # "2" is *very* rare, and can fake it using "2a"+repeat_string
h1 = '$2$04$......................1O4gOrCYaqBG3o/4LnT2ykQUt1wbyju'
h2 = '$2a$04$......................qiOQjkB8hxU8OzRhS.GhRMa4VUnkPty'
return test_crypt("test",h1) and test_crypt("test", h2)
diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py
index 4f469fc..b1d25b5 100644
--- a/passlib/handlers/cisco.py
+++ b/passlib/handlers/cisco.py
@@ -61,14 +61,14 @@ class cisco_pix(uh.HasUserContext, uh.StaticHandler):
user = self.user
if user:
- # NOTE: not *positive* about this, but it looks like per-user
- # accounts use first 4 chars of user as salt, whereas global
- # "enable" passwords don't have any salt at all.
+ # not positive about this, but it looks like per-user
+ # accounts use the first 4 chars of the username as the salt,
+ # whereas global "enable" passwords don't have any salt at all.
if isinstance(user, unicode):
user = user.encode("utf-8")
secret += user[:4]
- # pad/truncate to 16
+ # null-pad or truncate to 16 bytes
secret = right_pad_string(secret, 16)
# md5 digest
@@ -123,6 +123,8 @@ class cisco_type7(uh.GenericHandler):
setting_kwds = ("salt",)
checksum_chars = uh.UPPER_HEX_CHARS
+ # NOTE: encoding could handle max_salt_value=99, but since key is only 52
+ # chars in size, not sure what appropriate behavior is for that edge case.
min_salt_value = 0
max_salt_value = 52
@@ -154,9 +156,9 @@ class cisco_type7(uh.GenericHandler):
self.salt = self._norm_salt(salt)
def _norm_salt(self, salt):
- # NOTE: the "salt" for this algorithm is a small integer.
+ "the salt for this algorithm is an integer 0-52, not a string"
# XXX: not entirely sure that values >15 are valid, so for
- # compatibility we don't output those values but we do accept them.
+ # compatibility we don't output those values, but we do accept them.
if salt is None:
if self.use_defaults:
salt = self._generate_salt()
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index ee49236..642316e 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -60,7 +60,7 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
# really, apache? you had to invent a whole new "$apr1$" format,
# when all you did was change the ident incorporated into the hash?
# would love to find webpage explaining why just using a portable
- # implementation of $1$ wasn't sufficient. *nothing* else was changed.
+ # implementation of $1$ wasn't sufficient. *nothing else* was changed.
#===================================================================
# init & validate inputs
@@ -143,8 +143,8 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
# of things beforehand. It works off of a couple of observations
# about the original algorithm:
#
- # 1. each round is a combination of 'dc', 'salt', and 'pwd'; determined
- # by the whether 'i' a multiple of 2,3, and/or 7.
+ # 1. each round is a combination of 'dc', 'salt', and 'pwd'; and the exact
+ # combination is determined by whether 'i' a multiple of 2,3, and/or 7.
# 2. since lcm(2,3,7)==42, the series of combinations will repeat
# every 42 rounds.
# 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
@@ -152,12 +152,12 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
#
# Using these observations, the following code...
# * calculates the round-specific combination of salt & pwd for each round 0-41
- # * runs through as many 42-round blocks as possible
- # * runs through as many pairs of rounds as possible for remaining rounds
- # * performs once last round if the total rounds should be odd.
+ # * runs through as many 42-round blocks as possible (23)
+ # * runs through as many pairs of rounds as needed for remaining rounds (17)
+ # * this results in the required 42*23+2*17=1000 rounds required by md5_crypt.
#
# this cuts out a lot of the control overhead incurred when running the
- # original loop 40,000+ times in python, resulting in ~20% increase in
+ # original loop 1000 times in python, resulting in ~20% increase in
# speed under CPython (though still 2x slower than glibc crypt)
# prepare the 6 combinations of pwd & salt which are needed
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index aff0642..654124e 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -175,7 +175,8 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
warn(text, DeprecationWarning, stacklevel=2)
return func(*args, **kwds)
update_wrapper(wrapper, func)
- if updoc and (deprecated or removed) and wrapper.__doc__:
+ if updoc and (deprecated or removed) and \
+ wrapper.__doc__ and ".. deprecated::" not in wrapper.__doc__:
txt = deprecated or ''
if removed or replacement:
txt += "\n "
@@ -310,6 +311,7 @@ def consteq(left, right):
result = 1
# run constant-time string comparision
+ # TODO: use izip instead (but first verify it's faster than zip for this case)
if is_py3_bytes:
for l,r in zip(tmp, right):
result |= l ^ r
@@ -330,14 +332,16 @@ def splitcomma(source, sep=","):
return [ elem.strip() for elem in source.split(sep) ]
def saslprep(source, param="value"):
- """Normalizes unicode string using SASLPrep stringprep profile.
+ """Normalizes unicode strings using SASLPrep stringprep profile.
The SASLPrep profile is defined in :rfc:`4013`.
It provides a uniform scheme for normalizing unicode usernames
and passwords before performing byte-value sensitive operations
such as hashing. Among other things, it normalizes diacritic
representations, removes non-printing characters, and forbids
- invalid characters such as ``\\n``.
+ invalid characters such as ``\\n``. Properly internationalized
+ applications should run user passwords through this function
+ before hashing.
:arg source:
unicode string to normalize & validate
@@ -358,6 +362,8 @@ def saslprep(source, param="value"):
This function is not available under Jython,
as the Jython stdlib is missing the :mod:`!stringprep` module
(`Jython issue 1758320 <http://bugs.jython.org/issue1758320>`_).
+
+ .. versionadded:: 1.6
"""
# saslprep - http://tools.ietf.org/html/rfc4013
# stringprep - http://tools.ietf.org/html/rfc3454
@@ -414,7 +420,7 @@ def saslprep(source, param="value"):
in_table_c8 = stringprep.in_table_c8
in_table_c9 = stringprep.in_table_c9
for c in data:
- # check for this mapping stage should have removed
+ # check for chars mapping stage should have removed
assert not in_table_b1(c), "failed to strip B.1 in mapping stage"
assert not in_table_c12(c), "failed to replace C.1.2 in mapping stage"
@@ -1446,14 +1452,14 @@ except NotImplementedError: # pragma: no cover
def genseed(value=None):
"generate prng seed value from system resources"
- # if value is rng, extract a bunch of bits from it's state
from hashlib import sha512
- if hasattr(value, "getrandbits"):
- value = value.getrandbits(1<<15)
- text = u("%s %s %s %.15f %.15f %s") % (
- # if caller specified a seed value (e.g. current rng state), mix it in
+ text = u("%s %s %s %s %.15f %.15f %s") % (
+ # if caller specified a seed value, mix it in
value,
+ # if caller's seed value was an RNG, mix in bits from it's state
+ value.getrandbits(1<<15) if hasattr(value, "getrandbits") else None,
+
# add current process id
# NOTE: not available in some environments, e.g. GAE
os.getpid() if hasattr(os, "getpid") else None,
diff --git a/setup.py b/setup.py
index 408a62a..d29e782 100644
--- a/setup.py
+++ b/setup.py
@@ -85,12 +85,12 @@ Passlib is a password hashing library for Python 2 & 3, which provides
cross-platform implementations of over 30 password hashing algorithms, as well
as a framework for managing existing password hashes. It's designed to be useful
for a wide range of tasks, from verifying a hash found in /etc/shadow, to
-providing full-strength password hashing for multi-user application.
+providing full-strength password hashing for multi-user applications.
-* See the `online documentation <http://packages.python.org/passlib>`_
+* See the `documentation <http://packages.python.org/passlib>`_
for details, installation instructions, and examples.
-* See the `Passlib homepage <http://passlib.googlecode.com>`_
+* See the `homepage <http://passlib.googlecode.com>`_
for the latest news, more information, and additional downloads.
* See the `changelog <http://packages.python.org/passlib/history.html>`_
@@ -158,7 +158,8 @@ setup(
url = "http://passlib.googlecode.com",
download_url =
- ("http://passlib.googlecode.com/files/passlib-" + VERSION + ".tar.gz")
+# ("http://passlib.googlecode.com/files/passlib-" + VERSION + ".tar.gz")
+ ("http://pypi.python.org/packages/source/p/passlib/passlib-" + VERSION + ".tar.gz")
if is_release else None,
description = SUMMARY,