summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-16 16:13:52 -0500
committerEli Collins <elic@assurancetechnologies.com>2011-02-16 16:13:52 -0500
commit4cfd0bb3647b25ee1a7a3f54d636ef72244f7fcb (patch)
tree92c9184188e2cc64e2c80274076ca4bebe70742b /passlib
parent3eefcd1f31c534cced89139ebf72d36ceafc3a1c (diff)
downloadpasslib-4cfd0bb3647b25ee1a7a3f54d636ef72244f7fcb.tar.gz
large rearrangment of structure
=============================== * moved passlib.hash package to passlib.drivers * combined some passlib.driver modules together (eg ext_des_crypt merged w/ des_crypt) * renamed all hash classes to lower case, to match name attrs. * renamed ext_des_crypt to bsdi_crypt * added special proxy module "passlib.hash" which lazily loads drivers from correct location - registry system reworked, has list of locations for builtin drivers, and capability for apps to add more - passlib.hash *is* the registry, changes to it affect list - thanks to this, documentation can remain, pointing to passlib.hash.xxx as location for driver * moved passlib.utils.handlers to passlib.utils.drivers, renamed classes from XxxHandler -> XxxHash * combined all driver tests into single passlib/tests/test_drivers.py file * NOTE: disabled default unicode testing, not ready for that yet * all driver UTs pass (others not checked)
Diffstat (limited to 'passlib')
-rw-r--r--passlib/__init__.py9
-rw-r--r--passlib/apache.py2
-rw-r--r--passlib/base.py164
-rw-r--r--passlib/drivers/__init__.py2
-rw-r--r--passlib/drivers/bcrypt.py12
-rw-r--r--passlib/drivers/des_crypt.py20
-rw-r--r--passlib/drivers/ext_des_crypt.py131
-rw-r--r--passlib/drivers/md5_crypt.py18
-rw-r--r--passlib/drivers/mysql.py26
-rw-r--r--passlib/drivers/nthash.py10
-rw-r--r--passlib/drivers/phpass.py16
-rw-r--r--passlib/drivers/postgres.py40
-rw-r--r--passlib/drivers/sha1_crypt.py10
-rw-r--r--passlib/drivers/sha2_crypt.py19
-rw-r--r--passlib/drivers/sun_md5_crypt.py16
-rw-r--r--passlib/sqldb.py50
-rw-r--r--passlib/tests/handler_utils.py355
-rw-r--r--passlib/tests/test_base.py (renamed from passlib/tests/test_context.py)11
-rw-r--r--passlib/tests/test_drivers.py422
-rw-r--r--passlib/tests/test_frontend.py5
-rwxr-xr-xpasslib/tests/test_hash_bcrypt.py167
-rw-r--r--passlib/tests/test_hash_des_crypt.py74
-rw-r--r--passlib/tests/test_hash_md5_crypt.py57
-rw-r--r--passlib/tests/test_hash_misc.py91
-rw-r--r--passlib/tests/test_hash_mysql.py51
-rw-r--r--passlib/tests/test_hash_postgres.py71
-rw-r--r--passlib/tests/test_hash_sha_crypt.py131
-rw-r--r--passlib/tests/test_hash_sun_md5_crypt.py17
-rw-r--r--passlib/tests/test_utils.py37
-rw-r--r--passlib/tests/test_utils_drivers.py (renamed from passlib/tests/test_handler.py)14
-rw-r--r--passlib/tests/test_utils_pbkdf2.py6
-rw-r--r--passlib/tests/utils.py387
-rw-r--r--passlib/unix.py4
-rw-r--r--passlib/utils/_slow_bcrypt.py2
-rw-r--r--passlib/utils/drivers.py (renamed from passlib/utils/handlers.py)56
-rw-r--r--passlib/win32.py6
36 files changed, 1105 insertions, 1404 deletions
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",