diff options
-rw-r--r-- | passlib/handlers/digests.py | 5 | ||||
-rw-r--r-- | passlib/handlers/misc.py | 4 | ||||
-rw-r--r-- | passlib/handlers/pbkdf2.py | 9 | ||||
-rw-r--r-- | passlib/ifc.py | 188 | ||||
-rw-r--r-- | passlib/utils/handlers.py | 35 |
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 |