summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-08-01 16:59:17 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-08-01 16:59:17 -0400
commit0c1086c6a41884352356f0d4822c95080e0caad6 (patch)
treeb418474812d933094e6fffbd660f3fbe35f5aff1
parent432fd93f6884036df59007f09dee86adfb7078ad (diff)
downloadpasslib-0c1086c6a41884352356f0d4822c95080e0caad6.tar.gz
large number of assorted documentation updates
-rw-r--r--CHANGES42
-rw-r--r--docs/conf.py111
-rw-r--r--docs/index.rst2
-rw-r--r--docs/install.rst20
-rw-r--r--docs/lib/passlib.hash.hex_digests.rst7
-rw-r--r--docs/lib/passlib.hash.msdcc2.rst20
-rw-r--r--docs/lib/passlib.hash.mysql323.rst2
-rw-r--r--docs/lib/passlib.hash.pbkdf2_digest.rst4
-rw-r--r--docs/lib/passlib.registry.rst2
-rw-r--r--docs/lib/passlib.utils.des.rst2
-rw-r--r--docs/lib/passlib.utils.rst2
-rw-r--r--docs/make.py5
-rw-r--r--docs/modular_crypt_format.rst103
-rw-r--r--docs/new_app_quickstart.rst12
-rw-r--r--docs/password_hash_api.rst60
-rw-r--r--docs/requirements.txt2
-rw-r--r--passlib/context.py14
-rw-r--r--passlib/exc.py15
-rw-r--r--passlib/ext/django/models.py4
-rw-r--r--passlib/ext/django/utils.py12
-rw-r--r--passlib/handlers/cisco.py2
-rw-r--r--passlib/utils/__init__.py74
-rw-r--r--passlib/utils/des.py12
-rw-r--r--setup.py2
-rw-r--r--tox.ini2
25 files changed, 318 insertions, 215 deletions
diff --git a/CHANGES b/CHANGES
index 11d9808..464c0eb 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,31 +8,31 @@ Release History
============================
Minor bugfix release
-
+
* *bugfix*: Various :class:`~passlib.context.CryptContext` methods
would incorrectly raise :exc:`TypeError` if passed a :class:`!unicode`
- user category under Python 2. For consistency
- they will now be treated the same as the equivalent ``utf-8`` :class:`bytes`.
+ user category under Python 2. For consistency,
+ :class:`!unicode` user category values are now encoded to ``utf-8`` :class:`bytes` under Python 2.
* *bugfix*: Reworked internals of the :class:`CryptContext` config compiler
to fix a couple of border cases (:issue:`39`):
-
+
- It will now throw a :exc:`ValueError`
if the :ref:`default <context-default-option>` scheme is marked as
:ref:`deprecated <context-deprecated-option>`.
- - If no default scheme is specified, it will use the first
+ - If no default scheme is specified, it will use the first
*non-deprecated* scheme.
- - Finally, it will now throw a :exc:`ValueError` if all schemes
+ - Finally, it will now throw a :exc:`ValueError` if all schemes
are marked as deprecated.
-
- * *bugfix*: FreeBSD 8.3 added native support for :class:`~passlib.hash.sha256_crypt`,
- updated unittests and documentation accordingly (:issue:`35`).
-
+
+ * *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
- if filesystem had mtime resolution >= 1 second (:issue:`35`).
-
+ if mtime resolution >= 1 second (:issue:`35`).
+
* Various documentation updates and corrections.
-
+
**1.6** (2012-05-01)
====================
@@ -43,10 +43,10 @@ Overview
Welcome to Passlib 1.6.
- The main goal of this release was to clean up the codebase, tighten input
- validation, and simplify the publically exposed interfaces. This release also
- brings a number of other improvements: 10 or so new hash algorithms,
- additional security precautions for the existing algorithms,
+ The main goal of this release was to clean up the codebase, tighten input
+ validation, and simplify the publically exposed interfaces. This release also
+ brings a number of other improvements: 10 or so new hash algorithms,
+ additional security precautions for the existing algorithms,
a number of speed improvements, and updated documentation.
Deprecated APIs
@@ -121,8 +121,8 @@ Existing Hashes
.. _consteq-issue:
*Constant Time Comparison*
- All hash comparisons in Passlib now use a "constant time" [#consteq]_
- comparison function :func:`~passlib.utils.consteq` instead
+ All hash comparisons in Passlib now use the "constant time" [#consteq]_
+ comparison function :func:`~passlib.utils.consteq`, instead
of ``==``.
This change is motivated a well-known `hmac timing attack <http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/>`_
@@ -322,7 +322,7 @@ Internal Changes
**1.5.3** (2011-10-08)
======================
- Bugfix release -- fixes BCrypt padding/verification issue
+ Bugfix release -- fixes BCrypt padding/verification issue (:issue:`25`)
.. _bcrypt-padding-issue:
@@ -375,7 +375,7 @@ Internal Changes
* *bugfix:* :class:`django_des_crypt` now accepts all
:data:`hash64 <passlib.utils.h64>` characters in it's salts;
- previously it accepted only lower-case hexidecimal characters :issue:`22`.
+ previously it accepted only lower-case hexidecimal characters (:issue:`22`).
* Additional unittests added for all
standard :doc:`Django hashes </lib/passlib.hash.django_std>`.
diff --git a/docs/conf.py b/docs/conf.py
index 1c8267f..27b26f4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,23 +1,20 @@
# -*- coding: utf-8 -*-
-#
-# Passlib documentation build configuration file, created by
-# sphinx-quickstart on Mon Mar 2 14:12:06 2009.
-#
-# 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
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
+"""
+Sphinx configuration file for 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
+autogenerated file. All configuration values have a default; values that are
+commented out serve to show the default.
+"""
+#=============================================================================
+# environment setup
+#=============================================================================
import sys, os
-options = os.environ.get("PASSLIB_DOCS", "").split(",")
-
# make sure passlib in sys.path
-doc_root = os.path.abspath(os.path.join(__file__,os.path.pardir))
-source_root = os.path.abspath(os.path.join(doc_root,os.path.pardir))
+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,
@@ -25,19 +22,28 @@ sys.path.insert(0, source_root)
# documentation root, use os.path.abspath to make it absolute, like shown here.
##sys.path.insert(0, os.path.abspath('.'))
-# building the docs requires the Cloud sphinx theme & extensions
-# https://bitbucket.org/ecollins/cloud_sptheme
-# which contains some sphinx extensions used by passlib
+#=============================================================================
+# imports
+#=============================================================================
+
+# load build option flags
+options = os.environ.get("PASSLIB_DOCS", "").split(",")
+
+# building the docs requires the Cloud Sphinx theme & extensions (>= v1.4),
+# which contains some sphinx extensions used by Passlib.
+# (https://bitbucket.org/ecollins/cloud_sptheme)
import cloud_sptheme as csp
# hack to make autodoc generate documentation from the correct class...
import passlib.utils.md4 as md4_mod
md4_mod.md4 = md4_mod._builtin_md4
-# -- General configuration -----------------------------------------------------
+#=============================================================================
+# General configuration
+#=============================================================================
# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.0'
+needs_sphinx = '1.1'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
@@ -73,19 +79,20 @@ source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'contents'
+
+# The frontpage document.
index_doc = 'index'
# General information about the project.
-project = u'Passlib'
-copyright = u'2008-2012, Assurance Technologies, LLC'
+project = 'Passlib'
+copyright = '2008-2012, Assurance Technologies, LLC'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
-#
-# version: The short X.Y version.
# release: The full version, including alpha/beta/rc tags.
+# version: The short X.Y version.
from passlib import __version__ as release
version = csp.get_version(release)
@@ -127,14 +134,18 @@ add_function_parentheses = True
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
-modindex_common_prefix = [ "passlib." ]
+modindex_common_prefix = ["passlib."]
-# -- Options for all output ---------------------------------------------------
-todo_include_todos = "hide-todos" not in options
-keep_warnings = "hide-warnings" not in options
+#=============================================================================
+# Options for all output
+#=============================================================================
+todo_include_todos = True
+keep_warnings = True
issue_tracker_url = "gc:passlib"
-# -- Options for HTML output ---------------------------------------------------
+#=============================================================================
+# Options for HTML output
+#=============================================================================
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
@@ -143,24 +154,24 @@ html_theme = '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.
-if html_theme in ['cloud', 'redcloud']:
- html_theme_options = { "roottarget": index_doc, "collapsiblesidebar": True }
+html_theme_options = {}
+if csp.is_cloud_theme(html_theme):
+ html_theme_options.update(roottarget=index_doc)
if 'for-pypi' in options:
- html_theme_options['googleanalytics_id'] = 'UA-22302196-2'
- html_theme_options['googleanalytics_path'] = '/passlib/'
-else:
- html_theme_options = {}
-html_theme_options.update(issueicon=r'"\21D7"')
+ html_theme_options.update(
+ googleanalytics_id = 'UA-22302196-2',
+ googleanalytics_path = '/passlib/',
+ )
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [csp.get_theme_dir()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
-html_title = project + " v" + release + " Documentation"
+html_title = "%s v%s Documentation" % (project, release)
# A shorter title for the navigation bar. Default is the same as html_title.
-html_short_title = project + " " + version + " Documentation"
+html_short_title = "%s %s Documentation" % (project, version)
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
@@ -220,8 +231,9 @@ html_use_smartypants = True
# Output file base name for HTML help builder.
htmlhelp_basename = project + 'Doc'
-
-# -- Options for LaTeX output --------------------------------------------------
+#=============================================================================
+# Options for LaTeX output
+#=============================================================================
# The paper size ('letter' or 'a4').
##latex_paper_size = 'letter'
@@ -232,8 +244,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 + u' Documentation',
- u'Assurance Technologies, LLC', 'manual'),
+ (index_doc, project + '.tex', project + ' Documentation',
+ 'Assurance Technologies, LLC', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -259,12 +271,17 @@ latex_documents = [
# If false, no module index is generated.
##latex_domain_indices = True
-
-# -- Options for manual page output --------------------------------------------
+#=============================================================================
+# Options for manual page output
+#=============================================================================
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (index_doc, project, project + u' Documentation',
- [u'Assurance Technologies, LLC'], 1)
+ (index_doc, project, project + ' Documentation',
+ ['Assurance Technologies, LLC'], 1)
]
+
+#=============================================================================
+# EOF
+#=============================================================================
diff --git a/docs/index.rst b/docs/index.rst
index 5396e6b..1d8641b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -107,8 +107,6 @@ Other Documents
Online Resources
================
- .. rst-class:: html-plain-table
-
================ ===================================================
Homepage: `<http://passlib.googlecode.com>`_
Online Docs: `<http://packages.python.org/passlib>`_
diff --git a/docs/install.rst b/docs/install.rst
index 78c4149..0f98cc9 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -2,6 +2,8 @@
Installation
============
+.. index:: Google App Engine; compatibility
+
Supported Platforms
===================
Passlib requires Python 2 (>= 2.5) or Python 3.
@@ -38,15 +40,15 @@ Optional Libraries
Installation Instructions
=========================
-To download and install using :command:`easy_install`::
+To install from PyPi using :command:`pip`::
- easy_install passlib
+ pip install passlib
-To download and install using :command:`pip`::
+To install from PyPi using :command:`easy_install`::
- pip install passlib
+ easy_install passlib
-To install from a source directory using :command:`setup.py`::
+To install from the source using :command:`setup.py`::
python setup.py install
@@ -84,11 +86,11 @@ online at `<http://packages.python.org/passlib>`_.
If you wish to generate your own copy of the documentation,
you will need to:
-1. Install `Sphinx <http://sphinx.pocoo.org/>`_ (1.0 or better)
-2. Install the `Cloud Sphinx Theme <http://packages.python.org/cloud_sptheme>`_.
+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).
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`
+5. Once Sphinx completes it's run, point a web browser to the file at :samp:`{SOURCE}/build/sphinx/html/index.html`
to access the Passlib documentation in html format.
6. Alternately, steps 4 & 5 can be replaced by running :samp:`python setup.py docdist`,
- which will build a zip file of the documentation in :samp:`{$SOURCE}/dist`.
+ which will build a zip file of the documentation in :samp:`{SOURCE}/dist`.
diff --git a/docs/lib/passlib.hash.hex_digests.rst b/docs/lib/passlib.hash.hex_digests.rst
index 0f8f5fd..de13bca 100644
--- a/docs/lib/passlib.hash.hex_digests.rst
+++ b/docs/lib/passlib.hash.hex_digests.rst
@@ -37,6 +37,8 @@ and can be used directly as follows::
.. seealso:: the generic :ref:`PasswordHash usage examples <password-hash-examples>`
+.. index:: virtualbox; passwordhash
+
Interface
=========
.. class:: hex_md4()
@@ -51,6 +53,11 @@ Interface
They support no settings or other keywords.
+.. note::
+
+ Oracle VirtualBox's :cmd:`VBoxManager internalcommands passwordhash` command
+ uses :class:`hex_sha256`.
+
Format & Algorithm
==================
All of these classes just report the result of the specified digest,
diff --git a/docs/lib/passlib.hash.msdcc2.rst b/docs/lib/passlib.hash.msdcc2.rst
index f63f4a0..6cd99aa 100644
--- a/docs/lib/passlib.hash.msdcc2.rst
+++ b/docs/lib/passlib.hash.msdcc2.rst
@@ -13,7 +13,7 @@ This class implements the DCC2 (Domain Cached Credentials version 2) hash, used
by Windows Vista and newer to cache and verify remote credentials when the relevant
server is unavailable. It is known by a number of other names,
including "mscache2" and "mscash2" (Microsoft CAched haSH). It replaces
-the weaker :doc:`msdcc (v1)<passlib.hash.msdcc>` hash used by previous releases
+the weaker :doc:`msdcc v1<passlib.hash.msdcc>` hash used by previous releases
of Windows. Security wise it is not particularly weak, but due to it's
use of the username as a salt, it should probably not be used for anything
but verifying existing cached credentials.
@@ -77,13 +77,29 @@ The digest is calculated as follows:
Security Issues
===============
-This hash is essentially DCC v1 with a fixed-round PBKDF2 function
+This hash is essentially :doc:`msdcc v1 <passlib.hash.msdcc>` with a fixed-round PBKDF2 function
wrapped around it. The number of rounds of PBKDF2 is currently
sufficient to make this a semi-reasonable way to store passwords,
but the use of the lowercase username as a salt, and the fact
that the rounds can't be increased, means this hash is not particularly
future-proof, and should not be used for new applications.
+Deviations
+==========
+
+* Max Password Size
+
+ Windows appears to enforce a maximum password size,
+ but the actual value of this limit is unclear; sources
+ report it to be set at assorted values from 26 to 128 characters,
+ and it may in fact vary between Windows releases.
+ The one consistent peice of information is that
+ passwords above the limit are simply not allowed (rather
+ than truncated ala :class:`~passlib.hash.des_crypt`).
+ Because of this, Passlib does not currently enforce a size limit:
+ any hashes this class generates should be correct, provided Windows
+ is willing to accept a password of that size.
+
.. rubric:: Footnotes
.. [#] Description of DCC v2 algorithm -
diff --git a/docs/lib/passlib.hash.mysql323.rst b/docs/lib/passlib.hash.mysql323.rst
index 80272b4..f5fac6a 100644
--- a/docs/lib/passlib.hash.mysql323.rst
+++ b/docs/lib/passlib.hash.mysql323.rst
@@ -62,7 +62,7 @@ but verifying existing MySQL 3.2.3 - 4.0 password hashes.
.. rubric:: Footnotes
-.. [#f1] Source of implementation used by passlib -
+.. [#f1] Source of implementation used by Passlib -
`<http://djangosnippets.org/snippets/1508/>`_
.. [#f2] Mysql document describing transition -
diff --git a/docs/lib/passlib.hash.pbkdf2_digest.rst b/docs/lib/passlib.hash.pbkdf2_digest.rst
index 522213a..8fc5f59 100644
--- a/docs/lib/passlib.hash.pbkdf2_digest.rst
+++ b/docs/lib/passlib.hash.pbkdf2_digest.rst
@@ -89,10 +89,10 @@ follow the same format, :samp:`$pbkdf2-{digest}${rounds}${salt}${checksum}`.
* :samp:`{checksum}` - this is the :func:`adapted base64 encoding <passlib.utils.ab64_encode>`
of the raw derived key bytes returned from the PBKDF2 function.
- Each scheme uses output size of it's specific :samp:`{digest}`
+ Each scheme uses the digest size of it's specific hash algorithm (:samp:`{digest}`)
as the size of the raw derived key. This is enlarged
by appromixately 4/3 by the base64 encoding,
- resulting in a checksum size of 27, 43, and 86 for each of the respective algorithms.
+ resulting in a checksum size of 27, 43, and 86 for each of the respective algorithms listed above.
The algorithm used by all of these schemes is deliberately identical and simple:
The password is encoded into UTF-8 if not already encoded,
diff --git a/docs/lib/passlib.registry.rst b/docs/lib/passlib.registry.rst
index 2457cb1..6db21c4 100644
--- a/docs/lib/passlib.registry.rst
+++ b/docs/lib/passlib.registry.rst
@@ -10,7 +10,7 @@ that it knows about. While custom handlers can be used directly within an applic
or even handed to a :class:`!CryptContext`; it is frequently useful to register
them globally within a process and then refer to them by name.
This module provides facilities for that, as well as programmatically
-querying passlib to detect what algorithms are available.
+querying Passlib to detect what algorithms are available.
.. warning::
diff --git a/docs/lib/passlib.utils.des.rst b/docs/lib/passlib.utils.des.rst
index ea09506..67fef1e 100644
--- a/docs/lib/passlib.utils.des.rst
+++ b/docs/lib/passlib.utils.des.rst
@@ -12,7 +12,7 @@
should not be used in new applications.
This module contains routines for encrypting blocks of data using the DES algorithm.
-They do not support multi-block operation or decryption,
+Note that these functions do not support multi-block operation or decryption,
since they are designed primarily for use in password hash algorithms
(such as :class:`~passlib.hash.des_crypt` and :class:`~passlib.hash.bsdi_crypt`).
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst
index dc9a12f..c3f2739 100644
--- a/docs/lib/passlib.utils.rst
+++ b/docs/lib/passlib.utils.rst
@@ -161,7 +161,7 @@ Randomness
.. autofunction:: getrandbytes
.. autofunction:: getrandstr
-.. autofunction:: generate_password(size=10, charset=<default>)
+.. autofunction:: generate_password(size=10, charset=<default charset>)
Interface Tests
===============
diff --git a/docs/make.py b/docs/make.py
deleted file mode 100644
index 1f84953..0000000
--- a/docs/make.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"Makefile for Sphinx documentation, adapted to python"
-import os
-from cloud_sptheme.make_helper import SphinxMaker
-if __name__ == "__main__":
- SphinxMaker.execute(root_dir=os.path.join(__file__,os.pardir))
diff --git a/docs/modular_crypt_format.rst b/docs/modular_crypt_format.rst
index 6457170..a5566a5 100644
--- a/docs/modular_crypt_format.rst
+++ b/docs/modular_crypt_format.rst
@@ -40,44 +40,45 @@ Requirements
============
Unfortunately, there is no specification document for this format.
Instead, it exists in *de facto* form only; the following
-is an attempt to roughly identify the guidelines followed
-by the modular crypt format hashes found in passlib:
+is an attempt to roughly identify the conventions followed
+by the modular crypt format hashes found in Passlib:
-1. Hash strings must use only 7-bit ascii characters.
+1. Hash strings should use only 7-bit ascii characters.
No known OS or application generates hashes which violate this rule.
- However, some systems (e.g. Linux's shadow routines) will happily
- and correctly accept hashes which contain 8-bit characters in their salt.
+ However, some systems (e.g. Linux) will happily
+ accept hashes which contain 8-bit characters in their salt,
This is probably a case of "permissive in what you accept,
strict in what you generate".
-2. Hash strings should always start with the prefix :samp:`${identifier}$`,
+2. Hash strings should start with the prefix :samp:`${identifier}$`,
where :samp:`{identifier}` is a short string uniquely identifying
hashes generated by that algorithm, using only lower case ascii
- letters, numbers, and hyphens.
+ letters, numbers, and hyphens
+ (c.f. the list of :ref:`known identifiers <mcf-identifiers>` below).
- Initially, most schemes adhereing to this format
- only used a single digit to identify the hash
- (e.g. ``$1$`` for :class:`!md5_crypt`).
- Because of this, many systems only look at the first
+ When MCF was first introduced, most schemes choose a single digit
+ as their identifier (e.g. ``$1$`` for :class:`~passlib.hash.md5_crypt`).
+ Because of this, some older systems only look at the first
character when attempting to distinguish hashes.
+ However, as Unix variants have branched off,
+ new schemes were developed which used larger
+ identifying strings (e.g. ``$sha1$`` for :class:`~passlib.hash.sha1_crypt`).
- Despite this, as Unix systems have branched off,
- new hashes have been developed which used larger
- identifying strings (e.g. ``$sha1$`` for :class:`~passlib.hash.sha1_crypt`);
- so in general identifier strings should not be assumed to use a single character.
+ At this point, any new hash schemes should probably use a 6-8 character
+ descriptive identifier, to avoid potential namespace clashes.
-3. Hashes should contain only ascii letters ``a``-``z`` and ``A``-``Z``,
+3. Hashes should only contain the ascii letters ``a``-``z`` and ``A``-``Z``,
ascii numbers 0-9, and the characters ``./``; though additionally
- they should use the ``$`` character as an internal field separator.
+ they may use the ``$`` character as an internal field separator.
- This is the least adhered-to of any modular crypt format rule.
+ This is the least adhered-to of any modular crypt format convention.
Other characters (such as ``=,-``) are sometimes
used by various formats, though sparingly.
The only hard and fast stricture
- is that ``:;!*`` and non-printable characters be avoided,
- since this would interfere with parsing of /etc/shadow
+ is that ``:;!*`` and all non-printable characters be avoided,
+ since this would interfere with parsing of the Unix shadow password file,
where these hashes are typically stored.
Pretty much all modular-crypt-format hashes
@@ -86,8 +87,8 @@ by the modular crypt format hashes found in passlib:
though the exact character value assignments vary between hashes
(see :data:`passlib.utils.h64`).
-4. Hash schemes should put their "checksum" portion
- at the end of the hash, preferrably separated
+4. Hash schemes should put their "digest" portion
+ at the end of the hash, preferably separated
by a ``$``.
This allows password hashes to be easily truncated
@@ -99,12 +100,13 @@ by the modular crypt format hashes found in passlib:
in order to verify a password, without
having to perform excessive parsing.
- Most modular crypt format hashes follow this,
- though some (like :class:`~passlib.hash.bcrypt`) omit the ``$`` separator.
+ Most modular crypt format hashes follow this convention,
+ though some (like :class:`~passlib.hash.bcrypt`) omit the ``$`` separator
+ between the configuration and the digest.
- As well, there is no set standard about whether configuration
+ Furthermore, there is no set standard about whether configuration
strings should or should not include a trailing ``$`` at the end,
- though the general rule is that a hash behave the same regardless
+ though the general rule is that hashing should behave the same in either case
(:class:`~passlib.hash.sun_md5_crypt` behaves particularly poorly
regarding this last point).
@@ -122,26 +124,48 @@ by the modular crypt format hashes found in passlib:
Identifiers & Platform Support
==============================
+OS Defined Hashes
+-----------------
The following table lists of all the major MCF hashes supported by Passlib,
-and indicates which operating systems offer native support for them [#gae]_.
+and indicates which operating systems offer native support:
-==================================== ==================== =========== =========== =========== =========== ======= =======
-Scheme Prefix Linux FreeBSD NetBSD OpenBSD Solaris MacOSX
-==================================== ==================== =========== =========== =========== =========== ======= =======
-:class:`~passlib.hash.des_crypt` y y y y y y
-:class:`~passlib.hash.bsdi_crypt` ``_`` y y y y
+==================================== ==================== =========== =========== =========== =========== =======
+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.sun_md5_crypt` ``$md5$``, ``$md5,`` 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
-The following table lists the other MCF hashes supported by Passlib,
-most of which are only used by applications:
+===================== ==============================================================
+**MacOS X** Darwin's native :func:`!crypt` provides limited functionality,
+ supporting only :class:`~passlib.hash.des_crypt` and
+ :class:`~passlib.hash.bsdi_crypt`.
+
+**Google App Engine** As of 2011-08-19, Google App Engine's :func:`!crypt`
+ implementation appears to match that of a typical Linux
+ system.
+===================== ==============================================================
+
+Application-Defined Hashes
+--------------------------
+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)
@@ -158,11 +182,8 @@ Scheme Prefix Primary Use (if
.. rubric:: Footnotes
-.. [#gae] As of 2011-08-19, Google App Engine's :mod:`crypt` implementation
- appears to provide hash support matching that of a typical Linux system.
-
.. [#cta] :class:`!cta_pbkdf2_sha1` and :class:`!dlitz_pbkdf2_sha1` both use
the same identifier. While there are other internal differences,
- they can be quickly distinguished
- by the fact that cta hashes will always end in ``=``, while dlitz
+ the two can be quickly distinguished
+ by the fact that cta hashes always end in ``=``, while dlitz
hashes contain no ``=`` at all.
diff --git a/docs/new_app_quickstart.rst b/docs/new_app_quickstart.rst
index a036eb5..43b7631 100644
--- a/docs/new_app_quickstart.rst
+++ b/docs/new_app_quickstart.rst
@@ -35,6 +35,8 @@ For applications which started using this preset, but whose needs
have grown beyond it, it is recommended to create your own :mod:`CryptContext <passlib.context>`
instance; see below for more...
+.. index:: Passlib; recommended hash algorithms
+
.. _recommended-hashes:
Choosing a Hash
@@ -121,7 +123,9 @@ version for use in a pre-computed or brute-force search.
However, this design also hampers analysis of the algorithm
for future flaws.
-This algorithm is probably the best choice for Google App Engine,
+.. index:: Google App Engine; recommended hash algorithm
+
+:class:`~passlib.hash.sha512_crypt` is probably the best choice for Google App Engine,
as Google's production servers appear to provide native support
via :mod:`crypt`, which will be used by Passlib.
@@ -233,7 +237,7 @@ To start using your CryptContext, import the context you created wherever it's n
.. rubric:: Footnotes
.. [#choices] BCrypt, SHA-512 Crypt, and PBKDF2 are the most commonly
- used password hashes as of May 2011, when this document
- was written. You should make sure you are reading a current
- copy of the passlib documentation, in case the state
+ used password hashes as of Aug 2012, when this document
+ last updated. You should make sure you are reading a current
+ copy of the Passlib documentation, in case the state
of things has changed.
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index 8d136c2..f97a327 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -149,11 +149,11 @@ 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.
-
+
:type secret: unicode or bytes
:arg secret: string containing the password to encode.
@@ -286,7 +286,7 @@ Taken together, the :meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.gen
are two tightly-coupled methods that mimic the standard Unix
"crypt" interface. The first method generates salt / configuration
strings from a set of settings, and the second hashes the password
-using the provided configuration string.
+using the provided configuration string.
.. seealso::
@@ -452,7 +452,7 @@ the hashes in passlib:
the specific settings the hash uses, the following keywords should have
roughly the same behavior for all the hashes that support them:
- .. index::
+ .. index::
single: salt; PasswordHash keyword
``salt``
@@ -467,7 +467,7 @@ the hashes in passlib:
:class:`!bytes` instance, with additional constraints
appropriate to the algorithm.
- .. index::
+ .. index::
single: salt_size; PasswordHash keyword
``salt_size``
@@ -480,7 +480,7 @@ the hashes in passlib:
.. seealso:: the :ref:`salt info <salt-attributes>` attributes (below)
- .. index::
+ .. index::
single: rounds; PasswordHash keyword
``rounds``
@@ -500,7 +500,7 @@ the hashes in passlib:
.. seealso:: the :ref:`rounds info <rounds-attributes>` attributes (below)
- .. index::
+ .. index::
single: ident; PasswordHash keyword
``ident``
@@ -512,9 +512,9 @@ the hashes in passlib:
revision of the hash algorithm itself, and they may not all
offer the same level of security.
- .. index::
+ .. index::
single: relaxed; PasswordHash keyword
-
+
.. _relaxed-keyword:
``relaxed``
@@ -546,7 +546,7 @@ the hashes in passlib:
the following keywords should have roughly the same behavior
for all the hashes that support them:
- .. index::
+ .. index::
single: user; PasswordHash keyword
``user``
@@ -556,7 +556,7 @@ the hashes in passlib:
:class:`~passlib.hash.postgres_md5` and
:class:`~passlib.hash.oracle10`).
- .. index::
+ .. index::
single: encoding; PasswordHash keyword
``encoding``
@@ -597,7 +597,7 @@ and the following attributes should be defined:
.. attribute:: PasswordHash.salt_chars
A unicode string containing all the characters permitted
- in a salt string.
+ in a salt string.
For most :ref:`modular-crypt-format` hashes,
this is equal to :data:`passlib.utils.HASH64_CHARS`.
@@ -654,16 +654,6 @@ and the following attributes should be defined:
* ``"log2"`` - time taken scales exponentially with rounds value
(e.g. :class:`~passlib.hash.bcrypt`)
-.. todo::
-
- Add notes about when/how the default rounds are retuned.
- For the 1.6 release, all hashes were retuned to take ~250ms
- on a single 3 ghz cpu core, or more rounds if that was felt
- to not provide a minimum level of security. Also, there are
- so many variables affecting relative system performance,
- that this policy is more of an informed heuristic than a
- rigid algorithm.
-
..
todo: haven't decided if this is how I want the api look before
formally publishing it in the documentation:
@@ -720,3 +710,29 @@ and the following attributes should be defined:
:raises passlib.exc.MissingBackendError:
if the specified backend is not available.
+
+.. index:: rounds; choosing the right value
+
+Choosing the right ``rounds`` value
+===================================
+Passlib's default rounds settings 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**.
+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)
+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.
+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.
+
+.. [#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/docs/requirements.txt b/docs/requirements.txt
index e6841c4..794327c 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1 +1 @@
-cloud_sptheme>=1.3
+cloud_sptheme>=1.4
diff --git a/passlib/context.py b/passlib/context.py
index 84372dc..2b091f1 100644
--- a/passlib/context.py
+++ b/passlib/context.py
@@ -937,6 +937,12 @@ class _CryptRecord(object):
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()
@@ -2608,12 +2614,12 @@ class LazyCryptContext(CryptContext):
the first time one of it's methods is accessed.
:arg schemes:
- the first positional argument can be a list of schemes, or omitted,
+ 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,
+ If a callable is passed in via this keyword,
it will be invoked at lazy-load time
with the following signature:
``onload(**kwds) -> kwds``;
@@ -2626,8 +2632,8 @@ class LazyCryptContext(CryptContext):
:param create_policy:
.. deprecated:: 1.6
- This option will be removed in Passlib 1.8.
- Applications should use *onload* instead.
+ This option will be removed in Passlib 1.8,
+ applications should use ``onload`` instead.
:param kwds:
diff --git a/passlib/exc.py b/passlib/exc.py
index 1d3538b..bdcbc5e 100644
--- a/passlib/exc.py
+++ b/passlib/exc.py
@@ -10,8 +10,8 @@ class MissingBackendError(RuntimeError):
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`.
+ external libraries (which is currently just
+ :class:`~passlib.hash.bcrypt`).
"""
class PasswordSizeError(ValueError):
@@ -54,9 +54,9 @@ class PasslibConfigWarning(PasslibWarning):
This occurs primarily in one of two cases:
- * the CryptContext contains rounds limits which exceed the hard limits
+ * 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
+ * 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;
@@ -69,10 +69,11 @@ class PasslibHashWarning(PasslibWarning):
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).
+ * 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 hash malformed hash string was encountered, which while parsable,
+ * A malformed hash string was encountered which (while parsable)
should be re-encoded.
"""
diff --git a/passlib/ext/django/models.py b/passlib/ext/django/models.py
index e3938aa..4fb6aea 100644
--- a/passlib/ext/django/models.py
+++ b/passlib/ext/django/models.py
@@ -111,6 +111,7 @@ def _apply_patch():
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:
@@ -122,6 +123,7 @@ def _apply_patch():
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)
@@ -266,6 +268,8 @@ def _load():
_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")
diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py
index 098dbe4..3c03637 100644
--- a/passlib/ext/django/utils.py
+++ b/passlib/ext/django/utils.py
@@ -96,12 +96,12 @@ superuser__django_pbkdf2_sha256__default_rounds = 15000
# translating passlib names <-> hasher names
#=============================================================================
-# prefix used to shoehorn passlib's handler names into hasher namespace
-# (allows get_hasher() to be meaningfully called even if passlib handler
-# is the one being used)
+# 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.
+# 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_"
@@ -125,7 +125,7 @@ def hasher_to_passlib_name(hasher_name):
if getattr(handler, "django_name", None) == hasher_name:
return name
# XXX: this should only happen for custom hashers that have been registered.
- # work in progress (below) that would take care of those.
+ # _HasherHandler (below) is work in progress that would fix this.
raise ValueError("can't translate hasher name to passlib name: %r" %
hasher_name)
@@ -147,7 +147,7 @@ class _HasherWrapper(object):
def salt(self):
# XXX: our encode wrapper generates a new salt each time it's called,
- # so just returning an 'no value' flag here.
+ # so just returning a 'no value' flag here.
return _FAKE_SALT
def verify(self, password, encoded):
diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py
index b66168e..4f469fc 100644
--- a/passlib/handlers/cisco.py
+++ b/passlib/handlers/cisco.py
@@ -109,8 +109,6 @@ class cisco_type7(uh.GenericHandler):
will be issued instead. Correctable errors include
``salt`` values that are out of range.
- .. versionadded:: 1.6
-
Note that while this class outputs digests in upper-case hexidecimal,
it will accept lower-case as well.
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index c0f9a47..3b3c9f0 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -244,28 +244,40 @@ class memoized_property(object):
#=============================================================================
def consteq(left, right):
- """Check two strings/bytes for equality, taking constant time relative
- to the size of the righthand input.
-
- The purpose of this function is to aid in preventing timing attacks
- during digest comparisons (see the 1.6 changelog
- :ref:`entry <consteq-issue>` for more details).
+ """Check two strings/bytes for equality.
+ This is functionally equivalent to ``left == right``,
+ but attempts to take constant time relative to the size of the righthand input.
+
+ The purpose of this function is to help prevent timing attacks
+ during digest comparisons: the standard ``==`` operator aborts
+ after the first mismatched character, causing it's runtime to be
+ proportional to the longest prefix shared by the two inputs.
+ If an attacker is able to predict and control one of the two
+ inputs, repeated queries can be leveraged to reveal information about
+ the content of the second argument. To minimize this risk, :func:`!consteq`
+ is designed to take ``THETA(len(right))`` time, regardless
+ of the contents of the two strings.
+ It is recommended that the attacker-controlled input
+ be passed in as the left-hand value.
+
+ .. warning::
+
+ This function is *not* perfect. Various VM-dependant issues
+ (e.g. the VM's integer object instantiation algorithm, internal unicode representation, etc),
+ may still cause the function's run time to be affected by the inputs,
+ though in a less predictable manner.
+ *To minimize such risks, this function should not be passed* :class:`unicode`
+ *inputs that might contain non-* ``ASCII`` *characters*.
+
+ .. versionadded:: 1.6
"""
# NOTE:
- # This function attempts to take an amount of time proportional
- # to ``THETA(len(right))``. The main loop is designed so that timing attacks
- # against this function should reveal nothing about how much (or which
- # parts) of the two inputs match.
- #
- # Why ``THETA(len(right))``?
- # Assuming the attacker controls one of the two inputs, padding to
- # the largest input or trimming to the smallest input both allow
- # a timing attack to reveal the length of the other input.
- # However, by fixing the runtime to be proportional to the right input:
- # * If the right value is attacker controlled, the runtime is proportional
- # to their input, giving nothing away about the left value's size.
- # * If the left value is attacker controlled, the runtime is constant
- # relative to their input, giving nothing away about the right value's size.
+ # resources & discussions considered in the design of this function:
+ # hmac timing attack --
+ # http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/
+ # python developer discussion surrounding similar function --
+ # http://bugs.python.org/issue15061
+ # http://bugs.python.org/issue14955
# validate types
if isinstance(left, unicode):
@@ -329,8 +341,8 @@ def saslprep(source, param="value"):
unicode string to normalize & validate
:param param:
- optionally override noun used to refer to source in error messages,
- defaults to ``value``; mainly useful to make caller's error
+ Optional noun used to refer to identify source parameter in error messages
+ (Defaults to the string ``"value"``). This is mainly useful to make the caller's error
messages make more sense.
:raises ValueError:
@@ -341,8 +353,9 @@ def saslprep(source, param="value"):
.. note::
- Due to a missing :mod:`!stringprep` module, this feature
- is not available on Jython.
+ 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>`_).
"""
# saslprep - http://tools.ietf.org/html/rfc4013
# stringprep - http://tools.ietf.org/html/rfc3454
@@ -448,8 +461,8 @@ def render_bytes(source, *args):
This function is motivated by the fact that
:class:`bytes` instances do not support ``%`` or ``{}`` formatting under Python 3.
This function is an attempt to provide a replacement:
- it converts everything to unicode (decode bytes instances as latin-1),
- performs the required formatting, then encodes the result to latin-1.
+ it converts everything to unicode (decoding bytes instances as ``latin-1``),
+ performs the required formatting, then encodes the result to ``latin-1``.
Calling ``render_bytes(source, *args)`` should function roughly the same as
``source % args`` under Python 2.
@@ -1518,7 +1531,7 @@ def getrandstr(rng, charset, count):
_52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'
def generate_password(size=10, charset=_52charset):
- """generate random password using given length & chars
+ """generate random password using given length & charset
:param size:
size of password.
@@ -1530,7 +1543,12 @@ def generate_password(size=10, charset=_52charset):
except for the characters ``1IiLl0OoS5``, which were omitted
due to their visual similarity.
- :returns: randomly generated password.
+ :returns: :class:`!str` containing randomly generated password.
+
+ .. note::
+
+ Using the default character set, on a OS with :class:`!SystemRandom` support,
+ this function should generate passwords with 5.7 bits of entropy per character.
"""
return getrandstr(rng, charset, size)
diff --git a/passlib/utils/des.py b/passlib/utils/des.py
index 989e38a..def894d 100644
--- a/passlib/utils/des.py
+++ b/passlib/utils/des.py
@@ -665,15 +665,15 @@ def des_encrypt_block(key, input, salt=0, rounds=1):
plaintext block to encrypt, as 8 byte string.
:arg salt:
- optional 24-bit integer used to mutate the base DES algorithm in a
- manner specific to :class:`~passlib.hash.des_crypt` and it's variants:
-
- for each bit ``i`` which is set in the salt value,
+ Optional 24-bit integer used to mutate the base DES algorithm in a
+ manner specific to :class:`~passlib.hash.des_crypt` and it's variants.
+ The default value ``0`` provides the normal (unsalted) DES behavior.
+ The salt functions as follows:
+ if the ``i``'th bit of ``salt`` is set,
bits ``i`` and ``i+24`` are swapped in the DES E-box output.
- the default (``salt=0``) provides the normal DES behavior.
:arg rounds:
- optional number of rounds of to apply the DES key schedule.
+ Optional number of rounds of to apply the DES key schedule.
the default (``rounds=1``) provides the normal DES behavior,
but :class:`~passlib.hash.des_crypt` and it's variants use
alternate rounds values.
diff --git a/setup.py b/setup.py
index ff0b901..408a62a 100644
--- a/setup.py
+++ b/setup.py
@@ -90,7 +90,7 @@ providing full-strength password hashing for multi-user application.
* See the `online documentation <http://packages.python.org/passlib>`_
for details, installation instructions, and examples.
-* See the `passlib homepage <http://passlib.googlecode.com>`_
+* See the `Passlib homepage <http://passlib.googlecode.com>`_
for the latest news, more information, and additional downloads.
* See the `changelog <http://packages.python.org/passlib/history.html>`_
diff --git a/tox.ini b/tox.ini
index b733e01..dc6f284 100644
--- a/tox.ini
+++ b/tox.ini
@@ -99,7 +99,7 @@ basepython = pypy1.8
#===========================================================================
# Jython - no special directives, currently same as py25
#===========================================================================
-
+
#===========================================================================
# Google App Engine integration
#===========================================================================