diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-16 16:13:52 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-16 16:13:52 -0500 |
| commit | 4cfd0bb3647b25ee1a7a3f54d636ef72244f7fcb (patch) | |
| tree | 92c9184188e2cc64e2c80274076ca4bebe70742b | |
| parent | 3eefcd1f31c534cced89139ebf72d36ceafc3a1c (diff) | |
| download | passlib-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)
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", |
