summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-16 16:13:52 -0500
committerEli Collins <elic@assurancetechnologies.com>2011-02-16 16:13:52 -0500
commit4cfd0bb3647b25ee1a7a3f54d636ef72244f7fcb (patch)
tree92c9184188e2cc64e2c80274076ca4bebe70742b
parent3eefcd1f31c534cced89139ebf72d36ceafc3a1c (diff)
downloadpasslib-4cfd0bb3647b25ee1a7a3f54d636ef72244f7fcb.tar.gz
large rearrangment of structure
=============================== * moved passlib.hash package to passlib.drivers * combined some passlib.driver modules together (eg ext_des_crypt merged w/ des_crypt) * renamed all hash classes to lower case, to match name attrs. * renamed ext_des_crypt to bsdi_crypt * added special proxy module "passlib.hash" which lazily loads drivers from correct location - registry system reworked, has list of locations for builtin drivers, and capability for apps to add more - passlib.hash *is* the registry, changes to it affect list - thanks to this, documentation can remain, pointing to passlib.hash.xxx as location for driver * moved passlib.utils.handlers to passlib.utils.drivers, renamed classes from XxxHandler -> XxxHash * combined all driver tests into single passlib/tests/test_drivers.py file * NOTE: disabled default unicode testing, not ready for that yet * all driver UTs pass (others not checked)
-rw-r--r--docs/copyright.rst4
-rw-r--r--docs/install.rst2
-rw-r--r--docs/lib/passlib.hash.apr_md5_crypt.rst8
-rw-r--r--docs/lib/passlib.hash.bcrypt.rst6
-rw-r--r--docs/lib/passlib.hash.des_crypt.rst6
-rw-r--r--docs/lib/passlib.hash.ext_des_crypt.rst8
-rw-r--r--docs/lib/passlib.hash.md5_crypt.rst8
-rw-r--r--docs/lib/passlib.hash.mysql_323.rst8
-rw-r--r--docs/lib/passlib.hash.mysql_41.rst8
-rw-r--r--docs/lib/passlib.hash.nthash.rst4
-rw-r--r--docs/lib/passlib.hash.phpass.rst4
-rw-r--r--docs/lib/passlib.hash.postgres_md5.rst6
-rw-r--r--docs/lib/passlib.hash.rst34
-rw-r--r--docs/lib/passlib.hash.sha1_crypt.rst8
-rw-r--r--docs/lib/passlib.hash.sha256_crypt.rst8
-rw-r--r--docs/lib/passlib.hash.sha512_crypt.rst6
-rw-r--r--docs/lib/passlib.hash.sun_md5_crypt.rst12
-rw-r--r--docs/lib/passlib.sqldb.rst8
-rw-r--r--docs/lib/passlib.unix.rst38
-rw-r--r--docs/lib/passlib.utils.des.rst2
-rw-r--r--docs/lib/passlib.utils.rst2
-rw-r--r--docs/password_hash_api.rst4
-rw-r--r--passlib/__init__.py9
-rw-r--r--passlib/apache.py2
-rw-r--r--passlib/base.py164
-rw-r--r--passlib/drivers/__init__.py2
-rw-r--r--passlib/drivers/bcrypt.py12
-rw-r--r--passlib/drivers/des_crypt.py20
-rw-r--r--passlib/drivers/ext_des_crypt.py131
-rw-r--r--passlib/drivers/md5_crypt.py18
-rw-r--r--passlib/drivers/mysql.py26
-rw-r--r--passlib/drivers/nthash.py10
-rw-r--r--passlib/drivers/phpass.py16
-rw-r--r--passlib/drivers/postgres.py40
-rw-r--r--passlib/drivers/sha1_crypt.py10
-rw-r--r--passlib/drivers/sha2_crypt.py19
-rw-r--r--passlib/drivers/sun_md5_crypt.py16
-rw-r--r--passlib/sqldb.py50
-rw-r--r--passlib/tests/handler_utils.py355
-rw-r--r--passlib/tests/test_base.py (renamed from passlib/tests/test_context.py)11
-rw-r--r--passlib/tests/test_drivers.py422
-rw-r--r--passlib/tests/test_frontend.py5
-rwxr-xr-xpasslib/tests/test_hash_bcrypt.py167
-rw-r--r--passlib/tests/test_hash_des_crypt.py74
-rw-r--r--passlib/tests/test_hash_md5_crypt.py57
-rw-r--r--passlib/tests/test_hash_misc.py91
-rw-r--r--passlib/tests/test_hash_mysql.py51
-rw-r--r--passlib/tests/test_hash_postgres.py71
-rw-r--r--passlib/tests/test_hash_sha_crypt.py131
-rw-r--r--passlib/tests/test_hash_sun_md5_crypt.py17
-rw-r--r--passlib/tests/test_utils.py37
-rw-r--r--passlib/tests/test_utils_drivers.py (renamed from passlib/tests/test_handler.py)14
-rw-r--r--passlib/tests/test_utils_pbkdf2.py6
-rw-r--r--passlib/tests/utils.py387
-rw-r--r--passlib/unix.py4
-rw-r--r--passlib/utils/_slow_bcrypt.py2
-rw-r--r--passlib/utils/drivers.py (renamed from passlib/utils/handlers.py)56
-rw-r--r--passlib/win32.py6
58 files changed, 1202 insertions, 1501 deletions
diff --git a/docs/copyright.rst b/docs/copyright.rst
index 354574c..150bbf4 100644
--- a/docs/copyright.rst
+++ b/docs/copyright.rst
@@ -46,7 +46,7 @@ implementation of OpenBSD's BCrypt algorithm, written by Damien Miller,
and released under a BSD license.
:mod:`passlib.utils._slow_bcrypt` is a python translation of this code,
-which is used as a fallback backend for :mod:`passlib.hash.bCrypt`
+which is used as a fallback backend for :mod:`passlib.drivers.bCrypt`
when the external python library `py-bcrypt <http://www.mindrot.org/projects/py-bcrypt/>`_
is not installed.
@@ -68,7 +68,7 @@ This is the license and copyright for jBCrypt::
MD5-Crypt
---------
-The fallback pure-python implementation contained in :mod:`passlib.hash.md5_crypt`
+The fallback pure-python implementation contained in :mod:`passlib.drivers.md5_crypt`
was derived from the
`FreeBSD md5-crypt <http://www.freebsd.org/cgi/cvsweb.cgi/~checkout~/src/lib/libcrypt/crypt.c?rev=1.2>`_,
implementation which was released under the following license::
diff --git a/docs/install.rst b/docs/install.rst
index f6ba0c6..e6b8fa2 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -17,7 +17,7 @@ The following libraries are not required, but will be used if found:
* If installed, `py-bcrypt <http://www.mindrot.org/projects/py-bcrypt/>`_ will be
used instead of PassLib's slower pure-python bcrypt implementation.
- (see :mod:`passlib.hash.bcrypt`).
+ (see :mod:`passlib.drivers.bcrypt`).
*This is strongly recommended, as the builtin implementation is VERY slow*.
* stdlib ``crypt.crypt()`` will be used if present, and if the underlying
diff --git a/docs/lib/passlib.hash.apr_md5_crypt.rst b/docs/lib/passlib.hash.apr_md5_crypt.rst
index 017ff31..5173d5e 100644
--- a/docs/lib/passlib.hash.apr_md5_crypt.rst
+++ b/docs/lib/passlib.hash.apr_md5_crypt.rst
@@ -1,11 +1,11 @@
==================================================================
-:mod:`passlib.hash.apr_md5_crypt` - Apache MD5-Crypt password hash
+:mod:`passlib.drivers.apr_md5_crypt` - Apache MD5-Crypt password hash
==================================================================
-.. module:: passlib.hash.apr_md5_crypt
+.. module:: passlib.drivers.apr_md5_crypt
:synopsis: Apache MD5-Crypt variant
-This format is a variation of :mod:`~passlib.hash.md5_crypt`,
+This format is a variation of :mod:`~passlib.drivers.md5_crypt`,
primarily used by the Apache webserver in ``htpasswd`` files.
It contains only minor changes to md5-crypt, and should
be considered just as strong / weak as md5-crypt itself.
@@ -19,7 +19,7 @@ it's internal hash calculation. Thus, hashes generated
by this and md5-crypt are in no way compatible with eachother
(they will not even have the same checksum for the same salt).
-For details about usage & algorithm, see :mod:`~passlib.hash.md5_crypt`.
+For details about usage & algorithm, see :mod:`~passlib.drivers.md5_crypt`.
References
==========
diff --git a/docs/lib/passlib.hash.bcrypt.rst b/docs/lib/passlib.hash.bcrypt.rst
index 32842fc..8b7cc5a 100644
--- a/docs/lib/passlib.hash.bcrypt.rst
+++ b/docs/lib/passlib.hash.bcrypt.rst
@@ -1,11 +1,11 @@
==================================================================
-:mod:`passlib.hash.bcrypt` - BCrypt
+:mod:`passlib.drivers.bcrypt` - BCrypt
==================================================================
-.. module:: passlib.hash.bcrypt
+.. module:: passlib.drivers.bcrypt
:synopsis: BCrypt
-BCrypt was developed to replace :mod:`~passlib.hash.md5_crypt` for BSD systems.
+BCrypt was developed to replace :mod:`~passlib.drivers.md5_crypt` for BSD systems.
It uses a modified version of the Blowfish stream cipher. Featuring
a large salt and variable number of rounds, it's currently the default
password hash for many systems (notably BSD), and has no known weaknesses.
diff --git a/docs/lib/passlib.hash.des_crypt.rst b/docs/lib/passlib.hash.des_crypt.rst
index c5e0eb3..6447492 100644
--- a/docs/lib/passlib.hash.des_crypt.rst
+++ b/docs/lib/passlib.hash.des_crypt.rst
@@ -1,8 +1,8 @@
=======================================================================
-:mod:`passlib.hash.des_crypt` - Tradtional Unix (DES) Crypt
+:mod:`passlib.drivers.des_crypt` - Tradtional Unix (DES) Crypt
=======================================================================
-.. module:: passlib.hash.des_crypt
+.. module:: passlib.drivers.des_crypt
:synopsis: Traditional Unix (DES) Crypt
.. warning::
@@ -20,7 +20,7 @@ Usage
=====
This module can be used directly as follows::
- >>> from passlib.hash import des_crypt as dc
+ >>> from passlib.drivers.import des_crypt as dc
>>> dc.encrypt("password") #generate new salt, encrypt password
'JQMuyS6H.AGMo'
diff --git a/docs/lib/passlib.hash.ext_des_crypt.rst b/docs/lib/passlib.hash.ext_des_crypt.rst
index d0c2676..8e7fbe5 100644
--- a/docs/lib/passlib.hash.ext_des_crypt.rst
+++ b/docs/lib/passlib.hash.ext_des_crypt.rst
@@ -1,12 +1,12 @@
=================================================================================
-:mod:`passlib.hash.ext_des_crypt` - BSDi (Extended DES) Crypt
+:mod:`passlib.drivers.ext_des_crypt` - BSDi (Extended DES) Crypt
=================================================================================
-.. module:: passlib.hash.ext_des_crypt
+.. module:: passlib.drivers.ext_des_crypt
:synopsis: BSDi (Extended DES) Crypt
This algorithm was developed by BSDi for their BSD/OS distribution.
-It's based on :mod:`~passlib.hash.des_crypt`, and contains a larger
+It's based on :mod:`~passlib.drivers.des_crypt`, and contains a larger
salt and a variable number of rounds. Nonetheless, since it's based on DES,
and still shares many of des-crypt's other flaws,
it should not be used in new applications.
@@ -14,7 +14,7 @@ it should not be used in new applications.
Usage
=====
Aside from differences in format and salt size,
-ext-des-crypt usage is exactly the same as :mod:`~passlib.hash.des_crypt`.
+ext-des-crypt usage is exactly the same as :mod:`~passlib.drivers.des_crypt`.
.. todo::
diff --git a/docs/lib/passlib.hash.md5_crypt.rst b/docs/lib/passlib.hash.md5_crypt.rst
index e8235a7..6458851 100644
--- a/docs/lib/passlib.hash.md5_crypt.rst
+++ b/docs/lib/passlib.hash.md5_crypt.rst
@@ -1,8 +1,8 @@
==================================================================
-:mod:`passlib.hash.md5_crypt` - MD5 Crypt
+:mod:`passlib.drivers.md5_crypt` - MD5 Crypt
==================================================================
-.. module:: passlib.hash.md5_crypt
+.. module:: passlib.drivers.md5_crypt
:synopsis: MD5 Crypt
This algorithm was developed to replace the aging des-crypt.
@@ -13,13 +13,13 @@ algorithm which it's based around is considered broken,
though pre-image attacks are currently only theoretical.
Despite this, MD5-Crypt itself is not considered broken,
and is still considered ok to use, though new applications
-should use a stronger scheme (eg :mod:`~passlib.hash.sha512_crypt`) if possible.
+should use a stronger scheme (eg :mod:`~passlib.drivers.sha512_crypt`) if possible.
Usage
=====
This module can be used directly as follows::
- >>> from passlib.hash import md5_crypt as mc
+ >>> from passlib.drivers.import md5_crypt as mc
>>> mc.encrypt("password") #generate new salt, encrypt password
'$1$3azHgidD$SrJPt7B.9rekpmwJwtON31'
diff --git a/docs/lib/passlib.hash.mysql_323.rst b/docs/lib/passlib.hash.mysql_323.rst
index b81acf4..962d780 100644
--- a/docs/lib/passlib.hash.mysql_323.rst
+++ b/docs/lib/passlib.hash.mysql_323.rst
@@ -1,8 +1,8 @@
========================================================================
-:mod:`passlib.hash.mysql_323` - MySQL 3.2.3 password hash
+:mod:`passlib.drivers.mysql_323` - MySQL 3.2.3 password hash
========================================================================
-.. module:: passlib.hash.mysql_323
+.. module:: passlib.drivers.mysql_323
:synopsis: MySQL 3.2.3 password hash
.. warning::
@@ -15,7 +15,7 @@ This module implements the first of MySQL's password hash functions,
used to store it's user account passwords. Introduced in MySQL 3.2.3
under the function ``PASSWORD()``, this function was renamed
to ``OLD_PASSWORD()`` under MySQL 4.1, when a newer password
-hash algorithm was introduced (see :mod:`~passlib.hash.mysql_41`).
+hash algorithm was introduced (see :mod:`~passlib.drivers.mysql_41`).
Lacking any sort of salt, it's simplistic algorithm amounts to
little more than a checksum, and should not be used for *any*
purpose but verifying existing MySQL 3.2.3 - 4.0 password hashes.
@@ -26,7 +26,7 @@ Users will most likely find the frontends provided by :mod:`passlib.sqldb`
to be more useful than accessing this module directly.
That aside, this module can be used directly as follows::
- >>> from passlib.hash import mysql_323 as mold
+ >>> from passlib.drivers.import mysql_323 as mold
>>> mold.encrypt("password") #encrypt password
'5d2e19393cc5ef67'
diff --git a/docs/lib/passlib.hash.mysql_41.rst b/docs/lib/passlib.hash.mysql_41.rst
index 85a0b71..894a700 100644
--- a/docs/lib/passlib.hash.mysql_41.rst
+++ b/docs/lib/passlib.hash.mysql_41.rst
@@ -1,8 +1,8 @@
=====================================================================
-:mod:`passlib.hash.mysql_41` - MySQL 4.1 password hash
+:mod:`passlib.drivers.mysql_41` - MySQL 4.1 password hash
=====================================================================
-.. module:: passlib.hash.mysql_41
+.. module:: passlib.drivers.mysql_41
:synopsis: MySQL 4.1 password hash
.. warning::
@@ -14,7 +14,7 @@
This module implements the second of MySQL's password hash functions,
used to store it's user account passwords. Introduced in MySQL 4.1.1
under the function ``PASSWORD()``, it replaced the previous
-algorithm (:mod:`~passlib.hash.mysql_323`) as the default
+algorithm (:mod:`~passlib.drivers.mysql_323`) as the default
used by MySQL, and is still in active use under MySQL 5.
Lacking any sort of salt, and using only 2 rounds
of the common SHA1 message digest, it's not very secure,
@@ -26,7 +26,7 @@ Usage
Users will most likely find the frontends provided by :mod:`passlib.sqldb`
to be more useful than accessing this module directly.
That aside, this module can be used directly in the same manner
-as :mod:`~passlib.hash.mysql_323`.
+as :mod:`~passlib.drivers.mysql_323`.
Functions
=========
diff --git a/docs/lib/passlib.hash.nthash.rst b/docs/lib/passlib.hash.nthash.rst
index bb35415..e346cec 100644
--- a/docs/lib/passlib.hash.nthash.rst
+++ b/docs/lib/passlib.hash.nthash.rst
@@ -1,8 +1,8 @@
==================================================================
-:mod:`passlib.hash.nthash` - Windows NT-HASH for Unix
+:mod:`passlib.drivers.nthash` - Windows NT-HASH for Unix
==================================================================
-.. module:: passlib.hash.nthash
+.. module:: passlib.drivers.nthash
:synopsis: Windows NT-HASH for Unix
.. warning::
diff --git a/docs/lib/passlib.hash.phpass.rst b/docs/lib/passlib.hash.phpass.rst
index 01e2690..6a8b291 100644
--- a/docs/lib/passlib.hash.phpass.rst
+++ b/docs/lib/passlib.hash.phpass.rst
@@ -1,8 +1,8 @@
==================================================================
-:mod:`passlib.hash.phpass` - PHPass Portable Hash
+:mod:`passlib.drivers.phpass` - PHPass Portable Hash
==================================================================
-.. module:: passlib.hash.phpass
+.. module:: passlib.drivers.phpass
:synopsis: PHPass Portable Hash
This algorithm is used primarily by PHP software
diff --git a/docs/lib/passlib.hash.postgres_md5.rst b/docs/lib/passlib.hash.postgres_md5.rst
index bbb6d9c..f3a80bb 100644
--- a/docs/lib/passlib.hash.postgres_md5.rst
+++ b/docs/lib/passlib.hash.postgres_md5.rst
@@ -1,8 +1,8 @@
==================================================================
-:mod:`passlib.hash.postgres_md5` - PostgreSQL MD5 password hash
+:mod:`passlib.drivers.postgres_md5` - PostgreSQL MD5 password hash
==================================================================
-.. module:: passlib.hash.postgres_md5
+.. module:: passlib.drivers.postgres_md5
:synopsis: PostgreSQL MD5 password hash
.. warning::
@@ -24,7 +24,7 @@ Users will most likely find the frontend provided by :mod:`passlib.sqldb`
to be more useful than accessing this module directly.
That aside, this module can be used directly as follows::
- >>> from passlib.hash import postgres_md5 as pm
+ >>> from passlib.drivers.import postgres_md5 as pm
>>> pm.encrypt("password", "username") #encrypt password using specified username
'md55a231fcdb710d73268c4f44283487ba2'
diff --git a/docs/lib/passlib.hash.rst b/docs/lib/passlib.hash.rst
index 74bc40e..0a78718 100644
--- a/docs/lib/passlib.hash.rst
+++ b/docs/lib/passlib.hash.rst
@@ -1,5 +1,5 @@
============================================
-:mod:`passlib.hash` - Password Hash Schemes
+:mod:`passlib.drivers. - Password Hash Schemes
============================================
.. module:: passlib.hash
@@ -15,7 +15,7 @@ While many applications may find it easier to use a :class:`CryptContext`
instance, or retreive handlers via :func:`get_crypt_handler`, they can
also be imported and used directly from this package:
- >>> from passlib.hash import md5_crypt
+ >>> from passlib.drivers.import md5_crypt
>>> hash = md5_crypt.encrypt("password")
Passlib contains the following builtin password algorithms:
@@ -29,24 +29,24 @@ the :ref:`modular crypt format <modular-crypt-format>`.
.. toctree::
:maxdepth: 1
- passlib.hash.des_crypt
- passlib.hash.ext_des_crypt
- passlib.hash.md5_crypt
- passlib.hash.bcrypt
- passlib.hash.sha1_crypt
- passlib.hash.sha256_crypt
- passlib.hash.sha512_crypt
+ passlib.drivers.des_crypt
+ passlib.drivers.ext_des_crypt
+ passlib.drivers.md5_crypt
+ passlib.drivers.bcrypt
+ passlib.drivers.sha1_crypt
+ passlib.drivers.sha256_crypt
+ passlib.drivers.sha512_crypt
.. toctree::
:hidden:
- passlib.hash.sun_md5_crypt
+ passlib.drivers.sun_md5_crypt
.. todo::
These aren't fully implemented / tested yet:
- * :mod:`~passlib.hash.sun_md5_crypt` - MD5-based scheme used by Solaris 10 (NOT related to md5-crypt above).
+ * :mod:`~passlib.drivers.sun_md5_crypt` - MD5-based scheme used by Solaris 10 (NOT related to md5-crypt above).
Non-Standard Unix-Compatible Schemes
------------------------------------
@@ -58,9 +58,9 @@ the modular crypt format.
.. toctree::
:maxdepth: 1
- passlib.hash.apr_md5_crypt
- passlib.hash.phpass
- passlib.hash.nthash
+ passlib.drivers.apr_md5_crypt
+ passlib.drivers.phpass
+ passlib.drivers.nthash
Other Schemes
-------------
@@ -71,6 +71,6 @@ not seen outside those specific contexts:
.. toctree::
:maxdepth: 1
- passlib.hash.mysql_323
- passlib.hash.mysql_41
- passlib.hash.postgres_md5
+ passlib.drivers.mysql_323
+ passlib.drivers.mysql_41
+ passlib.drivers.postgres_md5
diff --git a/docs/lib/passlib.hash.sha1_crypt.rst b/docs/lib/passlib.hash.sha1_crypt.rst
index e9b01b5..3f753e7 100644
--- a/docs/lib/passlib.hash.sha1_crypt.rst
+++ b/docs/lib/passlib.hash.sha1_crypt.rst
@@ -1,8 +1,8 @@
===================================================================
-:mod:`passlib.hash.sha1_crypt` - SHA-1 Crypt
+:mod:`passlib.drivers.sha1_crypt` - SHA-1 Crypt
===================================================================
-.. module:: passlib.hash.sha1_crypt
+.. module:: passlib.drivers.sha1_crypt
:synopsis: SHA-1 Crypt
SHA1-Crypt is a hash algorithm introduced by NetBSD in 2004.
@@ -12,7 +12,7 @@ and supports a large salt and variable number of rounds.
Usage
=====
Supporting a variable sized salt and variable number of rounds,
-this scheme is used in exactly the same way as :mod:`~passlib.hash.sha512_crypt`.
+this scheme is used in exactly the same way as :mod:`~passlib.drivers.sha512_crypt`.
Functions
=========
@@ -54,7 +54,7 @@ as well as providing some of the advancements made in PDKDF2).
* the checksum is then rendered into hash-64 format
using an ordering that roughly corresponds to big-endian
- encoding of 24-bit chunks (see :data:`passlib.hash.sha1_crypt._chk_offsets` for exact byte order).
+ encoding of 24-bit chunks (see :data:`passlib.drivers.sha1_crypt._chk_offsets` for exact byte order).
Deviations
==========
diff --git a/docs/lib/passlib.hash.sha256_crypt.rst b/docs/lib/passlib.hash.sha256_crypt.rst
index d709c84..064d4ff 100644
--- a/docs/lib/passlib.hash.sha256_crypt.rst
+++ b/docs/lib/passlib.hash.sha256_crypt.rst
@@ -1,11 +1,11 @@
==================================================================
-:mod:`passlib.hash.sha256_crypt` - SHA-256 Crypt
+:mod:`passlib.drivers.sha256_crypt` - SHA-256 Crypt
==================================================================
-.. module:: passlib.hash.sha526_crypt
+.. module:: passlib.drivers.sha526_crypt
:synopsis: SHA-256 Crypt
-This scheme is identical to :mod:`~passlib.hash.sha512_crypt` in almost every way,
+This scheme is identical to :mod:`~passlib.drivers.sha512_crypt` in almost every way,
they are defined by the same specification and have the same design and structure,
except the following differences:
@@ -13,4 +13,4 @@ except the following differences:
* it uses SHA-256 as it's internal hash function instead of SHA-512.
* it's output hash is correspondingly smaller.
-For details about this module, see :mod:`~passlib.hash.sha512_crypt`.
+For details about this module, see :mod:`~passlib.drivers.sha512_crypt`.
diff --git a/docs/lib/passlib.hash.sha512_crypt.rst b/docs/lib/passlib.hash.sha512_crypt.rst
index cdf4e23..eb1b77a 100644
--- a/docs/lib/passlib.hash.sha512_crypt.rst
+++ b/docs/lib/passlib.hash.sha512_crypt.rst
@@ -1,12 +1,12 @@
===================================================================
-:mod:`passlib.hash.sha512_crypt` - SHA-512 Crypt
+:mod:`passlib.drivers.sha512_crypt` - SHA-512 Crypt
===================================================================
-.. module:: passlib.hash.sha512_crypt
+.. module:: passlib.drivers.sha512_crypt
:synopsis: SHA-512 Crypt
SHA-512 Crypt and SHA-256 Crypt were developed as a response
-to :mod:`~passlib.hash.bcrypt`. They are descendants of :mod:`~passlib.hash.md5_crypt`,
+to :mod:`~passlib.drivers.bcrypt`. They are descendants of :mod:`~passlib.drivers.md5_crypt`,
and incorporate many changes: replaced MD5 with newer message digest algorithms,
some internal cleanups in MD5-Crypt's rounds algorithm,
and the introduction of a variable rounds parameter.
diff --git a/docs/lib/passlib.hash.sun_md5_crypt.rst b/docs/lib/passlib.hash.sun_md5_crypt.rst
index 393c9c2..5703988 100644
--- a/docs/lib/passlib.hash.sun_md5_crypt.rst
+++ b/docs/lib/passlib.hash.sun_md5_crypt.rst
@@ -1,8 +1,8 @@
===============================================================
-:mod:`passlib.hash.sun_md5_crypt` - Sun MD5 Crypt password hash
+:mod:`passlib.drivers.sun_md5_crypt` - Sun MD5 Crypt password hash
===============================================================
-.. module:: passlib.hash.sun_md5_crypt
+.. module:: passlib.drivers.sun_md5_crypt
:synopsis: Sun MD5 Crypt
.. warning::
@@ -16,7 +16,7 @@
This algorithm is used by Solaris, as a replacement for the aging des-crypt.
It is mainly used on later versions of Solaris, and is not found many other
places. While based on the MD5 message digest, it has very little at all
-in common with the :mod:`~passlib.hash.md5_crypt` algorithm. It supports
+in common with the :mod:`~passlib.drivers.md5_crypt` algorithm. It supports
32 bit variable rounds and an 8 character salt. Due to a theoretic pre-image
attacks on the MD5 message digest, this algorithm should probably not
be used in new deploys.
@@ -25,7 +25,7 @@ Usage
=====
This module supports both rounds and salts,
and so can be used in the exact same manner
-as :mod:`~passlib.hash.sha512_crypt`.
+as :mod:`~passlib.drivers.sha512_crypt`.
Functions
=========
@@ -73,8 +73,8 @@ by one of the creators). Given a password, the number of rounds, and a salt...
data string, along with the current iteration number as an ascii string.
- if a 0, the same as 1, except that magic constant data is not included.
-* The final checksum is then encoded into :mod:`hash64 <~passlib.hash.h64>` using the same
- transposed byte order that :mod:`~passlib.hash.md5_crypt` uses.
+* The final checksum is then encoded into :mod:`hash64 <~passlib.drivers.h64>` using the same
+ transposed byte order that :mod:`~passlib.drivers.md5_crypt` uses.
The constant data string is referenced above is a 1517 byte ascii string... an excerpt from Hamlet,
starting with ``To be, or not to be...`` and ending with ``...all my sins remember'd.\n``,
diff --git a/docs/lib/passlib.sqldb.rst b/docs/lib/passlib.sqldb.rst
index 0520f30..5422889 100644
--- a/docs/lib/passlib.sqldb.rst
+++ b/docs/lib/passlib.sqldb.rst
@@ -13,7 +13,7 @@ which should be capable of recognizing passwords in modern postgres systems:
.. object:: postgres_context
This object should recognize password hashes stores in postgres' pg_shadow table.
- it can recognize :mod:`~passlib.hash.postgres_md5` hashes,
+ it can recognize :mod:`~passlib.drivers.postgres_md5` hashes,
as well as plaintext hashes.
It defaults to postgres_md5 when generating new hashes.
@@ -26,11 +26,11 @@ for handling MySQL user passwords:
.. object:: mysql_context
- This object should recognize the new :mod:`~passlib.hash.mysql_41` hashes,
- as well as any legacy :mod:`~passlib.hash.mysql_323` hashes.
+ This object should recognize the new :mod:`~passlib.drivers.mysql_41` hashes,
+ as well as any legacy :mod:`~passlib.drivers.mysql_323` hashes.
It defaults to mysql_41 when generating new hashes.
.. object:: mysql3_context
This object is for use with older MySQL deploys which only recognize
- the :mod:`~passlib.hash.mysql_323` hash.
+ the :mod:`~passlib.drivers.mysql_323` hash.
diff --git a/docs/lib/passlib.unix.rst b/docs/lib/passlib.unix.rst
index 4e59714..637db43 100644
--- a/docs/lib/passlib.unix.rst
+++ b/docs/lib/passlib.unix.rst
@@ -13,19 +13,19 @@ tailor to the hashes supported on various unix systems.
.. object:: linux_context
this should recognize the hashes used on most linux systems:
- :mod:`~passlib.hash.des_crypt`,
- :mod:`~passlib.hash.md5_crypt`,
- :mod:`~passlib.hash.sha256_crypt`, and
- :mod:`~passlib.hash.sha512_crypt` (used as the default).
+ :mod:`~passlib.drivers.des_crypt`,
+ :mod:`~passlib.drivers.md5_crypt`,
+ :mod:`~passlib.drivers.sha256_crypt`, and
+ :mod:`~passlib.drivers.sha512_crypt` (used as the default).
.. object:: bsd_context
this should recognize the hashes used on most bsd systems:
- :mod:`~passlib.hash.des_crypt`,
- :mod:`~passlib.hash.ext_des_crypt`,
- :mod:`~passlib.hash.nthash`,
- :mod:`~passlib.hash.md5_crypt`,
- :mod:`~passlib.hash.bcrypt` (used as the default).
+ :mod:`~passlib.drivers.des_crypt`,
+ :mod:`~passlib.drivers.ext_des_crypt`,
+ :mod:`~passlib.drivers.nthash`,
+ :mod:`~passlib.drivers.md5_crypt`,
+ :mod:`~passlib.drivers.bcrypt` (used as the default).
.. note::
@@ -47,7 +47,7 @@ Usage
Modular Crypt Format
====================
A vast majority of the schemes used on unix systems (and supported by this library)
-follow the "Modular Crypt Format", introduced around the time :mod:`~passlib.hash.md5_crypt` was developed.
+follow the "Modular Crypt Format", introduced around the time :mod:`~passlib.drivers.md5_crypt` was developed.
This scheme allows hashes generates by multiple schemes to co-exist within a database,
by requiring that all hash string begin with a unique prefix ``$identifier$``;
where ``identifier`` is a short alphanumeric string globally identifying
@@ -63,7 +63,7 @@ In fact, for the most part they avoid using any characters except
this can be violated on some systems if the user intervenes.
.. note::
- :mod:`passlib.hash.des_crypt` and :mod:`passlib.hash.ext_des_crypt`
+ :mod:`passlib.drivers.des_crypt` and :mod:`passlib.drivers.ext_des_crypt`
do not follow this protocol, since they predate it by many years.
OS Format Support
@@ -74,12 +74,12 @@ are known to support which schemes:
=================================== =========== =========== =========== ===========
Scheme Linux FreeBSD NetBSD OpenBSD
=================================== =========== =========== =========== ===========
-:mod:`~passlib.hash.nthash` y
-:mod:`~passlib.hash.des_crypt` y y y y
-:mod:`~passlib.hash.ext_des_crypt` y y
-:mod:`~passlib.hash.md5_crypt` y y y y
-:mod:`~passlib.hash.bcrypt` y y y
-:mod:`~passlib.hash.sha1_crypt` y
-:mod:`~passlib.hash.sha256_crypt` y
-:mod:`~passlib.hash.sha512_crypt` y
+:mod:`~passlib.drivers.nthash` y
+:mod:`~passlib.drivers.des_crypt` y y y y
+:mod:`~passlib.drivers.ext_des_crypt` y y
+:mod:`~passlib.drivers.md5_crypt` y y y y
+:mod:`~passlib.drivers.bcrypt` y y y
+:mod:`~passlib.drivers.sha1_crypt` y
+:mod:`~passlib.drivers.sha256_crypt` y
+:mod:`~passlib.drivers.sha512_crypt` y
=================================== =========== =========== =========== ===========
diff --git a/docs/lib/passlib.utils.des.rst b/docs/lib/passlib.utils.des.rst
index 337e4d1..11d6f76 100644
--- a/docs/lib/passlib.utils.des.rst
+++ b/docs/lib/passlib.utils.des.rst
@@ -15,7 +15,7 @@ This module contains routines for encrypting blocks of data using the DES algori
They do not support multi-block operation or decryption,
since they are designed for use in password hash algorithms
-such as :mod:`~passlib.hash.des_crypt` and :mod:`~passlib.hash.ext_des_crypt`.
+such as :mod:`~passlib.drivers.des_crypt` and :mod:`~passlib.drivers.ext_des_crypt`.
.. autofunction:: expand_des_key
.. autofunction:: des_encrypt_block
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst
index 5d7d067..0465310 100644
--- a/docs/lib/passlib.utils.rst
+++ b/docs/lib/passlib.utils.rst
@@ -59,4 +59,4 @@ There are also a few sub modules which provide additional utility functions:
document this module...
- passlib.utils.handlers
+ passlib.utils.drivers
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index 3c86b84..084a669 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -23,7 +23,7 @@ and other parts have been kept intentionally non-commital, in order to allow
flexibility of implementation.
All of the schemes built into passlib implement this interface;
-most them as modules within the :mod:`passlib.hash` package.
+most them as modules within the :mod:`passlib.drivers. package.
Overview
========
@@ -66,7 +66,7 @@ Informational Attributes
All handlers built into passlib are implemented as modules
whose path corresponds to the name, with an underscore replacing the hyphen.
- For example, ``des-crypt`` is stored as the module ``passlib.hash.des_crypt``.
+ For example, ``des-crypt`` is stored as the module ``passlib.drivers.des_crypt``.
.. attribute:: setting_kwds
diff --git a/passlib/__init__.py b/passlib/__init__.py
index 823ebf4..1fd9cd6 100644
--- a/passlib/__init__.py
+++ b/passlib/__init__.py
@@ -3,11 +3,12 @@
__version__ = "1.3"
#=========================================================
-#
+#import special proxy object as 'passlib.hash' module
#=========================================================
-import passlib.base
-schemes = passlib.base.schemes
-##from passlib.base import CryptContext
+from passlib.base import _hashmod as hash
+import sys
+sys.modules['passlib.hash'] = hash
+del sys
#=========================================================
#quickstart interface
diff --git a/passlib/apache.py b/passlib/apache.py
index 9888210..9348f7f 100644
--- a/passlib/apache.py
+++ b/passlib/apache.py
@@ -14,7 +14,7 @@ from __future__ import with_statement
import logging; log = logging.getLogger(__name__)
#site
#libs
-from passlib.hash import postgres_md5
+from passlib.drivers.import postgres_md5
from passlib.base import CryptContext
#pkg
#local
diff --git a/passlib/base.py b/passlib/base.py
index c8ac50b..6566941 100644
--- a/passlib/base.py
+++ b/passlib/base.py
@@ -26,13 +26,14 @@ from warnings import warn
#site
from pkg_resources import resource_string
#libs
-import passlib.hash as _hmod
+##import passlib.drivers.as _hmod
from passlib.utils import Undef, is_crypt_handler, splitcomma, rng
#pkg
#local
__all__ = [
#global registry
'register_crypt_handler',
+ 'register_crypt_location',
'get_crypt_handler',
'list_crypt_handlers'
@@ -42,20 +43,79 @@ __all__ = [
]
#=========================================================
+#proxy object
+#=========================================================
+class PasslibHashProxy(object):
+ """proxy module passlib.hash
+
+ this module is in fact an object which lazy-loads
+ the requested password hash algorithm from wherever it has been stored.
+ it acts as a thin wrapper around :func:`passlib.base.get_crypt_handler`.
+ """
+ __name__ = "passlib.hash"
+ __package__ = None
+
+ def __getattr__(self, attr):
+ if attr.startswith("_"):
+ raise AttributeError, "unknown attribute: %r" % (attr,)
+ handler = get_crypt_handler(attr, None)
+ if handler is None:
+ raise AttributeError, "unknown password hash: %r" % (attr,)
+ setattr(self, attr, handler)
+ return handler
+
+ def __repr__(self):
+ return "<proxy module 'passlib.hash'>"
+
+ def __dir__(self):
+ "report list of all actual attrs, PLUS all known algorithms that haven't been loaded"
+ attrs = set(dir(self.__class__))
+ attrs.update(self.__dict__)
+ attrs.update(_driver_locations)
+ return sorted(attrs)
+
+#NOTE: this is inserted into sys.modules by passlib/__init__.py
+_hashmod = PasslibHashProxy()
+
+#=========================================================
#global registry
#=========================================================
-#list of builtin hashes (for list_crypt_handlers, to work around lazy loading)
-#XXX: could write some code in setup.py that generates this from package listing.
-_builtin_names = set([
- "apr_md5_crypt", "bcrypt", "des_crypt", "ext_des_crypt",
- "md5_crypt", "mysql_323", "mysql_41", "postgres_md5",
- "sha256_crypt", "sha512_crypt", "sun_md5_crypt",
- ])
+#: dict mapping hash names -> loaded driver objects (uses passlib.hash.__dict__ so the two will always be in sync)
+_drivers = _hashmod.__dict__
+
+#: dict mapping hash names -> (module name, None | class name) that should be location of driver
+_driver_locations = {
+ "apr_md5_crypt": ("passlib.drivers.md5_crypt", "apr_md5_crypt"),
+ "bcrypt": ("passlib.drivers.bcrypt", "bcrypt"),
+ "bsdi_crypt": ("passlib.drivers.des_crypt", "bsdi_crypt"),
+ "des_crypt": ("passlib.drivers.des_crypt", "des_crypt"),
+ "md5_crypt": ("passlib.drivers.md5_crypt", "md5_crypt"),
+ "mysql323": ("passlib.drivers.mysql", "mysql323"),
+ "mysql41": ("passlib.drivers.mysql", "mysql41"),
+ "nthash": ("passlib.drivers.nthash", "nthash"),
+ "phpass": ("passlib.drivers.phpass", "phpass"),
+ "postgres_md5": ("passlib.drivers.postgres", "postgres_md5"),
+ "postgres_plaintext":("passlib.drivers.postgres", "postgres_plaintext"),
+ "sha1_crypt": ("passlib.drivers.sha1_crypt", "sha1_crypt"),
+ "sha256_crypt": ("passlib.drivers.sha2_crypt", "sha256_crypt"),
+ "sha512_crypt": ("passlib.drivers.sha2_crypt", "sha512_crypt"),
+ "sun_md5_crypt": ("passlib.drivers.sun_md5_crypt","sun_md5_crypt"),
+}
+
+def register_crypt_location(name, path):
+ "register location to lazy-load driver when requested"
+ global _driver_locations
+ if ':' in path:
+ modname, modattr = path.split(":")
+ else:
+ modname = path
+ modattr = None
+ _driver_locations[name] = (modname, modattr)
def register_crypt_handler(obj, force=False):
"register CryptHandler handler"
- global _hmod
+ global _drivers
#validate obj
if not is_crypt_handler(obj):
@@ -72,7 +132,7 @@ def register_crypt_handler(obj, force=False):
raise ValueError, "invalid characters in name (only underscore, a-z, 0-9 allowed): %r" % (name,)
#check for existing handler
- other = getattr(_hmod, name, None)
+ other = _drivers.get(name)
if other:
if other is obj:
return #already registered
@@ -82,42 +142,62 @@ def register_crypt_handler(obj, force=False):
raise ValueError, "handler already registered for name %r: %r" % (name, other)
#put handler into hash module
- setattr(_hmod, name, obj)
+ _drivers[name] = obj
log.info("registered crypt handler %r: %r", name, obj)
def get_crypt_handler(name, default=Undef):
"resolve crypt algorithm name"
- global _hmod
+ global _drivers, _driver_locations
+
+ #check if handler loaded
+ handler = _drivers.get(name)
+ if handler:
+ return handler
- #normalize name
+ #normalize name (and if changed, check dict again)
alt = name.replace("-","_").lower()
if alt != name:
warn("handler names be lower-case, and use underscores instead of hyphens: %r => %r" % (name, alt))
name = alt
- #check if handler loaded
- handler = getattr(_hmod, name, None)
- if handler:
- return handler
+ #check if handler loaded
+ handler = _drivers.get(name)
+ if handler:
+ return handler
- #try to lazy load from passlib.hash.xxx
- try:
- mod = __import__("passlib.hash." + name, None, None, ['dummy'], 0)
- except ImportError, err:
- #make sure we don't hide failure to import dependancy
- if str(err) != "No module named " + name:
- raise
- else:
- #if importing module registered a class, return it instead of the module.
- handler = getattr(_hmod, name, None)
- if handler and is_crypt_handler(handler):
+ #check if lazy load mapping has been specified for this driver
+ route = _driver_locations.get(name)
+ if route:
+ modname, modattr = route
+
+ #try to load the module - any import errors indicate runtime config,
+ # either missing packages, or bad path provided to register_crypt_location()
+ mod = __import__(modname, None, None, ['dummy'], 0)
+
+ #first check if importing module triggered register_crypt_handler(),
+ #though this is discouraged due to it's magical implicitness
+ handler = _drivers.get(name)
+ if handler:
+ #XXX: issue deprecation warning here?
+ assert is_crypt_handler(handler), "unexpected object: name=%r object=%r" % (name, handler)
return handler
- #<mod> should now be value store in _hmod.name,
- #we call register_crypt_handler() as sanity check to be sure.
- #(will raise ValueError/TypeError if something went wrong)
- register_crypt_handler(mod)
- return mod
+ #if attribute specified, assume *that's* the handler, otherwise assume the module itself.
+ if modattr:
+ handler = getattr(mod, modattr)
+ else:
+ handler = mod
+
+ #XXX: can this ever happen under legitimate circumstances?
+ if handler.name != name:
+ raise RuntimeError, "handler name does not match expected name: %r vs %r" % (handler.name, name)
+
+ #run through register_crypt_handler, to validate it
+ register_crypt_handler(handler)
+
+ return handler
+
+ #TODO: check egg entry points under name "passlib.hash"
#fail!
if default is Undef:
@@ -127,23 +207,7 @@ def get_crypt_handler(name, default=Undef):
def list_crypt_handlers():
"return sorted list of all known crypt algorithm names"
- global _hmod, _builtin_names
- return sorted(_builtin_names.union(x for x in dir(_hmod) if not x.startswith("_")))
-
-#=========================================================
-#proxy object
-#=========================================================
-class PasslibHashProxy(object):
- def __getattr__(self, attr):
- if not attr.startswith("_"):
- handler = get_crypt_handler(attr, None)
- if handler is not None:
- setattr(self, attr, handler)
- return handler
- raise AttributeError, "unknown password hash: %r" % (attr,)
-
-import sys
-sys.modules['passlib.schemes'] = schemes = PasslibHashProxy()
+ return filter(lambda x: not x.startswith("_"), dir(_hashmod))
#=========================================================
#policy
diff --git a/passlib/drivers/__init__.py b/passlib/drivers/__init__.py
index 025b6d2..1ee5ea0 100644
--- a/passlib/drivers/__init__.py
+++ b/passlib/drivers/__init__.py
@@ -1 +1,3 @@
#XXX: make this a namespace package ?
+
+#TODO: bigcrypt, crypt16, raw hex strings for md5/sha1/sha256/sha512
diff --git a/passlib/drivers/bcrypt.py b/passlib/drivers/bcrypt.py
index 4521fec..2fc56a3 100644
--- a/passlib/drivers/bcrypt.py
+++ b/passlib/drivers/bcrypt.py
@@ -21,9 +21,8 @@ try:
except ImportError:
pybcrypt_hashpw = None
#libs
-from passlib.base import register_crypt_handler
from passlib.utils import autodocument, os_crypt
-from passlib.utils.handlers import BackendExtHandler
+from passlib.utils.drivers import BackendExtHash
#TODO: make this a lazy import, generally don't want to load it.
from passlib.utils._slow_bcrypt import raw_bcrypt as slow_raw_bcrypt
@@ -31,13 +30,13 @@ from passlib.utils._slow_bcrypt import raw_bcrypt as slow_raw_bcrypt
#pkg
#local
__all__ = [
- "BCrypt",
+ "bcrypt",
]
#=========================================================
#handler
#=========================================================
-class BCrypt(BackendExtHash):
+class bcrypt(BackendExtHash):
#=========================================================
#class attrs
#=========================================================
@@ -128,7 +127,7 @@ class BCrypt(BackendExtHash):
@classmethod
def set_backend(cls, name=None):
- result = super(BCrypt, cls).set_backend(name)
+ result = super(bcrypt, cls).set_backend(name)
#issue warning if builtin is ever chosen by default
# (but if they explicitly ask for it, let it happen)
if name != "builtin" and result == "builtin":
@@ -150,13 +149,12 @@ class BCrypt(BackendExtHash):
#eoc
#=========================================================
-autodocument(BCrypt, settings_doc="""
+autodocument(bcrypt, settings_doc="""
:param ident:
selects specific version of BCrypt hash that will be used.
Typically you want to leave this alone, and let it default to ``2a``,
but it can be set to ``2`` to use the older version of BCrypt.
""")
-register_crypt_handler(BCrypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/des_crypt.py b/passlib/drivers/des_crypt.py
index d797a96..c83e1e3 100644
--- a/passlib/drivers/des_crypt.py
+++ b/passlib/drivers/des_crypt.py
@@ -1,4 +1,4 @@
-"""passlib.hash.des_crypt - traditional unix (DES) crypt
+"""passlib.drivers.des_crypt - traditional unix (DES) crypt
.. note::
@@ -58,14 +58,14 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.base import register_crypt_handler
from passlib.utils import h64, autodocument, classproperty, os_crypt
-from passlib.utils.handlers import BackendExtHandler, ExtHandler
+from passlib.utils.drivers import BackendExtHash, ExtHash
from passlib.utils.des import mdes_encrypt_int_block
#pkg
#local
__all__ = [
- "DesCrypt",
+ "des_crypt",
+ "bsdi_crypt",
]
#=========================================================
@@ -134,7 +134,7 @@ def raw_ext_crypt(secret, rounds, salt):
#=========================================================
#handler
#=========================================================
-class DesCrypt(BackendExtHandler):
+class des_crypt(BackendExtHash):
#=========================================================
#class attrs
#=========================================================
@@ -203,18 +203,17 @@ class DesCrypt(BackendExtHandler):
#eoc
#=========================================================
-autodocument(DesCrypt)
-register_crypt_handler(DesCrypt)
+autodocument(des_crypt)
#=========================================================
#handler
#=========================================================
#XXX: rename this to BSDiCrypt?
-class ExtDesCrypt(ExtHandler):
+class bsdi_crypt(ExtHash):
#=========================================================
#class attrs
#=========================================================
- name = "ext_des_crypt"
+ name = "bsdi_crypt"
setting_kwds = ("salt", "rounds")
min_salt_chars = max_salt_chars = 4
@@ -277,8 +276,7 @@ class ExtDesCrypt(ExtHandler):
#eoc
#=========================================================
-autodocument(ExtDesCrypt)
-register_crypt_handler(ExtDesCrypt)
+autodocument(bsdi_crypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/ext_des_crypt.py b/passlib/drivers/ext_des_crypt.py
deleted file mode 100644
index 9729637..0000000
--- a/passlib/drivers/ext_des_crypt.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""passlib.hash.ext_des_crypt - extended BSDi unix (DES) crypt"""
-#=========================================================
-#imports
-#=========================================================
-#core
-import re
-import logging; log = logging.getLogger(__name__)
-from warnings import warn
-#site
-#libs
-from passlib.base import register_crypt_handler
-from passlib.utils.handlers import ExtHandler
-from passlib.utils import h64, autodocument
-from passlib.utils.des import mdes_encrypt_int_block
-from passlib.hash.des_crypt import _crypt_secret_to_key
-#pkg
-#local
-__all__ = [
- "ExtDesCrypt",
-]
-
-#TODO: rename this to BSDiCrypt
-
-#=========================================================
-#backend
-#=========================================================
-def raw_ext_crypt(secret, rounds, salt):
- "ext_crypt() helper which returns checksum only"
-
- #decode salt
- try:
- salt_value = h64.decode_int24(salt)
- except ValueError:
- raise ValueError, "invalid salt"
-
- #validate secret
- if '\x00' in secret:
- #builtin linux crypt doesn't like this, so we don't either
- #XXX: would make more sense to raise ValueError, but want to be compatible w/ stdlib crypt
- raise ValueError, "secret must be string without null bytes"
-
- #convert secret string into an integer
- key_value = _crypt_secret_to_key(secret)
- idx = 8
- end = len(secret)
- while idx < end:
- next = idx+8
- key_value = mdes_encrypt_int_block(key_value, key_value) ^ _crypt_secret_to_key(secret[idx:next])
- idx = next
-
- #run data through des using input of 0
- result = mdes_encrypt_int_block(key_value, 0, salt_value, rounds)
-
- #run h64 encode on result
- return h64.encode_dc_int64(result)
-
-#=========================================================
-#handler
-#=========================================================
-class ExtDesCrypt(ExtHandler):
- #=========================================================
- #class attrs
- #=========================================================
- name = "ext_des_crypt"
- setting_kwds = ("salt", "rounds")
-
- min_salt_chars = max_salt_chars = 4
-
- default_rounds = 1000
- min_rounds = 0
- max_rounds = 16777215 # (1<<24)-1
- rounds_cost = "linear"
-
- checksum_chars = 11
- checksum_charset = h64.CHARS
-
- # NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,
- # but that seems to be an OS policy, not a algorithm limitation.
-
- #=========================================================
- #internal helpers
- #=========================================================
- _pat = re.compile(r"""
- ^
- _
- (?P<rounds>[./a-z0-9]{4})
- (?P<salt>[./a-z0-9]{4})
- (?P<chk>[./a-z0-9]{11})?
- $""", re.X|re.I)
-
- @classmethod
- def identify(cls, hash):
- return bool(hash and cls._pat.match(hash))
-
- @classmethod
- def from_string(cls, hash):
- if not hash:
- raise ValueError, "no hash specified"
- m = cls._pat.match(hash)
- if not m:
- raise ValueError, "invalid ext-des-crypt hash"
- rounds, salt, chk = m.group("rounds", "salt", "chk")
- return cls(
- rounds=h64.decode_int24(rounds),
- salt=salt,
- checksum=chk,
- strict=bool(chk),
- )
-
- def to_string(self):
- return "_%s%s%s" % (h64.encode_int24(self.rounds), self.salt, self.checksum or '')
-
- #=========================================================
- #backend
- #=========================================================
- #TODO: check if os_crypt supports ext-des-crypt.
-
- def calc_checksum(self, secret):
- if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
- return raw_ext_crypt(secret, self.rounds, self.salt)
-
- #=========================================================
- #eoc
- #=========================================================
-
-autodocument(ExtDesCrypt)
-register_crypt_handler(ExtDesCrypt)
-#=========================================================
-#eof
-#=========================================================
diff --git a/passlib/drivers/md5_crypt.py b/passlib/drivers/md5_crypt.py
index 6b2a1be..77badad 100644
--- a/passlib/drivers/md5_crypt.py
+++ b/passlib/drivers/md5_crypt.py
@@ -1,4 +1,4 @@
-"""passlib.hash.md5_crypt - md5-crypt algorithm"""
+"""passlib.drivers.md5_crypt - md5-crypt algorithm"""
#=========================================================
#imports
#=========================================================
@@ -9,13 +9,13 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.base import register_crypt_handler
from passlib.utils import h64, autodocument, os_crypt
-from passlib.utils.handlers import ExtHandler, BackendExtHandler
+from passlib.utils.drivers import ExtHash, BackendExtHash
#pkg
#local
__all__ = [
- "Md5Crypt",
+ "md5_crypt",
+ "apr_md5_crypt",
]
#=========================================================
@@ -126,7 +126,7 @@ _chk_offsets = (
#=========================================================
#handler
#=========================================================
-class Md5Crypt(BackendExtHandler):
+class md5_crypt(BackendExtHash):
#=========================================================
#algorithm information
#=========================================================
@@ -195,13 +195,12 @@ class Md5Crypt(BackendExtHandler):
#eoc
#=========================================================
-autodocument(Md5Crypt)
-register_crypt_handler(Md5Crypt)
+autodocument(md5_crypt)
#=========================================================
#apache variant of md5-crypt
#=========================================================
-class AprMd5Crypt(ExtHandler):
+class apr_md5_crypt(ExtHash):
#=========================================================
#algorithm information
#=========================================================
@@ -254,8 +253,7 @@ class AprMd5Crypt(ExtHandler):
#eoc
#=========================================================
-autodocument(AprMd5Crypt)
-register_crypt_handler(AprMd5Crypt)
+autodocument(apr_md5_crypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/mysql.py b/passlib/drivers/mysql.py
index e82700a..3af31ec 100644
--- a/passlib/drivers/mysql.py
+++ b/passlib/drivers/mysql.py
@@ -1,10 +1,10 @@
-"""passlib.hash.mysql
+"""passlib.drivers.mysql
MySQL 3.2.3 / OLD_PASSWORD()
This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1.
- See :mod:`passlib.hash.mysql_41` for the new algorithm was put in place in version 4.1
+ See :mod:`passlib.drivers.mysql_41` for the new algorithm was put in place in version 4.1
This algorithm is known to be very insecure, and should only be used to verify existing password hashes.
@@ -23,29 +23,29 @@ MySQL 4.1.1 / NEW PASSWORD
#imports
#=========================================================
#core
+from hashlib import sha1
import re
import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
#pkg
-from passlib.base import register_crypt_handler
from passlib.utils import autodocument
-from passlib.utils.handlers import BaseHandler
+from passlib.utils.drivers import BaseHash
#local
__all__ = [
- 'MySQL_323',
- 'MySQL_41',
+ 'mysql323',
+ 'mysq41',
]
#=========================================================
#backend
#=========================================================
-class MySQL_323(BaseHandler):
+class mysql323(BaseHash):
#=========================================================
#class attrs
#=========================================================
- name = "mysql_323"
+ name = "mysql323"
setting_kwds = ()
#=========================================================
@@ -101,17 +101,16 @@ class MySQL_323(BaseHandler):
#eoc
#=========================================================
-autodocument(MySQL_323)
-register_crypt_handler(MySQL_323)
+autodocument(mysql323)
#=========================================================
#handler
#=========================================================
-class MySQL_41(BaseHandler):
+class mysql41(BaseHash):
#=========================================================
#algorithm information
#=========================================================
- name = "mysql_41"
+ name = "mysql41"
setting_kwds = ()
#=========================================================
@@ -152,8 +151,7 @@ class MySQL_41(BaseHandler):
#eoc
#=========================================================
-autodocument(MySQL_41)
-register_crypt_handler(MySQL_41)
+autodocument(mysql41)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/nthash.py b/passlib/drivers/nthash.py
index 6efe63f..cc78212 100644
--- a/passlib/drivers/nthash.py
+++ b/passlib/drivers/nthash.py
@@ -1,4 +1,4 @@
-"""passlib.hash.nthash - unix-crypt compatible nthash passwords"""
+"""passlib.drivers.nthash - unix-crypt compatible nthash passwords"""
#=========================================================
#imports
#=========================================================
@@ -8,10 +8,9 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.base import register_crypt_handler
from passlib.utils.md4 import md4
from passlib.utils import autodocument
-from passlib.utils.handlers import ExtHandler
+from passlib.utils.drivers import ExtHash
#pkg
#local
__all__ = [
@@ -29,7 +28,7 @@ def raw_nthash(secret, hex=False):
#=========================================================
#handler
#=========================================================
-class NTHash(ExtHandler):
+class nthash(ExtHash):
#=========================================================
#class attrs
#=========================================================
@@ -101,13 +100,12 @@ class NTHash(ExtHandler):
#eoc
#=========================================================
-autodocument(NTHash, settings_doc="""
+autodocument(nthash, settings_doc="""
:param ident:
This handler supports two different :ref:`modular-crypt-format` identifiers.
It defaults to ``3``, but users may specify the alternate ``NT`` identifier
which is used in some contexts.
""")
-register_crypt_handler(NTHash)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/phpass.py b/passlib/drivers/phpass.py
index 2705a6e..b24ce66 100644
--- a/passlib/drivers/phpass.py
+++ b/passlib/drivers/phpass.py
@@ -1,4 +1,4 @@
-"""passlib.hash.phpass - PHPass Portable Crypt
+"""passlib.drivers.phpass - PHPass Portable Crypt
phppass located - http://www.openwall.com/phpass/
algorithm described - http://www.openwall.com/articles/PHP-Users-Passwords
@@ -16,22 +16,17 @@ from warnings import warn
#site
#libs
from passlib.utils import h64, autodocument
-from passlib.utils.handlers import ExtHandler
-from passlib.base import register_crypt_handler
+from passlib.utils.drivers import ExtHash
#pkg
#local
__all__ = [
- "genhash",
- "genconfig",
- "encrypt",
- "identify",
- "verify",
+ "phpass",
]
#=========================================================
#phpass
#=========================================================
-class PHPass(ExtHandler):
+class phpass(ExtHash):
#=========================================================
#class attrs
@@ -129,13 +124,12 @@ class PHPass(ExtHandler):
#eoc
#=========================================================
-autodocument(PHPass, settings_doc="""
+autodocument(phpass, settings_doc="""
:param ident:
phpBB3 uses ``H`` instead of ``P`` for it's identifier,
this may be set to ``H`` in order to generate phpBB3 compatible hashes.
it defaults to ``P``.
""")
-register_crypt_handler(PHPass)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/postgres.py b/passlib/drivers/postgres.py
index cafb9b1..274151d 100644
--- a/passlib/drivers/postgres.py
+++ b/passlib/drivers/postgres.py
@@ -1,4 +1,4 @@
-"""passlib.hash.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table"""
+"""passlib.drivers.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table"""
#=========================================================
#imports
#=========================================================
@@ -10,17 +10,46 @@ from warnings import warn
#site
#libs
#pkg
-from passlib.base import register_crypt_handler
from passlib.utils import autodocument
#local
__all__ = [
- "PostgresMD5",
+ "postgres_md5",
]
#=========================================================
+#
+#=========================================================
+class postgres_plaintext(object):
+ "fake password hash which recognizes ALL hashes, and assumes they encode the password in plain-text"
+ name = "postgres_plaintext"
+
+ setting_kwds = ()
+ context_kwds = ("user",)
+
+ @classmethod
+ def genconfig(cls):
+ return None
+
+ @classmethod
+ def genhash(cls, secret, config, user):
+ return secret
+
+ @classmethod
+ def identify(cls, hash):
+ return bool(hash)
+
+ @classmethod
+ def encrypt(cls, secret, config, user):
+ return secret
+
+ @classmethod
+ def verify(cls, secret, hash, user):
+ return secret == hash
+
+#=========================================================
#handler
#=========================================================
-class PostgresMD5(object):
+class postgres_md5(object):
#=========================================================
#algorithm information
#=========================================================
@@ -74,10 +103,9 @@ class PostgresMD5(object):
#eoc
#=========================================================
-autodocument(PostgresMD5, context_doc="""\
+autodocument(postgres_md5, context_doc="""\
:param user: string containing name of postgres user account this password is associated with.
""")
-register_crypt_handler(PostgresMD5)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/sha1_crypt.py b/passlib/drivers/sha1_crypt.py
index da332bd..96d18e4 100644
--- a/passlib/drivers/sha1_crypt.py
+++ b/passlib/drivers/sha1_crypt.py
@@ -1,4 +1,4 @@
-"""passlib.hash.sha1_crypt
+"""passlib.drivers.sha1_crypt
"""
#=========================================================
@@ -14,9 +14,8 @@ from warnings import warn
#site
#libs
from passlib.utils import autodocument, h64
-from passlib.utils.handlers import ExtHandler
+from passlib.utils.drivers import ExtHash
from passlib.utils.pbkdf2 import hmac_sha1
-from passlib.base import register_crypt_handler
#pkg
#local
__all__ = [
@@ -24,7 +23,7 @@ __all__ = [
#=========================================================
#sha1-crypt
#=========================================================
-class SHA1Crypt(ExtHandler):
+class sha1_crypt(ExtHash):
#=========================================================
#class attrs
@@ -108,8 +107,7 @@ class SHA1Crypt(ExtHandler):
#eoc
#=========================================================
-autodocument(SHA1Crypt)
-register_crypt_handler(SHA1Crypt)
+autodocument(sha1_crypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/sha2_crypt.py b/passlib/drivers/sha2_crypt.py
index 4e3fa6c..c8c15bc 100644
--- a/passlib/drivers/sha2_crypt.py
+++ b/passlib/drivers/sha2_crypt.py
@@ -1,4 +1,4 @@
-"""passlib.hash.sha2_crypt - SHA256/512-CRYPT"""
+"""passlib.drivers.sha2_crypt - SHA256/512-CRYPT"""
#=========================================================
#imports
#=========================================================
@@ -9,9 +9,8 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.base import register_crypt_handler
from passlib.utils import h64, autodocument, os_crypt, classproperty
-from passlib.utils.handlers import BackendExtHandler
+from passlib.utils.drivers import BackendExtHash
#pkg
#local
__all__ = [
@@ -206,7 +205,7 @@ _512_offsets = (
#=========================================================
#handler
#=========================================================
-class SHA256Crypt(BackendExtHandler):
+class sha256_crypt(BackendExtHash):
#=========================================================
#algorithm information
@@ -231,7 +230,7 @@ class SHA256Crypt(BackendExtHandler):
if implicit_rounds is None:
implicit_rounds = True
self.implicit_rounds = implicit_rounds
- super(SHA256Crypt, self).__init__(**kwds)
+ super(sha256_crypt, self).__init__(**kwds)
#=========================================================
#parsing
@@ -318,7 +317,7 @@ class SHA256Crypt(BackendExtHandler):
#eoc
#=========================================================
-autodocument(SHA256Crypt, settings_doc="""
+autodocument(sha256_crypt, settings_doc="""
:param implicit_rounds:
this is an internal option which generally doesn't need to be touched.
@@ -327,12 +326,11 @@ autodocument(SHA256Crypt, settings_doc="""
and the flag is ignored otherwise. the spec requires the two different
encodings be preserved as they are, instead of normalizing them.
""")
-register_crypt_handler(SHA256Crypt)
#=========================================================
#sha 512 crypt
#=========================================================
-class SHA512Crypt(BackendExtHandler):
+class sha512_crypt(BackendExtHash):
#=========================================================
#algorithm information
@@ -357,7 +355,7 @@ class SHA512Crypt(BackendExtHandler):
if implicit_rounds is None:
implicit_rounds = True
self.implicit_rounds = implicit_rounds
- super(SHA512Crypt, self).__init__(**kwds)
+ super(sha512_crypt, self).__init__(**kwds)
#=========================================================
#parsing
@@ -446,7 +444,7 @@ class SHA512Crypt(BackendExtHandler):
#eoc
#=========================================================
-autodocument(SHA512Crypt, settings_doc="""
+autodocument(sha512_crypt, settings_doc="""
:param implicit_rounds:
this is an internal option which generally doesn't need to be touched.
@@ -455,7 +453,6 @@ autodocument(SHA512Crypt, settings_doc="""
and the flag is ignored otherwise. the spec requires the two different
encodings be preserved as they are, instead of normalizing them.
""")
-register_crypt_handler(SHA512Crypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/drivers/sun_md5_crypt.py b/passlib/drivers/sun_md5_crypt.py
index 2728a97..68294c4 100644
--- a/passlib/drivers/sun_md5_crypt.py
+++ b/passlib/drivers/sun_md5_crypt.py
@@ -1,4 +1,4 @@
-"""passlib.hash.sun_md5_crypt - Sun's Md5 Crypt, used on Solaris
+"""passlib.drivers.sun_md5_crypt - Sun's Md5 Crypt, used on Solaris
.. warning::
@@ -25,17 +25,12 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.base import register_crypt_handler
from passlib.utils import h64, autodocument
-from passlib.utils.handlers import ExtHandler
+from passlib.utils.drivers import ExtHash
#pkg
#local
__all__ = [
- "genhash",
- "genconfig",
- "encrypt",
- "identify",
- "verify",
+ "sun_md5_crypt",
]
#=========================================================
@@ -190,7 +185,7 @@ _chk_offsets = (
#=========================================================
#handler
#=========================================================
-class SunMD5Crypt(ExtHandler):
+class sun_md5_crypt(ExtHash):
#=========================================================
#class attrs
#=========================================================
@@ -269,8 +264,7 @@ class SunMD5Crypt(ExtHandler):
#eoc
#=========================================================
-autodocument(SunMD5Crypt)
-register_crypt_handler(SunMD5Crypt)
+autodocument(sun_md5_crypt)
#=========================================================
#eof
#=========================================================
diff --git a/passlib/sqldb.py b/passlib/sqldb.py
index 40b0b32..6ed165a 100644
--- a/passlib/sqldb.py
+++ b/passlib/sqldb.py
@@ -2,70 +2,38 @@
#=========================================================
#imports
#=========================================================
-from __future__ import with_statement
#core
-import inspect
-import re
-import hashlib
import logging; log = logging.getLogger(__name__)
-import time
-import os
#site
#libs
-from passlib.hash import postgres_md5, mysql_323, mysql_41
-from passlib.base import CryptContext, register_crypt_handler
-from passlib.utils.handlers import CryptHandler
+from passlib.base import CryptContext
#pkg
#local
__all__ = [
#postgres
+ 'postgres_plaintext',
'postgres_md5',
'postgres_context',
#mysql
- 'mysql_323',
- 'mysql_41',
+ 'mysql323',
+ 'mysql41',
'mysql3_context',
'mysql_context'
]
#=========================================================
-#helpers
-#=========================================================
-class PostgresPlaintextHandler(CryptHandler):
- "fake password hash which recognizes ALL hashes, and assumes they encode the password in plain-text"
- name = "postgres_plaintext"
-
- context_kwds = ("user",)
-
- @classmethod
- def genconfig(cls):
- return None
-
- @classmethod
- def genhash(cls, secret, config, user=None):
- return secret
-
- @classmethod
- def identify(cls, hash):
- return bool(hash)
-
- @classmethod
- def verify(cls, secret, hash, user=None):
- return secret == hash
-
-register_crypt_handler(PostgresPlainTextHandler)
-
-#=========================================================
#postgres
#=========================================================
-postgres_context = CryptContext([PostgresPlainTextHandler, postgres_md5])
+from passlib.drivers.postgres import postgres_plaintext, postgres_md5
+postgres_context = CryptContext([postgres_plaintext, postgres_md5])
#=========================================================
#mysql
#=========================================================
-mysql3_context = CryptContext([mysql_323])
-mysql_context = CryptContext([mysql_323, mysql_41])
+from passlib.drivers.mysql import mysql323, mysql41
+mysql3_context = CryptContext([mysql323])
+mysql_context = CryptContext([mysql323, mysql41])
#=========================================================
#TODO:
diff --git a/passlib/tests/handler_utils.py b/passlib/tests/handler_utils.py
deleted file mode 100644
index 08d6c5a..0000000
--- a/passlib/tests/handler_utils.py
+++ /dev/null
@@ -1,355 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-#core
-import re
-#site
-from nose.plugins.skip import SkipTest
-#pkg
-from passlib.tests.utils import TestCase, enable_option
-from passlib.utils.handlers import BaseHandler, BackendMixin
-#module
-__all__ = [
- "_HandlerTestCase",
- "create_backend_test",
-]
-#=========================================================
-#other unittest helpers
-#=========================================================
-
-class _HandlerTestCase(TestCase):
- """base class for testing CryptHandler implementations.
-
- .. todo::
- write directions on how to use this class.
- for now, see examples in places such as test_unix_crypt
- """
-
- #=========================================================
- #attrs to be filled in by subclass for testing specific handler
- #=========================================================
-
- #specify handler object here
- handler = None
-
- #this option is available for hashes which can't handle unicode
- supports_unicode = True
-
- #maximum number of chars which hash will include in checksum
- #override this only if hash doesn't use all chars (the default)
- secret_chars = -1
-
- #list of (secret,hash) pairs which handler should verify as matching
- known_correct = []
-
- #list of (secret,hash) pairs which handler should verify as NOT matching
- known_incorrect = []
-
- # list of handler's hashes with crucial invalidating typos, that handler shouldn't identify as belonging to it
- known_invalid = []
-
- # list of handler's hashes that it *will* identify as it's own, but genhash will raise error due to invalid internal requirements
- known_identified_invalid = []
-
- #list of (name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it
- #this list should generally be sufficient (if handler name in list, that entry will be skipped)
- known_other = [
- ('des_crypt', '6f8c114b58f2c'),
- ('md5_crypt', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
- ('sha512_crypt', "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
- "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"),
- ]
-
- #list of various secrets all algs are tested with to make sure they work
- standard_secrets = [
- '',
- ' ',
- 'my socrates note',
- 'Compl3X AlphaNu3meric',
- '4lpHa N|_|M3r1K W/ Cur51|\\|g: #$%(*)(*%#',
- 'Really Long Password (tm), which is all the rage nowadays. Maybe some Shakespeare?',
- ]
-
- unicode_secrets = [
- u'test with unic\u00D6de',
- ]
-
- #optional prefix to prepend to name of test method as it's called,
- #useful when multiple handler test classes being run.
- #default behavior should be sufficient
- def case_prefix(self):
- name = self.handler.name if self.handler else self.__class__.__name__
- backend = getattr(self.handler, "get_backend", None) #set by some of the builtin handlers
- if backend:
- name += " (%s backend)" % (backend(),)
- return name
-
- #=========================================================
- #alg interface helpers - allows subclass to overide how
- # default tests invoke the handler (eg for context_kwds)
- #=========================================================
- def do_concat(self, secret, prefix):
- "concatenate prefix onto secret"
- #NOTE: this is subclassable mainly for some algorithms
- #which accept non-strings in secret
- return prefix + secret
-
- def do_encrypt(self, secret, **kwds):
- "call handler's encrypt method with specified options"
- return self.handler.encrypt(secret, **kwds)
-
- def do_verify(self, secret, hash):
- "call handler's verify method"
- return self.handler.verify(secret, hash)
-
- def do_identify(self, hash):
- "call handler's identify method"
- return self.handler.identify(hash)
-
- #=========================================================
- #attributes
- #=========================================================
- def test_00_attributes(self):
- "test handler attributes are all defined"
- handler = self.handler
- def ga(name):
- return getattr(handler, name, None)
-
- name = ga("name")
- self.assert_(name, "name not defined:")
- self.assert_(name.lower() == name, "name not lower-case:")
- self.assert_(re.match("^[a-z0-9_]+$", name), "name must be alphanum + underscore: %r" % (name,))
-
- def test_01_base_handler(self):
- "check configuration of BaseHandler-derived classes"
- h = self.handler
- if not isinstance(h, type) or not issubclass(h, BaseHandler):
- raise SkipTest
- h.validate_class() #should raise AssertionError if something's wrong.
-
- #=========================================================
- #identify
- #=========================================================
- def test_10_identify_other(self):
- "test identify() against other schemes' hashes"
- for name, hash in self.known_other:
- self.assertEqual(self.do_identify(hash), name == self.handler.name)
-
- def test_11_identify_positive(self):
- "test identify() against scheme's own hashes"
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_identify(hash), True)
-
- for secret, hash in self.known_incorrect:
- self.assertEqual(self.do_identify(hash), True)
-
- for hash in self.known_identified_invalid:
- self.assertEqual(self.do_identify(hash), True)
-
- def test_12_identify_invalid(self):
- "test identify() against malformed instances of scheme's own hashes"
- if not self.known_invalid:
- raise SkipTest
- for hash in self.known_invalid:
- self.assertEqual(self.do_identify(hash), False, "hash=%r:" % (hash,))
-
- def test_13_identify_none(self):
- "test identify() against None / empty string"
- self.assertEqual(self.do_identify(None), False)
- self.assertEqual(self.do_identify(''), False)
-
- #=========================================================
- #verify
- #=========================================================
- def test_20_verify_positive(self):
- "test verify() against known-correct secret/hash pairs"
- self.assert_(self.known_correct, "test must define known_correct hashes")
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash))
-
- def test_21_verify_negative(self):
- "test verify() against known-incorrect secret/hash pairs"
- if not self.known_incorrect:
- raise SkipTest
- for secret, hash in self.known_incorrect:
- self.assertEqual(self.do_verify(secret, hash), False)
-
- #XXX: is this needed if known_incorrect is defined?
- def test_22_verify_derived_negative(self):
- "test verify() against derived incorrect secret/hash pairs"
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_verify(self.do_concat(secret,'x'), hash), False)
-
- def test_23_verify_other(self):
- "test verify() throws error against other algorithm's hashes"
- for name, hash in self.known_other:
- if name == self.handler.name:
- continue
- self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify other %r %r:" % (name, hash))
-
- def test_24_verify_invalid(self):
- "test verify() throws error against known-invalid hashes"
- if not self.known_invalid and not self.known_identified_invalid:
- raise SkipTest
- for hash in self.known_invalid + self.known_identified_invalid:
- self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify invalid %r:" % (hash,))
-
- def test_25_verify_none(self):
- "test verify() throws error against hash=None/empty string"
- #find valid hash so that doesn't mask error
- self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="verify None:")
- self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="verify empty:")
-
- #=========================================================
- #encrypt
- #=========================================================
-
- #---------------------------------------------------------
- #test encryption against various secrets
- #---------------------------------------------------------
- def test_30_encrypt_standard(self):
- "test encrypt() against standard secrets"
- for secret in self.standard_secrets:
- self.check_encrypt(secret)
-
- def test_31_encrypt_unicode(self):
- "test encrypt() against unicode secrets"
- if not self.supports_unicode:
- raise SkipTest
- for secret in self.unicode_secrets:
- self.check_encrypt(secret)
-
- #this is probably excessive
- ##def test_32_encrypt_positive(self):
- ## "test encrypt() against known-correct secret/hash pairs"
- ## for secret, hash in self.known_correct:
- ## self.check_encrypt(secret)
-
- def check_encrypt(self, secret):
- "check encrypt() behavior for a given secret"
- #hash the secret
- hash = self.do_encrypt(secret)
-
- #test identification
- self.assertEqual(self.do_identify(hash), True, "identify hash %r from secret %r:" % (hash, secret))
-
- #test positive verification
- self.assertEqual(self.do_verify(secret, hash), True, "verify hash %r from secret %r:" % (hash, secret))
-
- #test negative verification
- for other in ['', 'test', self.do_concat(secret,'x')]:
- if other != secret:
- self.assertEqual(self.do_verify(other, hash), False,
- "hash collision: %r and %r => %r" % (secret, other, hash))
-
- #---------------------------------------------------------
- #test salt handling
- #---------------------------------------------------------
- def test_33_encrypt_gensalt(self):
- "test encrypt() generates new salt each time"
- if 'salt' not in self.handler.setting_kwds:
- raise SkipTest
- for secret, hash in self.known_correct:
- hash2 = self.do_encrypt(secret)
- self.assertNotEqual(hash, hash2)
-
- #TODO: test too-short user-provided salts
- #TODO: test too-long user-provided salts
- #TODO: test invalid char in user-provided salts
-
- #---------------------------------------------------------
- #test secret handling
- #---------------------------------------------------------
- def test_37_secret_chars(self):
- "test secret_chars limit"
- sc = self.secret_chars
-
- base = "too many secrets" #16 chars
- alt = 'x' #char that's not in base string
-
- if sc > 0:
- #hash only counts the first <sc> characters
- #eg: bcrypt, des-crypt
-
- #create & hash something of exactly sc+1 chars
- secret = (base * (1+sc//16))[:sc+1]
- assert len(secret) == sc+1
- hash = self.do_encrypt(secret)
-
- #check sc value isn't too large
- #by verifying that sc-1'th char affects hash
- self.assert_(not self.do_verify(secret[:-2] + alt + secret[-1], hash), "secret_chars value is too large")
-
- #check sc value isn't too small
- #by verifying adding sc'th char doesn't affect hash
- self.assert_(self.do_verify(secret[:-1] + alt, hash))
-
- else:
- #hash counts all characters
- #eg: md5-crypt
- self.assertEquals(sc, -1)
-
- #NOTE: this doesn't do an exhaustive search to verify algorithm
- #doesn't have some cutoff point, it just tries
- #1024-character string, and alters the last char.
- #as long as algorithm doesn't clip secret at point <1024,
- #the new secret shouldn't verify.
- secret = base * 64
- hash = self.do_encrypt(secret)
- self.assert_(not self.do_verify(secret[:-1] + alt, hash))
-
- def test_38_encrypt_none(self):
- "test encrypt() refused secret=None"
- self.assertRaises(TypeError, self.do_encrypt, None)
-
- #=========================================================
- #
- #=========================================================
-
- #TODO: check genhash works
- #TODO: check genconfig works
-
- #TODO: check parse method works
- #TODO: check render method works
- #TODO: check default/min/max_rounds valid if present
-
- #=========================================================
- #eoc
- #=========================================================
-
-#=========================================================
-#backend test helpers
-#=========================================================
-def enable_backend_case(handler, name):
- "helper to check if a separate test is needed for the specified backend"
- assert issubclass(handler, BackendMixin), "handler must derived from BackendMixin"
- assert name in handler.backends, "unknown backend: %r" % (name,)
- return enable_option("all-backends") and handler.get_backend() != name and handler.has_backend(name)
-
-def create_backend_case(base_test, name):
- "create a test case (subclassing); if test doesn't need to be enabled, returns None"
- handler = base_test.handler
-
- if not enable_backend_case(handler, name):
- return None
-
- assert getattr(base_test, "setUp", None) is None #just haven't implemented this
- assert getattr(base_test, "cleanUp", None) is None #ditto
-
- class dummy(base_test):
- case_prefix = "%s (%s backend)" % (handler.name, name)
-
- def setUp(self):
- self.orig_backend = self.handler.get_backend()
- self.handler.set_backend(name)
-
- def cleanUp(self):
- self.handler.set_backend(self.orig_backend)
-
- dummy.__name__ = name.title() + base_test.__name__
- return dummy
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_base.py
index 220248c..82d6fb3 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_base.py
@@ -10,13 +10,16 @@ from logging import getLogger
#pkg
from passlib.base import CryptContext
from passlib.tests.utils import TestCase
-##from passlib.unix.des_crypt import DesCrypt
-##from passlib.unix.sha_crypt import Sha512Crypt
-import passlib.hash.md5_crypt as AnotherHash
-from passlib.tests.test_handler import UnsaltedHash, SaltedHash
+from passlib.drivers.md5_crypt import Md5Crypt as AnotherHash
+from passlib.tests.test_utils_drivers import UnsaltedHash, SaltedHash
#module
log = getLogger(__name__)
+#
+#FIXME: this unit test does not match the current CryptContext *at all*,
+# and needs to be updated / rewritten to match new system
+#
+
#=========================================================
#CryptContext
#=========================================================
diff --git a/passlib/tests/test_drivers.py b/passlib/tests/test_drivers.py
new file mode 100644
index 0000000..8fb562a
--- /dev/null
+++ b/passlib/tests/test_drivers.py
@@ -0,0 +1,422 @@
+"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
+#=========================================================
+#imports
+#=========================================================
+from __future__ import with_statement
+#core
+import hashlib
+import logging; log = logging.getLogger(__name__)
+try:
+ from warnings import catch_warnings
+except ImportError: #wasn't added until py26
+ catch_warnings = None
+import warnings
+#site
+#pkg
+from passlib.tests.utils import HandlerCase, create_backend_case, enable_option
+#module
+
+#=========================================================
+#apr md5 crypt
+#=========================================================
+from passlib.drivers.md5_crypt import apr_md5_crypt
+class AprMd5CryptTest(HandlerCase):
+ handler = apr_md5_crypt
+
+ #values taken from http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
+ known_correct = (
+ ('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/'),
+ )
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA!'
+ ]
+
+#=========================================================
+#bcrypt
+#=========================================================
+from passlib.drivers.bcrypt import bcrypt
+
+class BCryptTest(HandlerCase):
+ handler = bcrypt
+ secret_chars = 72
+
+ known_correct = (
+ #selected bcrypt test vectors
+ ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
+ ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'),
+ ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'),
+ ('abcdefghijklmnopqrstuvwxyz', '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
+ ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'),
+ )
+
+ known_invalid = [
+ #unsupported minor version
+ "$2b$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
+ ]
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
+ #rounds not zero-padded (pybcrypt rejects this, therefore so do we)
+ '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'
+ ]
+
+#NOTE: pybcrypt backend will be chosen as primary if possible, so just check for os crypt and builtin
+OsCrypt_BCryptTest = create_backend_case(BCryptTest, "os_crypt")
+
+#this one's unusuablly slow, don't test it unless user asks for it.
+Builtin_BCryptTest = create_backend_case(BCryptTest, "builtin") if enable_option("slow") else None
+
+#=========================================================
+#bsdi crypt
+#=========================================================
+from passlib.drivers.des_crypt import bsdi_crypt
+
+class BSDiCryptTest(HandlerCase):
+ "test BSDiCrypt algorithm"
+ handler = bsdi_crypt
+ known_correct = [
+ (" ", "_K1..crsmZxOLzfJH8iw"),
+ ("my", '_KR/.crsmykRplHbAvwA'), #<- to detect old 12-bit rounds bug
+ ("my socra", "_K1..crsmf/9NzZr1fLM"),
+ ("my socrates", '_K1..crsmOv1rbde9A9o'),
+ ("my socrates note", "_K1..crsm/2qeAhdISMA"),
+ ]
+ known_invalid = [
+ #bad char in otherwise correctly formatted hash
+ "_K1.!crsmZxOLzfJH8iw"
+ ]
+
+#=========================================================
+#des crypt
+#=========================================================
+from passlib.drivers.des_crypt import des_crypt
+
+class DesCryptTest(HandlerCase):
+ "test des-crypt algorithm"
+ handler = des_crypt
+ secret_chars = 8
+
+ #TODO: test
+
+ known_correct = (
+ #secret, example hash which matches secret
+ ('', 'OgAwTx2l6NADI'),
+ (' ', '/Hk.VPuwQTXbc'),
+ ('test', 'N1tQbOFcM5fpg'),
+ ('Compl3X AlphaNu3meric', 'um.Wguz3eVCx2'),
+ ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', 'sNYqfOyauIyic'),
+ ('AlOtBsOl', 'cEpWz5IUCShqM'),
+ (u'hell\u00D6', 'saykDgk3BPZ9E'),
+ )
+ known_invalid = [
+ #bad char in otherwise correctly formatted hash
+ '!gAwTx2l6NADI',
+ ]
+
+BuiltinDesCryptTest = create_backend_case(DesCryptTest, "builtin")
+
+#=========================================================
+#md5 crypt
+#=========================================================
+from passlib.drivers.md5_crypt import md5_crypt
+class Md5CryptTest(HandlerCase):
+ handler = md5_crypt
+
+ known_correct = (
+ ('', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
+ (' ', '$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'),
+ ('test', '$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'),
+ ('Compl3X AlphaNu3meric', '$1$nX1e7EeI$ljQn72ZUgt6Wxd9hfvHdV0'),
+ ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$1$jQS7o98J$V6iTcr71CGgwW2laf17pi1'),
+ ('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'),
+ )
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!',
+ ]
+
+BuiltinMd5CryptTest = create_backend_case(Md5CryptTest, "builtin")
+
+#=========================================================
+#mysql 323 & 41
+#=========================================================
+from passlib.drivers.mysql import mysql323, mysql41
+
+class Mysql323Test(HandlerCase):
+ handler = mysql323
+
+ #remove single space from secrets, since mysql-323 ignores all whitespace (?!)
+ standard_secrets = [ x for x in HandlerCase.standard_secrets if x != ' ' ]
+
+ known_correct = (
+ ('mypass', '6f8c114b58f2ce9e'),
+ )
+ known_invalid = [
+ #bad char in otherwise correct hash
+ '6z8c114b58f2ce9e',
+ ]
+
+ def test_whitespace(self):
+ "check whitespace is ignored per spec"
+ h = self.do_encrypt("mypass")
+ h2 = self.do_encrypt("my pass")
+ self.assertEqual(h, h2)
+
+class Mysql41Test(HandlerCase):
+ handler = mysql41
+ known_correct = (
+ ('mypass', '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'),
+ )
+ known_invalid = [
+ #bad char in otherwise correct hash
+ '*6Z8989366EAF75BB670AD8EA7A7FC1176A95CEF4',
+ ]
+
+#=========================================================
+#NTHASH for unix
+#=========================================================
+from passlib.drivers.nthash import nthash
+
+class NTHashTest(HandlerCase):
+ handler = nthash
+
+ known_correct = (
+ ('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'),
+ ('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'),
+ )
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$3$$7f8fe03093cc84b267b109625f6bbfxb',
+ ]
+
+#=========================================================
+#PHPass Portable Crypt
+#=========================================================
+from passlib.drivers.phpass import phpass
+
+class PHPassTest(HandlerCase):
+ handler = phpass
+
+ known_correct = (
+ ('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'),
+ ('compL3X!', '$P$FiS0N5L672xzQx1rt1vgdJQRYKnQM9/'),
+ ('test12345', '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'), #from the source
+ )
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r!L0',
+ ]
+
+#=========================================================
+#postgres_md5
+#=========================================================
+from passlib.drivers.postgres import postgres_md5, postgres_plaintext
+
+#FIXME: test postgres_plaintext
+
+class PostgresMD5CryptTest(HandlerCase):
+ handler = postgres_md5
+ known_correct = [
+ # ((secret,user),hash)
+ (('mypass', 'postgres'), 'md55fba2ea04fd36069d2574ea71c8efe9d'),
+ (('mypass', 'root'), 'md540c31989b20437833f697e485811254b'),
+ (("testpassword",'testuser'), 'md5d4fc5129cc2c25465a5370113ae9835f'),
+ ]
+ known_invalid = [
+ #bad 'z' char in otherwise correct hash
+ 'md54zc31989b20437833f697e485811254b',
+ ]
+
+ #NOTE: used to support secret=(password, user) format, but removed it for now.
+ ##def test_tuple_mode(self):
+ ## "check tuple mode works for encrypt/verify"
+ ## self.assertEquals(self.handler.encrypt(('mypass', 'postgres')),
+ ## 'md55fba2ea04fd36069d2574ea71c8efe9d')
+ ## self.assertEquals(self.handler.verify(('mypass', 'postgres'),
+ ## 'md55fba2ea04fd36069d2574ea71c8efe9d'), True)
+
+ def test_user(self):
+ "check user kwd is required for encrypt/verify"
+ self.assertRaises(TypeError, self.handler.encrypt, 'mypass')
+ self.assertRaises(TypeError, self.handler.verify, 'mypass', 'md55fba2ea04fd36069d2574ea71c8efe9d')
+
+ def do_concat(self, secret, prefix):
+ if isinstance(secret, tuple):
+ secret, user = secret
+ secret = prefix + secret
+ return secret, user
+ else:
+ return prefix + secret
+
+ def do_encrypt(self, secret, **kwds):
+ if isinstance(secret, tuple):
+ secret, user = secret
+ else:
+ user = 'default'
+ assert 'user' not in kwds
+ kwds['user'] = user
+ return self.handler.encrypt(secret, **kwds)
+
+ def do_verify(self, secret, hash):
+ if isinstance(secret, tuple):
+ secret, user = secret
+ else:
+ user = 'default'
+ return self.handler.verify(secret, hash, user=user)
+
+#=========================================================
+# (netbsd's) sha1 crypt
+#=========================================================
+from passlib.drivers.sha1_crypt import sha1_crypt
+
+class SHA1CryptTest(HandlerCase):
+ handler = sha1_crypt
+
+ known_correct = (
+ ("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"),
+ ("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"),
+ )
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH',
+ ]
+
+#=========================================================
+#sha256-crypt
+#=========================================================
+from passlib.drivers.sha2_crypt import sha256_crypt
+
+class SHA256CryptTest(HandlerCase):
+ handler = sha256_crypt
+ supports_unicode = True
+
+ known_correct = [
+ ('', '$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'),
+ (' ', '$5$rounds=10376$I5lNtXtRmf.OoMd8$Ko3AI1VvTANdyKhBPavaRjJzNpSatKU6QVN9uwS9MH.'),
+ ('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'),
+ ('Compl3X AlphaNu3meric', '$5$rounds=10350$o.pwkySLCzwTdmQX$nCMVsnF3TXWcBPOympBUUSQi6LGGloZoOsVJMGJ09UB'),
+ ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$5$rounds=11944$9dhlu07dQMRWvTId$LyUI5VWkGFwASlzntk1RLurxX54LUhgAcJZIt0pYGT7'),
+ (u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'),
+ ]
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ '$5$rounds=10428$uy/:jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMeZGsGx2aBvxTvDFI613c3',
+
+ #zero-padded rounds
+ '$5$rounds=010428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3',
+ ]
+
+BuiltinSHA256CryptTest = create_backend_case(SHA256CryptTest, "builtin")
+
+#=========================================================
+#test sha512-crypt
+#=========================================================
+from passlib.drivers.sha2_crypt import sha512_crypt
+
+class SHA512CryptTest(HandlerCase):
+ handler = sha512_crypt
+ supports_unicode = True
+
+ known_correct = [
+ ('', '$6$rounds=11021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1'),
+ (' ', '$6$rounds=11104$ED9SA4qGmd57Fq2m$q/.PqACDM/JpAHKmr86nkPzzuR5.YpYa8ZJJvI8Zd89ZPUYTJExsFEIuTYbM7gAGcQtTkCEhBKmp1S1QZwaXx0'),
+ ('test', '$6$rounds=11531$G/gkPn17kHYo0gTF$Kq.uZBHlSBXyzsOJXtxJruOOH4yc0Is13uY7yK0PvAvXxbvc1w8DO1RzREMhKsc82K/Jh8OquV8FZUlreYPJk1'),
+ ('Compl3X AlphaNu3meric', '$6$rounds=10787$wakX8nGKEzgJ4Scy$X78uqaX1wYXcSCtS4BVYw2trWkvpa8p7lkAtS9O/6045fK4UB2/Jia0Uy/KzCpODlfVxVNZzCCoV9s2hoLfDs/'),
+ ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$6$rounds=11065$5KXQoE1bztkY5IZr$Jf6krQSUKKOlKca4hSW07MSerFFzVIZt/N3rOTsUgKqp7cUdHrwV8MoIVNCk9q9WL3ZRMsdbwNXpVk0gVxKtz1'),
+ ]
+
+ known_identified_invalid = [
+ #zero-padded rounds
+ '$6$rounds=011021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1',
+ #bad char in otherwise correct hash
+ '$6$rounds=11021$KsvQipYPWpr9:wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1',
+ ]
+
+ #NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt
+ cases512 = [
+ #salt-hash, secret, result
+ ("$6$saltstring", "Hello world!",
+ "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
+ "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" ),
+
+ ( "$6$rounds=10000$saltstringsaltstring", "Hello world!",
+ "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
+ "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." ),
+
+ ( "$6$rounds=5000$toolongsaltstring", "This is just a test",
+ "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ"
+ "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" ),
+
+ ( "$6$rounds=1400$anotherlongsaltstring",
+ "a very much longer text to encrypt. This one even stretches over more"
+ "than one line.",
+ "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP"
+ "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" ),
+
+ ( "$6$rounds=77777$short",
+ "we have a short salt string but not a short password",
+ "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g"
+ "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" ),
+
+ ( "$6$rounds=123456$asaltof16chars..", "a short string",
+ "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
+ "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" ),
+
+ ( "$6$rounds=10$roundstoolow", "the minimum number is still observed",
+ "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
+ "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." ),
+ ]
+
+ def test_spec_vectors(self):
+ "verify sha512-crypt passes specification test vectors"
+ handler = self.handler
+
+ #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here
+ if catch_warnings:
+ ctx = catch_warnings()
+ ctx.__enter__()
+ warnings.filterwarnings("ignore", "sha512_crypt does not allow less than 1000 rounds: 10", UserWarning)
+
+ for config, secret, hash in self.cases512:
+ #make sure we got expected result back
+ result = handler.genhash(secret, config)
+ self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret))
+
+ #make sure parser truncated salts
+ info = handler.from_string(config)
+ self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret))
+
+ if catch_warnings:
+ ctx.__exit__(None,None,None)
+
+BuiltinSHA512CryptTest = create_backend_case(SHA512CryptTest, "builtin")
+
+#=========================================================
+#sun md5 crypt
+#=========================================================
+from passlib.drivers.sun_md5_crypt import sun_md5_crypt
+
+class SunMD5CryptTest(HandlerCase):
+ handler = sun_md5_crypt
+
+ known_correct = [
+ #sample hash found at http://compgroups.net/comp.unix.solaris/password-file-in-linux-and-solaris-8-9
+ ("passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"),
+ ]
+
+ known_identified_invalid = [
+ #bad char in otherwise correct hash
+ "$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/"
+ ]
+
+#=========================================================
+#EOF
+#=========================================================
diff --git a/passlib/tests/test_frontend.py b/passlib/tests/test_frontend.py
index 3182068..5fd54bb 100644
--- a/passlib/tests/test_frontend.py
+++ b/passlib/tests/test_frontend.py
@@ -20,10 +20,7 @@ def get_crypt_cases():
#this test suite uses info stored in the specific hash algs' test suites,
#so we have to import them here.
- from passlib.tests.test_hash_sha_crypt import Sha256CryptTest, Sha512CryptTest
- from passlib.tests.test_hash_des_crypt import DesCryptTest
- from passlib.tests.test_hash_bcrypt import BCryptTest
- from passlib.tests.test_hash_md5_crypt import Md5CryptTest
+ from passlib.tests.test_drivers import Sha256CryptTest, Sha512CryptTest, DesCryptTest, BCryptTest, Md5CryptTest
crypt_cases = [ DesCryptTest, Md5CryptTest, Sha256CryptTest]
if BCryptTest:
diff --git a/passlib/tests/test_hash_bcrypt.py b/passlib/tests/test_hash_bcrypt.py
deleted file mode 100755
index 4b64c15..0000000
--- a/passlib/tests/test_hash_bcrypt.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""passlib._bcrypt unitests (and parallel test for pybcrypt)
-
-The BaseTest class was adapted from the jBcrypt unitests,
-released under the following license:
-
- // Permission to use, copy, modify, and distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-#=========================================================
-#imports
-#=========================================================
-#site
-try:
- import bcrypt as pybcrypt
-except ImportError:
- pybcrypt = None
-#pkg
-from passlib.tests.utils import TestCase, enable_option
-from passlib.utils import _slow_bcrypt as slow_bcrypt
-from passlib.tests.handler_utils import _HandlerTestCase, create_backend_case
-from passlib.hash.bcrypt import BCrypt
-
-#=========================================================
-#utils
-#=========================================================
-class UtilTest(TestCase):
- "test slow_bcrypt's utility funcs"
-
- def test_encode64(self):
- encode = slow_bcrypt.encode_base64
- self.assertFunctionResults(encode, [
- ('', ''),
- ('..', '\x00'),
- ('...', '\x00\x00'),
- ('....', '\x00\x00\x00'),
- ('9u', '\xff'),
- ('996', '\xff\xff'),
- ('9999', '\xff\xff\xff'),
- ])
-
- def test_decode64(self):
- decode = slow_bcrypt.decode_base64
- self.assertFunctionResults(decode, [
- ('', ''),
- ('\x00', '..'),
- ('\x00\x00', '...'),
- ('\x00\x00\x00', '....'),
- ('\xff', '9u', ),
- ('\xff\xff','996'),
- ('\xff\xff\xff','9999'),
- ])
-
-#=========================================================
-#test frontend bcrypt algorithm
-#=========================================================
-
-#XXX: do we really need all these test vectors?
-## test_vectors = [
-## [ "",
-## "$2a$06$DCq7YPn5Rq63x1Lad4cll.",
-## "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." ],
-## [ "",
-## "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
-## "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" ],
-## [ "",
-## "$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
-## "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" ],
-## [ "",
-## "$2a$12$k42ZFHFWqBp3vWli.nIn8u",
-## "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" ],
-##
-## [ "a",
-## "$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
-## "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" ],
-## [ "a",
-## "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
-## "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." ],
-## [ "a",
-## "$2a$10$k87L/MF28Q673VKh8/cPi.",
-## "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" ],
-## [ "a",
-## "$2a$12$8NJH3LsPrANStV6XtBakCe",
-## "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" ],
-##
-## [ "abc",
-## "$2a$06$If6bvum7DFjUnE9p2uDeDu",
-## "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" ],
-## [ "abc",
-## "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
-## "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" ],
-## [ "abc",
-## "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
-## "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" ],
-## [ "abc",
-## "$2a$12$EXRkfkdmXn2gzds2SSitu.",
-## "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" ],
-##
-## [ "abcdefghijklmnopqrstuvwxyz",
-## "$2a$06$.rCVZVOThsIa97pEDOxvGu",
-## "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" ],
-## [ "abcdefghijklmnopqrstuvwxyz",
-## "$2a$08$aTsUwsyowQuzRrDqFflhge",
-## "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." ],
-## [ "abcdefghijklmnopqrstuvwxyz",
-## "$2a$10$fVH8e28OQRj9tqiDXs1e1u",
-## "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" ],
-## [ "abcdefghijklmnopqrstuvwxyz",
-## "$2a$12$D4G5f18o7aMMfwasBL7Gpu",
-## "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" ],
-##
-## [ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
-## "$2a$06$fPIsBO8qRqkjj273rfaOI.",
-## "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" ],
-## [ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
-## "$2a$08$Eq2r4G/76Wv39MzSX262hu",
-## "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" ],
-## [ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
-## "$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
-## "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" ],
-## [ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
-## "$2a$12$WApznUOJfkEGSmYRfnkrPO",
-## "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" ],
-## ]
-
-class BCryptTest(_HandlerTestCase):
- handler = BCrypt
- secret_chars = 72
-
- known_correct = (
- #selected subset of backend test vectors (see above)
- ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
- ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'),
- ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'),
- ('abcdefghijklmnopqrstuvwxyz', '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
- ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'),
- )
-
- known_invalid = [
- #unsupported version
- "$2b$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
- ]
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
- #rounds not zero-padded (pybcrypt rejects this, so so do we)
- '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'
- ]
-
-#NOTE: pybcrypt backend will be chosen as primary if possible,
-#so just check for os crypt and builtin
-OsCrypt_BCryptTest = create_backend_case(BCryptTest, "os_crypt")
-Builtin_BCryptTest = create_backend_case(BCryptTest, "builtin") if enable_option("slow") else None
-
-#=========================================================
-#eof
-#=========================================================
diff --git a/passlib/tests/test_hash_des_crypt.py b/passlib/tests/test_hash_des_crypt.py
deleted file mode 100644
index 7540623..0000000
--- a/passlib/tests/test_hash_des_crypt.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.utils import TestCase, enable_option
-from passlib.tests.handler_utils import _HandlerTestCase
-from passlib.hash.des_crypt import DesCrypt, ExtDesCrypt
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#test frontend class
-#=========================================================
-class DesCryptTest(_HandlerTestCase):
- "test DesCrypt algorithm"
- handler = DesCrypt
- secret_chars = 8
-
- #TODO: test
-
- known_correct = (
- #secret, example hash which matches secret
- ('', 'OgAwTx2l6NADI'),
- (' ', '/Hk.VPuwQTXbc'),
- ('test', 'N1tQbOFcM5fpg'),
- ('Compl3X AlphaNu3meric', 'um.Wguz3eVCx2'),
- ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', 'sNYqfOyauIyic'),
- ('AlOtBsOl', 'cEpWz5IUCShqM'),
- (u'hell\u00D6', 'saykDgk3BPZ9E'),
- )
- known_invalid = [
- #bad char in otherwise correctly formatted hash
- '!gAwTx2l6NADI',
- ]
-
-if enable_option("all-backends") and DesCrypt.get_backend() != "builtin":
-
- class BuiltinDesCryptTest(DesCryptTest):
- case_prefix = "des-crypt (builtin backend)"
-
- def setUp(self):
- self.tmp = self.handler.get_backend()
- self.handler.set_backend("builtin")
-
- def cleanUp(self):
- self.handler.set_backend(self.tmp)
-
-#=========================================================
-#bsdi crypt
-#=========================================================
-class ExtDesCryptTest(_HandlerTestCase):
- "test ExtDesCrypt algorithm"
- handler = ExtDesCrypt
- known_correct = [
- (" ", "_K1..crsmZxOLzfJH8iw"),
- ("my", '_KR/.crsmykRplHbAvwA'), #<- to detect old 12-bit rounds bug
- ("my socra", "_K1..crsmf/9NzZr1fLM"),
- ("my socrates", '_K1..crsmOv1rbde9A9o'),
- ("my socrates note", "_K1..crsm/2qeAhdISMA"),
- ]
- known_invalid = [
- #bad char in otherwise correctly formatted hash
- "_K1.!crsmZxOLzfJH8iw"
- ]
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_md5_crypt.py b/passlib/tests/test_hash_md5_crypt.py
deleted file mode 100644
index 0106700..0000000
--- a/passlib/tests/test_hash_md5_crypt.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.handler_utils import _HandlerTestCase, create_backend_case
-from passlib.tests.utils import enable_option
-from passlib.hash.md5_crypt import Md5Crypt, AprMd5Crypt
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#md5 crypt
-#=========================================================
-class Md5CryptTest(_HandlerTestCase):
- handler = Md5Crypt
-
- known_correct = (
- ('', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
- (' ', '$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'),
- ('test', '$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'),
- ('Compl3X AlphaNu3meric', '$1$nX1e7EeI$ljQn72ZUgt6Wxd9hfvHdV0'),
- ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$1$jQS7o98J$V6iTcr71CGgwW2laf17pi1'),
- ('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'),
- )
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!',
- ]
-
-BuiltinMd5CryptTest = create_backend_case(Md5CryptTest, "builtin")
-
-#=========================================================
-#apr md5 crypt
-#=========================================================
-class AprMd5CryptTest(_HandlerTestCase):
- handler = AprMd5Crypt
-
- #values taken from http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
- known_correct = (
- ('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/'),
- )
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA!'
- ]
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_misc.py b/passlib/tests/test_hash_misc.py
deleted file mode 100644
index a684bcb..0000000
--- a/passlib/tests/test_hash_misc.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.handler_utils import _HandlerTestCase
-from passlib.tests.utils import enable_option
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#NTHASH for unix
-#=========================================================
-from passlib.hash.nthash import NTHash
-
-class NTHashTest(_HandlerTestCase):
- handler = NTHash
-
- known_correct = (
- ('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'),
- ('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'),
- )
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$3$$7f8fe03093cc84b267b109625f6bbfxb',
- ]
-
-#=========================================================
-#PHPass Portable Crypt
-#=========================================================
-from passlib.hash import phpass
-
-class PHPassTest(_HandlerTestCase):
- handler = phpass.PHPass
-
- known_correct = (
- ('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'),
- ('compL3X!', '$P$FiS0N5L672xzQx1rt1vgdJQRYKnQM9/'),
- ('test12345', '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'), #from the source
- )
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r!L0',
- ]
-
-#=========================================================
-# netbsd sha1 crypt
-#=========================================================
-from passlib.hash import sha1_crypt
-
-class SHA1CryptTest(_HandlerTestCase):
- handler = sha1_crypt.SHA1Crypt
-
- known_correct = (
- ("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"),
- ("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"),
- )
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH',
- ]
-
-#=========================================================
-#sun md5 crypt
-#=========================================================
-from passlib.hash.sun_md5_crypt import SunMD5Crypt
-
-class SunMD5CryptTest(_HandlerTestCase):
- handler = SunMD5Crypt
-
- known_correct = [
- #sample hash found at http://compgroups.net/comp.unix.solaris/password-file-in-linux-and-solaris-8-9
- ("passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"),
- ]
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- "$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/"
- ]
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_mysql.py b/passlib/tests/test_hash_mysql.py
deleted file mode 100644
index 1d58426..0000000
--- a/passlib/tests/test_hash_mysql.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.handler_utils import _HandlerTestCase
-from passlib.hash.mysql import MySQL_323, MySQL_41
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#database hashes
-#=========================================================
-class MySQL_323Test(_HandlerTestCase):
- handler = MySQL_323
-
- #remove single space from secrets, since mysql-323 ignores all whitespace (?!)
- standard_secrets = [ x for x in _HandlerTestCase.standard_secrets if x != ' ' ]
-
- known_correct = (
- ('mypass', '6f8c114b58f2ce9e'),
- )
- known_invalid = [
- #bad char in otherwise correct hash
- '6z8c114b58f2ce9e',
- ]
-
- def test_whitespace(self):
- "check whitespace is ignored per spec"
- h = self.do_encrypt("mypass")
- h2 = self.do_encrypt("my pass")
- self.assertEqual(h, h2)
-
-class MySQL_41Test(_HandlerTestCase):
- handler = MySQL_41
- known_correct = (
- ('mypass', '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'),
- )
- known_invalid = [
- #bad char in otherwise correct hash
- '*6Z8989366EAF75BB670AD8EA7A7FC1176A95CEF4',
- ]
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_postgres.py b/passlib/tests/test_hash_postgres.py
deleted file mode 100644
index a2ad924..0000000
--- a/passlib/tests/test_hash_postgres.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.handler_utils import _HandlerTestCase
-from passlib.hash.postgres import PostgresMD5
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#database hashes
-#=========================================================
-class PostgresMD5CryptTest(_HandlerTestCase):
- handler = PostgresMD5
- known_correct = [
- # ((secret,user),hash)
- (('mypass', 'postgres'), 'md55fba2ea04fd36069d2574ea71c8efe9d'),
- (('mypass', 'root'), 'md540c31989b20437833f697e485811254b'),
- (("testpassword",'testuser'), 'md5d4fc5129cc2c25465a5370113ae9835f'),
- ]
- known_invalid = [
- #bad 'z' char in otherwise correct hash
- 'md54zc31989b20437833f697e485811254b',
- ]
-
- #NOTE: used to support secret=(password, user) format, but removed it for now.
- ##def test_tuple_mode(self):
- ## "check tuple mode works for encrypt/verify"
- ## self.assertEquals(self.handler.encrypt(('mypass', 'postgres')),
- ## 'md55fba2ea04fd36069d2574ea71c8efe9d')
- ## self.assertEquals(self.handler.verify(('mypass', 'postgres'),
- ## 'md55fba2ea04fd36069d2574ea71c8efe9d'), True)
-
- def test_user(self):
- "check user kwd is required for encrypt/verify"
- self.assertRaises(TypeError, self.handler.encrypt, 'mypass')
- self.assertRaises(TypeError, self.handler.verify, 'mypass', 'md55fba2ea04fd36069d2574ea71c8efe9d')
-
- def do_concat(self, secret, prefix):
- if isinstance(secret, tuple):
- secret, user = secret
- secret = prefix + secret
- return secret, user
- else:
- return prefix + secret
-
- def do_encrypt(self, secret, **kwds):
- if isinstance(secret, tuple):
- secret, user = secret
- else:
- user = 'default'
- assert 'user' not in kwds
- kwds['user'] = user
- return self.handler.encrypt(secret, **kwds)
-
- def do_verify(self, secret, hash):
- if isinstance(secret, tuple):
- secret, user = secret
- else:
- user = 'default'
- return self.handler.verify(secret, hash, user=user)
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_sha_crypt.py b/passlib/tests/test_hash_sha_crypt.py
deleted file mode 100644
index d3ee9a8..0000000
--- a/passlib/tests/test_hash_sha_crypt.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-import re
-try:
- from warnings import catch_warnings
-except ImportError: #wasn't added until py26
- catch_warnings = None
-import warnings
-#site
-#pkg
-from passlib.tests.utils import TestCase, enable_option
-from passlib.tests.handler_utils import _HandlerTestCase, create_backend_case
-from passlib.hash.sha2_crypt import SHA256Crypt, SHA512Crypt
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#test sha256-crypt
-#=========================================================
-class SHA256CryptTest(_HandlerTestCase):
- handler = SHA256Crypt
- supports_unicode = True
-
- known_correct = [
- ('', '$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'),
- (' ', '$5$rounds=10376$I5lNtXtRmf.OoMd8$Ko3AI1VvTANdyKhBPavaRjJzNpSatKU6QVN9uwS9MH.'),
- ('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'),
- ('Compl3X AlphaNu3meric', '$5$rounds=10350$o.pwkySLCzwTdmQX$nCMVsnF3TXWcBPOympBUUSQi6LGGloZoOsVJMGJ09UB'),
- ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$5$rounds=11944$9dhlu07dQMRWvTId$LyUI5VWkGFwASlzntk1RLurxX54LUhgAcJZIt0pYGT7'),
- (u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'),
- ]
-
- known_identified_invalid = [
- #bad char in otherwise correct hash
- '$5$rounds=10428$uy/:jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMeZGsGx2aBvxTvDFI613c3',
-
- #zero-padded rounds
- '$5$rounds=010428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3',
- ]
-
-BuiltinSHA256CryptTest = create_backend_case(SHA256CryptTest, "builtin")
-
-#=========================================================
-#test sha512-crypt
-#=========================================================
-class SHA512CryptTest(_HandlerTestCase):
- handler = SHA512Crypt
- supports_unicode = True
-
- known_correct = [
- ('', '$6$rounds=11021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1'),
- (' ', '$6$rounds=11104$ED9SA4qGmd57Fq2m$q/.PqACDM/JpAHKmr86nkPzzuR5.YpYa8ZJJvI8Zd89ZPUYTJExsFEIuTYbM7gAGcQtTkCEhBKmp1S1QZwaXx0'),
- ('test', '$6$rounds=11531$G/gkPn17kHYo0gTF$Kq.uZBHlSBXyzsOJXtxJruOOH4yc0Is13uY7yK0PvAvXxbvc1w8DO1RzREMhKsc82K/Jh8OquV8FZUlreYPJk1'),
- ('Compl3X AlphaNu3meric', '$6$rounds=10787$wakX8nGKEzgJ4Scy$X78uqaX1wYXcSCtS4BVYw2trWkvpa8p7lkAtS9O/6045fK4UB2/Jia0Uy/KzCpODlfVxVNZzCCoV9s2hoLfDs/'),
- ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$6$rounds=11065$5KXQoE1bztkY5IZr$Jf6krQSUKKOlKca4hSW07MSerFFzVIZt/N3rOTsUgKqp7cUdHrwV8MoIVNCk9q9WL3ZRMsdbwNXpVk0gVxKtz1'),
- ]
-
- known_identified_invalid = [
- #zero-padded rounds
- '$6$rounds=011021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1',
- #bad char in otherwise correct hash
- '$6$rounds=11021$KsvQipYPWpr9:wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1',
- ]
-
- #NOTE: these test cases taken from spec definition at http://www.akkadia.org/drepper/SHA-crypt.txt
- cases512 = [
- #salt-hash, secret, result
- ("$6$saltstring", "Hello world!",
- "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
- "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" ),
-
- ( "$6$rounds=10000$saltstringsaltstring", "Hello world!",
- "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
- "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." ),
-
- ( "$6$rounds=5000$toolongsaltstring", "This is just a test",
- "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ"
- "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" ),
-
- ( "$6$rounds=1400$anotherlongsaltstring",
- "a very much longer text to encrypt. This one even stretches over more"
- "than one line.",
- "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP"
- "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" ),
-
- ( "$6$rounds=77777$short",
- "we have a short salt string but not a short password",
- "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g"
- "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" ),
-
- ( "$6$rounds=123456$asaltof16chars..", "a short string",
- "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
- "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" ),
-
- ( "$6$rounds=10$roundstoolow", "the minimum number is still observed",
- "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
- "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." ),
- ]
-
- def test_spec_vectors(self):
- "verify sha512-crypt passes specification test vectors"
- handler = self.handler
-
- #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here
- if catch_warnings:
- ctx = catch_warnings()
- ctx.__enter__()
- warnings.filterwarnings("ignore", "sha512_crypt does not allow less than 1000 rounds: 10", UserWarning)
-
- for config, secret, hash in self.cases512:
- #make sure we got expected result back
- result = handler.genhash(secret, config)
- self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret))
-
- #make sure parser truncated salts
- info = handler.from_string(config)
- self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret))
-
- if catch_warnings:
- ctx.__exit__(None,None,None)
-
-BuiltinSHA512CryptTest = create_backend_case(SHA512CryptTest, "builtin")
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_hash_sun_md5_crypt.py b/passlib/tests/test_hash_sun_md5_crypt.py
deleted file mode 100644
index 964974a..0000000
--- a/passlib/tests/test_hash_sun_md5_crypt.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
-#=========================================================
-#imports
-#=========================================================
-from __future__ import with_statement
-#core
-import hashlib
-from logging import getLogger
-#site
-#pkg
-from passlib.tests.handler_utils import _HandlerTestCase
-#module
-log = getLogger(__name__)
-
-#=========================================================
-#EOF
-#=========================================================
diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py
index 0eec054..3855864 100644
--- a/passlib/tests/test_utils.py
+++ b/passlib/tests/test_utils.py
@@ -91,13 +91,46 @@ class BytesTest(TestCase):
self.assertEqual(utils.bytes_to_list('\x00\x00\x01', order="native"), [0, 0, 1])
#=========================================================
-#test des library
+#test slow_bcrypt module
+#=========================================================
+from passlib.utils import _slow_bcrypt as slow_bcrypt
+
+class BCryptUtilTest(TestCase):
+ "test passlib.utils._slow_bcrypt utility funcs"
+
+ def test_encode64(self):
+ encode = slow_bcrypt.encode_base64
+ self.assertFunctionResults(encode, [
+ ('', ''),
+ ('..', '\x00'),
+ ('...', '\x00\x00'),
+ ('....', '\x00\x00\x00'),
+ ('9u', '\xff'),
+ ('996', '\xff\xff'),
+ ('9999', '\xff\xff\xff'),
+ ])
+
+ def test_decode64(self):
+ decode = slow_bcrypt.decode_base64
+ self.assertFunctionResults(decode, [
+ ('', ''),
+ ('\x00', '..'),
+ ('\x00\x00', '...'),
+ ('\x00\x00\x00', '....'),
+ ('\xff', '9u', ),
+ ('\xff\xff','996'),
+ ('\xff\xff\xff','9999'),
+ ])
+
+
+#=========================================================
+#test des module
#=========================================================
class DesTest(TestCase):
#test vectors taken from http://www.skepticfiles.org/faq/testdes.htm
- #(key, plaintext, ciphertext) all as 64 bit
+ #data is list of (key, plaintext, ciphertext), all as 64 bit hex string
test_des_vectors = [
(line[4:20], line[21:37], line[38:54])
for line in
diff --git a/passlib/tests/test_handler.py b/passlib/tests/test_utils_drivers.py
index a58c0b3..1e96df0 100644
--- a/passlib/tests/test_handler.py
+++ b/passlib/tests/test_utils_drivers.py
@@ -10,8 +10,8 @@ from logging import getLogger
#site
#pkg
from passlib.utils import rng, getrandstr
-from passlib.utils.handlers import ExtHandler, StaticHandler
-from passlib.tests.handler_utils import _HandlerTestCase
+from passlib.utils.drivers import ExtHash, StaticHash
+from passlib.tests.utils import HandlerCase
#module
log = getLogger(__name__)
@@ -20,7 +20,7 @@ log = getLogger(__name__)
# to test the unittests themselves, as well as other
# parts of passlib. they shouldn't be used as actual password schemes.
#=========================================================
-class UnsaltedHash(StaticHandler):
+class UnsaltedHash(StaticHash):
"example algorithm which lacks a salt"
name = "unsalted_example"
@@ -42,7 +42,7 @@ class UnsaltedHash(StaticHandler):
def calc_checksum(self, secret):
return hashlib.sha1("boblious" + secret).hexdigest()
-class SaltedHash(ExtHandler):
+class SaltedHash(ExtHash):
"example algorithm with a salt"
name = "salted_example"
setting_kwds = ("salt",)
@@ -69,18 +69,18 @@ class SaltedHash(ExtHandler):
return hashlib.sha1(self.salt + secret + self.salt).hexdigest()
#=========================================================
-#test sample algorithms - really a self-test of _HandlerTestCase
+#test sample algorithms - really a self-test of HandlerCase
#=========================================================
#TODO: provide data samples for algorithms
# (positive knowns, negative knowns, invalid identify)
-class UnsaltedHashTest(_HandlerTestCase):
+class UnsaltedHashTest(HandlerCase):
handler = UnsaltedHash
known_correct = []
-class SaltedHashTest(_HandlerTestCase):
+class SaltedHashTest(HandlerCase):
handler = SaltedHash
known_correct = []
diff --git a/passlib/tests/test_utils_pbkdf2.py b/passlib/tests/test_utils_pbkdf2.py
index ba05a88..ec0859e 100644
--- a/passlib/tests/test_utils_pbkdf2.py
+++ b/passlib/tests/test_utils_pbkdf2.py
@@ -16,6 +16,12 @@ import passlib.utils.pbkdf2 as mod
log = getLogger(__name__)
#=========================================================
+#
+#=========================================================
+
+#TODO: should we bother testing hmac_sha1() function?
+
+#=========================================================
#test activate backend (stored in mod._crypt)
#=========================================================
class _Pbkdf2BackendTest(TestCase):
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index d2d1447..19d8891 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -3,19 +3,53 @@
#imports
#=========================================================
#core
+import logging; log = logging.getLogger(__name__)
+import re
import os
import unittest
-import logging; log = logging.getLogger(__name__)
+#site
+from nose.plugins.skip import SkipTest
#pkg
+from passlib.utils import classproperty
+from passlib.utils.drivers import BaseHash, BackendMixin
#local
__all__ = [
- 'TestCase',
- 'Param',
+ #util funcs
'enable_option',
+ 'Params',
+
+ #unit testing
+ 'TestCase',
+ 'HandlerCase',
+ 'enable_backend_case',
+ 'create_backend_case',
]
#=========================================================
-#helper for assertFunctionResults() method
+#option flags
+#=========================================================
+DEFAULT_TESTS = "active-backends"
+
+tests = [
+ v.strip()
+ for v
+ in os.environ.get("PASSLIB_TESTS", DEFAULT_TESTS).lower().split(",")
+ ]
+
+def enable_option(*names):
+ """check if a given test should be included based on the env var.
+
+ test flags:
+ all run ALL tests
+ active-backends test active backends
+ all-backends test ALL backends, even the inactive ones
+
+ slow required to enable really slow tests (eg builtin bcrypt backend)
+ """
+ return 'all' in tests or any(name in tests for name in names)
+
+#=========================================================
+#misc utility funcs
#=========================================================
class Params(object):
"helper to represent params for function call"
@@ -151,28 +185,343 @@ class TestCase(unittest.TestCase):
return template
#=========================================================
-#helper funcs
+#other unittest helpers
#=========================================================
+class HandlerCase(TestCase):
+ """base class for testing password hash drivers (esp passlib.utils.drivers.BaseHash subclasses)
-DEFAULT_TESTS = "active-backends"
+ .. todo::
+ write directions on how to use this class.
+ for now, see examples in places such as test_unix_crypt
+ """
+ @classproperty
+ def __test__(cls):
+ #so nose won't auto run *this* cls, but it will for subclasses
+ return cls is not HandlerCase
-tests = [
- v.strip()
- for v
- in os.environ.get("PASSLIB_TESTS", DEFAULT_TESTS).lower().split(",")
+ #=========================================================
+ #attrs to be filled in by subclass for testing specific handler
+ #=========================================================
+
+ #specify handler object here
+ handler = None
+
+ #this option is available for hashes which can't handle unicode
+ supports_unicode = False
+
+ #maximum number of chars which hash will include in checksum
+ #override this only if hash doesn't use all chars (the default)
+ secret_chars = -1
+
+ #list of (secret,hash) pairs which handler should verify as matching
+ known_correct = []
+
+ #list of (secret,hash) pairs which handler should verify as NOT matching
+ known_incorrect = []
+
+ # list of handler's hashes with crucial invalidating typos, that handler shouldn't identify as belonging to it
+ known_invalid = []
+
+ # list of handler's hashes that it *will* identify as it's own, but genhash will raise error due to invalid internal requirements
+ known_identified_invalid = []
+
+ #list of (name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it
+ #this list should generally be sufficient (if handler name in list, that entry will be skipped)
+ known_other = [
+ ('des_crypt', '6f8c114b58f2c'),
+ ('md5_crypt', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
+ ('sha512_crypt', "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
+ "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"),
]
-def enable_option(*names):
- """check if a given test should be included based on the env var.
+ #list of various secrets all algs are tested with to make sure they work
+ standard_secrets = [
+ '',
+ ' ',
+ 'my socrates note',
+ 'Compl3X AlphaNu3meric',
+ '4lpHa N|_|M3r1K W/ Cur51|\\|g: #$%(*)(*%#',
+ 'Really Long Password (tm), which is all the rage nowadays. Maybe some Shakespeare?',
+ ]
- test flags:
- all run ALL tests
- active-backends test active backends
- all-backends test ALL backends, even the inactive ones
+ unicode_secrets = [
+ u'test with unic\u00D6de',
+ ]
- slow required to enable really slow tests (eg builtin bcrypt backend)
- """
- return 'all' in tests or any(name in tests for name in names)
+ #optional prefix to prepend to name of test method as it's called,
+ #useful when multiple handler test classes being run.
+ #default behavior should be sufficient
+ def case_prefix(self):
+ name = self.handler.name if self.handler else self.__class__.__name__
+ backend = getattr(self.handler, "get_backend", None) #set by some of the builtin handlers
+ if backend:
+ name += " (%s backend)" % (backend(),)
+ return name
+
+ #=========================================================
+ #alg interface helpers - allows subclass to overide how
+ # default tests invoke the handler (eg for context_kwds)
+ #=========================================================
+ def do_concat(self, secret, prefix):
+ "concatenate prefix onto secret"
+ #NOTE: this is subclassable mainly for some algorithms
+ #which accept non-strings in secret
+ return prefix + secret
+
+ def do_encrypt(self, secret, **kwds):
+ "call handler's encrypt method with specified options"
+ return self.handler.encrypt(secret, **kwds)
+
+ def do_verify(self, secret, hash):
+ "call handler's verify method"
+ return self.handler.verify(secret, hash)
+
+ def do_identify(self, hash):
+ "call handler's identify method"
+ return self.handler.identify(hash)
+
+ #=========================================================
+ #attributes
+ #=========================================================
+ def test_00_attributes(self):
+ "test handler attributes are all defined"
+ handler = self.handler
+ def ga(name):
+ return getattr(handler, name, None)
+
+ name = ga("name")
+ self.assert_(name, "name not defined:")
+ self.assert_(name.lower() == name, "name not lower-case:")
+ self.assert_(re.match("^[a-z0-9_]+$", name), "name must be alphanum + underscore: %r" % (name,))
+
+ def test_01_base_handler(self):
+ "check configuration of BaseHash-derived classes"
+ h = self.handler
+ if not isinstance(h, type) or not issubclass(h, BaseHash):
+ raise SkipTest
+ h.validate_class() #should raise AssertionError if something's wrong.
+
+ #=========================================================
+ #identify
+ #=========================================================
+ def test_10_identify_other(self):
+ "test identify() against other schemes' hashes"
+ for name, hash in self.known_other:
+ self.assertEqual(self.do_identify(hash), name == self.handler.name)
+
+ def test_11_identify_positive(self):
+ "test identify() against scheme's own hashes"
+ for secret, hash in self.known_correct:
+ self.assertEqual(self.do_identify(hash), True)
+
+ for secret, hash in self.known_incorrect:
+ self.assertEqual(self.do_identify(hash), True)
+
+ for hash in self.known_identified_invalid:
+ self.assertEqual(self.do_identify(hash), True)
+
+ def test_12_identify_invalid(self):
+ "test identify() against malformed instances of scheme's own hashes"
+ if not self.known_invalid:
+ raise SkipTest
+ for hash in self.known_invalid:
+ self.assertEqual(self.do_identify(hash), False, "hash=%r:" % (hash,))
+
+ def test_13_identify_none(self):
+ "test identify() against None / empty string"
+ self.assertEqual(self.do_identify(None), False)
+ self.assertEqual(self.do_identify(''), False)
+
+ #=========================================================
+ #verify
+ #=========================================================
+ def test_20_verify_positive(self):
+ "test verify() against known-correct secret/hash pairs"
+ self.assert_(self.known_correct, "test must define known_correct hashes")
+ for secret, hash in self.known_correct:
+ self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash))
+
+ def test_21_verify_negative(self):
+ "test verify() against known-incorrect secret/hash pairs"
+ if not self.known_incorrect:
+ raise SkipTest
+ for secret, hash in self.known_incorrect:
+ self.assertEqual(self.do_verify(secret, hash), False)
+
+ #XXX: is this needed if known_incorrect is defined?
+ def test_22_verify_derived_negative(self):
+ "test verify() against derived incorrect secret/hash pairs"
+ for secret, hash in self.known_correct:
+ self.assertEqual(self.do_verify(self.do_concat(secret,'x'), hash), False)
+
+ def test_23_verify_other(self):
+ "test verify() throws error against other algorithm's hashes"
+ for name, hash in self.known_other:
+ if name == self.handler.name:
+ continue
+ self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify other %r %r:" % (name, hash))
+
+ def test_24_verify_invalid(self):
+ "test verify() throws error against known-invalid hashes"
+ if not self.known_invalid and not self.known_identified_invalid:
+ raise SkipTest
+ for hash in self.known_invalid + self.known_identified_invalid:
+ self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify invalid %r:" % (hash,))
+
+ def test_25_verify_none(self):
+ "test verify() throws error against hash=None/empty string"
+ #find valid hash so that doesn't mask error
+ self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="verify None:")
+ self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="verify empty:")
+
+ #=========================================================
+ #encrypt
+ #=========================================================
+
+ #---------------------------------------------------------
+ #test encryption against various secrets
+ #---------------------------------------------------------
+ def test_30_encrypt_standard(self):
+ "test encrypt() against standard secrets"
+ for secret in self.standard_secrets:
+ self.check_encrypt(secret)
+
+ def test_31_encrypt_unicode(self):
+ "test encrypt() against unicode secrets"
+ if not self.supports_unicode:
+ raise SkipTest
+ for secret in self.unicode_secrets:
+ self.check_encrypt(secret)
+
+ #this is probably excessive
+ ##def test_32_encrypt_positive(self):
+ ## "test encrypt() against known-correct secret/hash pairs"
+ ## for secret, hash in self.known_correct:
+ ## self.check_encrypt(secret)
+
+ def check_encrypt(self, secret):
+ "check encrypt() behavior for a given secret"
+ #hash the secret
+ hash = self.do_encrypt(secret)
+
+ #test identification
+ self.assertEqual(self.do_identify(hash), True, "identify hash %r from secret %r:" % (hash, secret))
+
+ #test positive verification
+ self.assertEqual(self.do_verify(secret, hash), True, "verify hash %r from secret %r:" % (hash, secret))
+
+ #test negative verification
+ for other in ['', 'test', self.do_concat(secret,'x')]:
+ if other != secret:
+ self.assertEqual(self.do_verify(other, hash), False,
+ "hash collision: %r and %r => %r" % (secret, other, hash))
+
+ #---------------------------------------------------------
+ #test salt handling
+ #---------------------------------------------------------
+ def test_33_encrypt_gensalt(self):
+ "test encrypt() generates new salt each time"
+ if 'salt' not in self.handler.setting_kwds:
+ raise SkipTest
+ for secret, hash in self.known_correct:
+ hash2 = self.do_encrypt(secret)
+ self.assertNotEqual(hash, hash2)
+
+ #TODO: test too-short user-provided salts
+ #TODO: test too-long user-provided salts
+ #TODO: test invalid char in user-provided salts
+
+ #---------------------------------------------------------
+ #test secret handling
+ #---------------------------------------------------------
+ def test_37_secret_chars(self):
+ "test secret_chars limit"
+ sc = self.secret_chars
+
+ base = "too many secrets" #16 chars
+ alt = 'x' #char that's not in base string
+
+ if sc > 0:
+ #hash only counts the first <sc> characters
+ #eg: bcrypt, des-crypt
+
+ #create & hash something of exactly sc+1 chars
+ secret = (base * (1+sc//16))[:sc+1]
+ assert len(secret) == sc+1
+ hash = self.do_encrypt(secret)
+
+ #check sc value isn't too large
+ #by verifying that sc-1'th char affects hash
+ self.assert_(not self.do_verify(secret[:-2] + alt + secret[-1], hash), "secret_chars value is too large")
+
+ #check sc value isn't too small
+ #by verifying adding sc'th char doesn't affect hash
+ self.assert_(self.do_verify(secret[:-1] + alt, hash))
+
+ else:
+ #hash counts all characters
+ #eg: md5-crypt
+ self.assertEquals(sc, -1)
+
+ #NOTE: this doesn't do an exhaustive search to verify algorithm
+ #doesn't have some cutoff point, it just tries
+ #1024-character string, and alters the last char.
+ #as long as algorithm doesn't clip secret at point <1024,
+ #the new secret shouldn't verify.
+ secret = base * 64
+ hash = self.do_encrypt(secret)
+ self.assert_(not self.do_verify(secret[:-1] + alt, hash))
+
+ def test_38_encrypt_none(self):
+ "test encrypt() refused secret=None"
+ self.assertRaises(TypeError, self.do_encrypt, None)
+
+ #=========================================================
+ #
+ #=========================================================
+
+ #TODO: check genhash works
+ #TODO: check genconfig works
+
+ #TODO: check parse method works
+ #TODO: check render method works
+ #TODO: check default/min/max_rounds valid if present
+
+ #=========================================================
+ #eoc
+ #=========================================================
+
+#=========================================================
+#backend test helpers
+#=========================================================
+def enable_backend_case(handler, name):
+ "helper to check if a separate test is needed for the specified backend"
+ assert issubclass(handler, BackendMixin), "handler must derived from BackendMixin"
+ assert name in handler.backends, "unknown backend: %r" % (name,)
+ return enable_option("all-backends") and handler.get_backend() != name and handler.has_backend(name)
+
+def create_backend_case(base_test, name):
+ "create a test case (subclassing); if test doesn't need to be enabled, returns None"
+ handler = base_test.handler
+
+ if not enable_backend_case(handler, name):
+ return None
+
+ assert getattr(base_test, "setUp", None) is None #just haven't implemented this
+ assert getattr(base_test, "cleanUp", None) is None #ditto
+
+ class dummy(base_test):
+ case_prefix = "%s (%s backend)" % (handler.name, name)
+
+ def setUp(self):
+ self.orig_backend = self.handler.get_backend()
+ self.handler.set_backend(name)
+
+ def cleanUp(self):
+ self.handler.set_backend(self.orig_backend)
+
+ dummy.__name__ = name.title() + base_test.__name__
+ return dummy
#=========================================================
#EOF
diff --git a/passlib/unix.py b/passlib/unix.py
index 32616db..319f9cc 100644
--- a/passlib/unix.py
+++ b/passlib/unix.py
@@ -5,7 +5,7 @@
#=========================================================
#pkg
from passlib.base import CryptContext, register_crypt_handler
-from passlib.utils.handlers import CryptHandler
+from passlib.utils.drivers import CryptHandler
#local
__all__ = [
"default_context",
@@ -74,7 +74,7 @@ linux_context = CryptContext([ "unix_disabled", "des_crypt", "md5_crypt", "sha25
#referencing source via -http://fxr.googlebit.com
# freebsd 6,7,8 - des, md5, bcrypt, nthash
-# netbsd - des, ext, md5, bcrypt, sha1
+# netbsd - des, ext, md5, bcrypt, sha1
# openbsd - des, ext, md5, bcrypt
bsd_context = CryptContext(["unix_disabled", "nthash", "des_crypt", "ext_des_crypt", "md5_crypt", "bcrypt"])
freebsd_context = CryptContext([ "unix_disabled", "des_crypt", "nthash", "md5_crypt", "bcrypt"])
diff --git a/passlib/utils/_slow_bcrypt.py b/passlib/utils/_slow_bcrypt.py
index 86208d6..5f68add 100644
--- a/passlib/utils/_slow_bcrypt.py
+++ b/passlib/utils/_slow_bcrypt.py
@@ -480,7 +480,7 @@ def iter_cyclic_words(data):
#=========================================================
#base bcrypt helper
#
-#interface designed only for use by passlib.hash.bcrypt.BCrypt
+#interface designed only for use by passlib.drivers.bcrypt.BCrypt
#probably not suitable for other purposes
#=========================================================
diff --git a/passlib/utils/handlers.py b/passlib/utils/drivers.py
index 4e5be97..d2dc248 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/drivers.py
@@ -19,19 +19,19 @@ from passlib.utils import classproperty, h64, getrandstr, rng, is_crypt_handler
__all__ = [
#framework for implementing handlers
- 'BaseHandler',
- 'ExtHandler',
- 'StaticHandler',
+ 'BaseHash',
+ 'ExtHash',
+ 'StaticHash',
'BackendMixin',
- 'BackendExtHandler',
- 'BackendStaticHandler',
+ 'BackendExtHash',
+ 'BackendStaticHash',
]
#=========================================================
#base handler
#=========================================================
-class BaseHandler(object):
+class BaseHash(object):
"""helper for implementing password hash handler with minimal methods
hash implementations should fill out the following:
@@ -134,12 +134,12 @@ class BaseHandler(object):
#=====================================================
#=========================================================
-# ExtHandler
+# ExtHash
# rounds+salt+xtra phpass, sha256_crypt, sha512_crypt
# rounds+salt bcrypt, ext_des_crypt, sha1_crypt, sun_md5_crypt
# salt only apr_md5_crypt, des_crypt, md5_crypt
#=========================================================
-class ExtHandler(BaseHandler):
+class ExtHash(BaseHash):
"""helper class for implementing hash schemes
hash implementations should fill out the following:
@@ -174,8 +174,8 @@ class ExtHandler(BaseHandler):
#----------------------------------------------
#password hash api - required attributes
#----------------------------------------------
- name = None #required by ExtHandler
- setting_kwds = None #required by ExtHandler
+ name = None #required by ExtHash
+ setting_kwds = None #required by ExtHash
context_kwds = ()
#----------------------------------------------
@@ -187,7 +187,7 @@ class ExtHandler(BaseHandler):
#----------------------------------------------
#salt information
#----------------------------------------------
- max_salt_chars = None #required by ExtHandler.norm_salt()
+ max_salt_chars = None #required by ExtHash.norm_salt()
@classproperty
def min_salt_chars(cls):
@@ -209,15 +209,15 @@ class ExtHandler(BaseHandler):
#rounds information
#----------------------------------------------
min_rounds = 0
- max_rounds = None #required by ExtHandler.norm_rounds()
- default_rounds = None #if not specified, ExtHandler.norm_rounds() will require explicit rounds value every time
+ max_rounds = None #required by ExtHash.norm_rounds()
+ default_rounds = None #if not specified, ExtHash.norm_rounds() will require explicit rounds value every time
rounds_cost = "linear" #common case
#----------------------------------------------
- #misc ExtHandler configuration
+ #misc ExtHash configuration
#----------------------------------------------
_strict_rounds_bounds = False #if true, always raises error if specified rounds values out of range - required by spec for some hashes
- _extra_init_settings = () #settings that ExtHandler.__init__ should handle by calling norm_<key>()
+ _extra_init_settings = () #settings that ExtHash.__init__ should handle by calling norm_<key>()
#=========================================================
#instance attributes
@@ -242,12 +242,12 @@ class ExtHandler(BaseHandler):
norm = getattr(self, "norm_" + key)
value = norm(value, strict=strict)
setattr(self, key, value)
- super(ExtHandler, self).__init__(**kwds)
+ super(ExtHash, self).__init__(**kwds)
@classmethod
def validate_class(cls):
"helper to ensure class is configured property"
- super(ExtHandler, cls).validate_class()
+ super(ExtHash, cls).validate_class()
if any(k not in cls.setting_kwds for k in cls._extra_init_settings):
raise AssertionError, "_extra_init_settings must be subset of setting_kwds"
@@ -293,16 +293,16 @@ class ExtHandler(BaseHandler):
@classproperty
def _has_salt(cls):
"attr for checking if salts are supported, memoizes itself on first use"
- if cls is ExtHandler:
- raise RuntimeError, "not allowed for ExtHandler directly"
+ if cls is ExtHash:
+ raise RuntimeError, "not allowed for ExtHash directly"
value = cls._has_salt = 'salt' in cls.setting_kwds
return value
@classproperty
def _has_rounds(cls):
"attr for checking if variable are supported, memoizes itself on first use"
- if cls is ExtHandler:
- raise RuntimeError, "not allowed for ExtHandler directly"
+ if cls is ExtHash:
+ raise RuntimeError, "not allowed for ExtHash directly"
value = cls._has_rounds = 'rounds' in cls.setting_kwds
return value
@@ -503,17 +503,17 @@ class ExtHandler(BaseHandler):
#=========================================================
#static - mysql_323, mysql_41, nthash, postgres_md5
#=========================================================
-class StaticHandler(ExtHandler):
+class StaticHash(ExtHash):
"""helper class optimized for implementing hash schemes which have NO settings whatsoever.
- the main thing this changes from ExtHandler:
+ the main thing this changes from ExtHash:
* :attr:`setting_kwds` must be an empty tuple (set by class)
* :meth:`genconfig` takes no kwds, and always returns ``None``.
* :meth:`genhash` accepts ``config=None``.
otherwise, this requires the same methods be implemented
- as does ExtHandler.
+ as does ExtHash.
"""
#=========================================================
#class attr
@@ -527,8 +527,8 @@ class StaticHandler(ExtHandler):
def validate_class(cls):
"helper to validate that class has been configured properly"
if cls.setting_kwds:
- raise AssertionError, "StaticHandler subclasses must not have any settings, perhaps you want ExtHandler?"
- super(StaticHandler, cls).validate_class()
+ raise AssertionError, "StaticHash subclasses must not have any settings, perhaps you want ExtHash?"
+ super(StaticHash, cls).validate_class()
#=========================================================
#primary interface
@@ -605,10 +605,10 @@ class BackendMixin(object):
assert self._backend, "set_backend() failed to load a default backend"
return self.calc_checksum(secret)
-class BackendExtHandler(BackendMixin, ExtHandler):
+class BackendExtHash(BackendMixin, ExtHash):
pass
-class BackendStaticHandler(BackendMixin, StaticHandler):
+class BackendStaticHash(BackendMixin, StaticHash):
pass
#=========================================================
diff --git a/passlib/win32.py b/passlib/win32.py
index 31bc18f..4c6650c 100644
--- a/passlib/win32.py
+++ b/passlib/win32.py
@@ -18,7 +18,7 @@ this module provided two functions to aid in any use-cases which exist.
.. autofunction:: raw_lmhash
.. autofunction:: raw_nthash
-See also :mod:`passlib.hash.nthash`.
+See also :mod:`passlib.drivers.nthash`.
"""
#=========================================================
#imports
@@ -28,8 +28,8 @@ from binascii import hexlify
#site
#pkg
from passlib.utils.des import des_encrypt_block
-from passlib.hash import nthash
-from passlib.hash.nthash import raw_nthash
+from passlib.drivers.import nthash
+from passlib.drivers.nthash import raw_nthash
#local
__all__ = [
"nthash",