From 2d9c9a1368b947802a9200bc34f7316b4d22f394 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Thu, 20 Sep 2012 12:56:26 -0400 Subject: various documentation updates --- CHANGES | 8 +- MANIFEST.in | 3 +- admin/benchmarks.py | 37 +++++----- docs/_static/masthead.png | Bin 6296 -> 8103 bytes docs/_static/masthead.svg | 142 ++++++++++++++++++++++++++++++++++-- docs/conf.py | 26 +++---- docs/lib/passlib.registry.rst | 4 +- docs/lib/passlib.utils.handlers.rst | 9 ++- docs/lib/passlib.utils.rst | 12 ++- docs/modular_crypt_format.rst | 3 +- docs/password_hash_api.rst | 85 +++++++++++---------- passlib/context.py | 2 +- passlib/utils/__init__.py | 11 ++- setup.py | 9 ++- 14 files changed, 248 insertions(+), 103 deletions(-) diff --git a/CHANGES b/CHANGES index cc52623..6f9f2be 100644 --- a/CHANGES +++ b/CHANGES @@ -28,18 +28,18 @@ Release History * *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 4687f37..8edbcd5 100644 --- a/admin/benchmarks.py +++ b/admin/benchmarks.py @@ -15,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 @@ -64,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,)) @@ -224,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(): @@ -234,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(): @@ -245,20 +242,26 @@ def test_phpass(): hash = handler.encrypt(SECRET, **kwds) handler.verify(SECRET, hash) handler.verify(OTHER, hash) - yield helper + return helper #============================================================================= # crypto utils #============================================================================= @benchmark.constructor() -def test_pbkdf2(): -# from passlib.hash import pbkdf2_sha1 - from passlib.hash import pbkdf2_sha256 as hash +def test_pbkdf2_sha1(): + from passlib.utils.pbkdf2 import pbkdf2 + def helper(): + result = hexlify(pbkdf2("abracadabra", "open sesasme", 40960, 20, "hmac-sha1")) + assert result == 'ad317ed77bce584c90932b609e37e3736e6297bf', result + return helper + +@benchmark.constructor() +def test_pbkdf2_sha256(): + from passlib.utils.pbkdf2 import pbkdf2 def helper(): -# hash.encrypt("password", salt="salt", rounds=10000) - result = pbkdf2_sha1.encrypt("password", salt="salt", rounds=10000) - assert result == '$pbkdf2-sha256$10240$c2FsdA$FUGp71zmshcv1IwX1DV3ADWDyP66H/ANJZwmoGuF7FA' - yield helper + result = hexlify(pbkdf2("abracadabra", "open sesasme", 10240, 32, "hmac-sha256")) + assert result == '21d1ac0d474aaec49feb4f2172a266223e43edcf1052643dd27d82ebd5fa10c6', result + return helper #============================================================================= # main diff --git a/docs/_static/masthead.png b/docs/_static/masthead.png index 45e9015..677fcb1 100644 Binary files a/docs/_static/masthead.png and b/docs/_static/masthead.png 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,16 +14,28 @@ 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" style="display:inline"> + + + + + + + 0111010001101111011011110010000001101101011000010110111001111001001000000111001101100101011000110111001001100101011101000111001101110100011011110110111100100000011011010110000101101110011110010010000001110011011001010110001101110010011001010111010001110011011101000110111101101111001000000110110101100001011011100111100100100000011100110110010101100011011100100110010101110100011100110111010001101111011011110010000001101101011000010110111001111001001000000111001101100101011000110111001001100101011101000111001101110100011011110110111100100000011011010110000101101110011110010010000001110011011001010110001101110010011001010111010001110011011101000110111101101111001000000110110101100001011011100111100100100000011100110110010101100011011100100110010101110100011100110111010001101111011011110010000001101101011000010110111001111001001000000111001101100101011000110111001001100101011101000111001101110100011011110110111100100000011011010110000101101110011110010010000001110011011001010110001101110010011001010111010001110011 = v1.4), @@ -85,7 +84,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 +149,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 +189,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. @@ -245,7 +245,7 @@ htmlhelp_basename = project + 'Doc' # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ (index_doc, project + '.tex', project + ' Documentation', - 'Assurance Technologies, LLC', 'manual'), + author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -279,7 +279,7 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ (index_doc, project, project + ' Documentation', - ['Assurance Technologies, LLC'], 1) + [author], 1) ] #============================================================================= 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..a88297e 100644 --- a/docs/modular_crypt_format.rst +++ b/docs/modular_crypt_format.rst @@ -154,7 +154,8 @@ by the following operating systems and platforms: ===================== ============================================================== **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 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 ` 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..89bfd5f 100644 --- a/passlib/context.py +++ b/passlib/context.py @@ -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 diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index aff0642..adabf0b 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -310,6 +310,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 +331,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 +361,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 `_). + + .. versionadded:: 1.6 """ # saslprep - http://tools.ietf.org/html/rfc4013 # stringprep - http://tools.ietf.org/html/rfc3454 @@ -414,7 +419,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" 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 `_ +* See the `documentation `_ for details, installation instructions, and examples. -* See the `Passlib homepage `_ +* See the `homepage `_ for the latest news, more information, and additional downloads. * See the `changelog `_ @@ -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, -- cgit v1.2.1