summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--passlib/handlers/digests.py5
-rw-r--r--passlib/handlers/misc.py4
-rw-r--r--passlib/handlers/pbkdf2.py9
-rw-r--r--passlib/ifc.py188
-rw-r--r--passlib/utils/handlers.py35
5 files changed, 208 insertions, 33 deletions
diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py
index b746f52..9e64656 100644
--- a/passlib/handlers/digests.py
+++ b/passlib/handlers/digests.py
@@ -52,12 +52,13 @@ class HexDigestHash(uh.StaticHandler):
# eoc
#=========================================================
-def create_hex_hash(hash, digest_name):
+def create_hex_hash(hash, digest_name, module=__name__):
#NOTE: could set digest_name=hash.name for cpython, but not for some other platforms.
h = hash()
name = "hex_" + digest_name
return type(name, (HexDigestHash,), dict(
name=name,
+ __module__=module, # so ABCMeta won't clobber it
_hash_func=staticmethod(hash), #sometimes it's a function, sometimes not. so wrap it.
checksum_size=h.digest_size*2,
__doc__="""This class implements a plain hexidecimal %s hash, and follows the :ref:`password-hash-api`.
@@ -78,7 +79,7 @@ hex_sha512 = create_hex_hash(hashlib.sha512, "sha512")
#=========================================================
# htdigest
#=========================================================
-class htdigest(object):
+class htdigest(uh.PasswordHash):
"""htdigest hash function.
.. todo::
diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py
index dcb56c6..2b26b12 100644
--- a/passlib/handlers/misc.py
+++ b/passlib/handlers/misc.py
@@ -79,7 +79,7 @@ class unix_fallback(uh.StaticHandler):
else:
return enable_wildcard
-class unix_disabled(object):
+class unix_disabled(uh.PasswordHash):
"""This class provides disabled password behavior for unix shadow files,
and follows the :ref:`password-hash-api`. This class does not implement a
hash, but instead provides disabled account behavior as found in
@@ -147,7 +147,7 @@ class unix_disabled(object):
else:
return to_native_str(marker or cls.marker, param="marker")
-class plaintext(object):
+class plaintext(uh.PasswordHash):
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
:type encoding: str
diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py
index e77f82b..3269f8e 100644
--- a/passlib/handlers/pbkdf2.py
+++ b/passlib/handlers/pbkdf2.py
@@ -84,7 +84,7 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen
secret = secret.encode("utf-8")
return pbkdf2(secret, self.salt, self.rounds, self.checksum_size, self._prf)
-def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None):
+def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
"create new Pbkdf2DigestHandler subclass for a specific hash"
name = 'pbkdf2_' + hash_name
if ident is None:
@@ -92,6 +92,7 @@ def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None):
prf = "hmac-%s" % (hash_name,)
base = Pbkdf2DigestHandler
return type(name, (base,), dict(
+ __module__=module, # so ABCMeta won't clobber it.
name=name,
ident=ident,
_prf = prf,
@@ -126,9 +127,9 @@ pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 60000, ident=u("$pbkdf2$"))
pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32)
pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64)
-ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$")
-ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$")
-ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$")
+ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
+ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
+ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True)
#=========================================================
#cryptacular's pbkdf2 hash
diff --git a/passlib/ifc.py b/passlib/ifc.py
new file mode 100644
index 0000000..e3eda06
--- /dev/null
+++ b/passlib/ifc.py
@@ -0,0 +1,188 @@
+"""passlib.ifc - abstract interfaces used by Passlib"""
+#=============================================================================
+# imports
+#=============================================================================
+# core
+import logging; log = logging.getLogger(__name__)
+import sys
+# site
+# pkg
+# local
+__all__ = [
+ "PasswordHash",
+]
+
+#=====================================================
+# 2.5-3.2 compatibility helpers
+#=====================================================
+if sys.version_info >= (2,6):
+ from abc import ABCMeta, abstractmethod, abstractproperty
+else:
+ # create stub for python 2.5
+ ABCMeta = type
+ def abstractmethod(func):
+ return func
+ def abstractproperty():
+ return None
+
+def create_with_metaclass(meta):
+ "class decorator that re-creates class using metaclass"
+ # have to do things this way since abc not present in py25,
+ # and py2/py3 have different ways of doing metaclasses.
+ def builder(cls):
+ if meta is type(cls):
+ return cls
+ return meta(cls.__name__, cls.__bases__, cls.__dict__.copy())
+ return builder
+
+#=====================================================
+# PasswordHash interface
+#=====================================================
+class PasswordHash(object):
+ """This class describes an abstract interface which all password hashes
+ in Passlib adhere to. Under Python 2.6 and up, this is an actual
+ Abstract Base Class built using the :mod:`!abc` module.
+
+ See the Passlib docs for full documentation.
+ """
+ #=====================================================
+ # class attributes
+ #=====================================================
+
+ #----------------------------------
+ # general information
+ #----------------------------------
+ name = abstractproperty()
+ setting_kwds = abstractproperty()
+ context_kwds = abstractproperty()
+
+ #----------------------------------
+ # salt information -- if 'salt' in setting_kwds
+ #----------------------------------
+ ##min_salt_size
+ ##max_salt_size
+ ##default_salt_size
+ ##salt_chars
+ ##default_salt_chars
+
+ #----------------------------------
+ # rounds information -- if 'rounds' in setting_kwds
+ #----------------------------------
+ ##min_rounds
+ ##max_rounds
+ ##default_rounds
+ ##rounds_cost
+
+ #----------------------------------
+ # encoding info -- if 'encoding' in context_kwds
+ #----------------------------------
+ ##default_encoding
+
+ #=====================================================
+ # primary methods
+ #=====================================================
+ @classmethod
+ @abstractmethod
+ def encrypt(cls, secret, **setting_and_context_kwds):
+ "encrypt secret, returning resulting hash"
+ raise NotImplementedError("must be implemented by subclass")
+
+ @classmethod
+ @abstractmethod
+ def verify(cls, secret, hash, **context_kwds):
+ "verify secret against hash, returns True/False"
+ raise NotImplementedError("must be implemented by subclass")
+
+ #=====================================================
+ # additional methods
+ #=====================================================
+ @classmethod
+ @abstractmethod
+ def identify(cls, hash):
+ "check if hash belongs to this scheme, returns True/False"
+ raise NotImplementedError("must be implemented by subclass")
+
+ @classmethod
+ @abstractmethod
+ def genconfig(cls, **setting_kwds):
+ "compile settings into a configuration string for genhash()"
+ raise NotImplementedError("must be implemented by subclass")
+
+ @classmethod
+ @abstractmethod
+ def genhash(cls, secret, config, **context_kwds):
+ "generated hash for secret, using settings from config/hash string"
+ raise NotImplementedError("must be implemented by subclass")
+
+ #=====================================================
+ # undocumented methods / attributes
+ #=====================================================
+ # the following entry points are used internally by passlib,
+ # and aren't documented as part of the exposed interface.
+ # they are subject to change between releases,
+ # but are documented here so there's a list of them *somewhere*.
+
+ #----------------------------------
+ # checksum information - defined for many hashes
+ #----------------------------------
+ ## checksum_chars
+ ## checksum_size
+
+ #----------------------------------
+ # CryptContext flags
+ #----------------------------------
+
+ # hack for bsdi_crypt: if True, causes CryptContext to only generate
+ # odd rounds values. assumed False if not defined.
+ ## _avoid_even_rounds = False
+
+ ##@classmethod
+ ##def _bind_needs_update(cls, **setting_kwds):
+ ## """return helper to detect hashes that need updating.
+ ##
+ ## if this method is defined, the CryptContext constructor
+ ## will invoke it with the settings specified for the context.
+ ## this method should return either ``None``, or a callable
+ ## with the signature ``needs_update(hash,secret)->bool``.
+ ##
+ ## this ``needs_update`` function should return True if the hash
+ ## should be re-encrypted, whether due to internal
+ ## issues or the specified settings.
+ ##
+ ## CryptContext will automatically take care of deprecating
+ ## hashes with insufficient rounds for classes which define fromstring()
+ ## and a rounds attribute - though the requirements for this last
+ ## part may change at some point.
+ ## """
+
+ #----------------------------------
+ # experimental methods
+ #----------------------------------
+
+ ##@classmethod
+ ##def normhash(cls, hash):
+ ## """helper to clean up non-canonic instances of hash.
+ ## currently only provided by bcrypt() to fix an historical passlib issue.
+ ## """
+
+ # experiment helper to estimate bitsize of different hashes,
+ # implement for GenericHandler, but may be currently be off for some hashes.
+ # want to expand this into a way to programmatically compare
+ # "strengths" of different hashes and hash algorithms.
+ # still needs to have some factor for estimate relative cost per round,
+ # ala in the style of the scrypt whitepaper.
+ ##@classmethod
+ ##def bitsize(cls, **kwds):
+ ## """returns dict mapping component -> bits contributed.
+ ## components currently include checksum, salt, rounds.
+ ## """
+
+ #=====================================================
+ # eoc
+ #=====================================================
+
+PasswordHash = create_with_metaclass(ABCMeta)(PasswordHash)
+
+#=============================================================================
+# eof
+#=============================================================================
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index 099fd84..6831003 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -16,6 +16,7 @@ from warnings import warn
import passlib.exc as exc
from passlib.exc import MissingBackendError, PasslibConfigWarning, \
PasslibHashWarning
+from passlib.ifc import PasswordHash
from passlib.registry import get_crypt_handler
from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\
BASE64_CHARS, HASH64_CHARS, rng, to_native_str, \
@@ -246,7 +247,7 @@ def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10):
#=====================================================
#GenericHandler
#=====================================================
-class GenericHandler(object):
+class GenericHandler(PasswordHash):
"""helper class for implementing hash handlers.
GenericHandler-derived classes will have (at least) the following
@@ -570,32 +571,9 @@ class GenericHandler(object):
return consteq(self._calc_checksum(secret), chk)
#=========================================================
- # undocumented entry points
+ # experimental methods
#=========================================================
- ##@classmethod
- ##def _bind_needs_update(cls, **settings):
- ## """return helper to detect deprecated hashes.
- ##
- ## if this method is defined, the CryptContext constructor
- ## will invoke it with the settings specified for the context.
- ## this method should return None or a callable
- ## with the signature ``func(hash,secret)->bool``.
- ##
- ## this function should return true if the hash
- ## should be re-encrypted, whether due to internal
- ## issues or the specified settings.
- ##
- ## CryptContext will automatically take care of rounds-deprecation
- ## for GenericHandler-derived classes
- ## """
-
- ##@classmethod
- ##def normhash(cls, hash):
- ## """helper to clean up non-canonic instances of hash.
- ## currently only provided by bcrypt() to fix an historical passlib issue.
- ## """
-
@classmethod
def bitsize(cls, **kwds):
"[experimental method] return info about bitsizes of hash"
@@ -1459,8 +1437,15 @@ class PrefixWrapper(object):
self._get_wrapped()
if ident is not None:
+ if ident is True:
+ # signal that prefix is identifiable in itself.
+ if prefix:
+ ident = prefix
+ else:
+ raise ValueError("no prefix specified")
if isinstance(ident, bytes):
ident = ident.decode("ascii")
+ # XXX: what if ident includes parts of wrapped hash's ident?
if ident[:len(prefix)] != prefix[:len(ident)]:
raise ValueError("ident must agree with prefix")
self._ident = ident