diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2011-04-05 16:07:22 -0400 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2011-04-05 16:07:22 -0400 |
commit | 7ddd256b16018b6967604ecf6cae94d4a0080ef8 (patch) | |
tree | 2c0c546d4c85786a610884eda3ce83a48a9dd108 /passlib/handlers | |
parent | a4fce8f8e87530ae2b1de581a7e490176003e8a2 (diff) | |
download | passlib-7ddd256b16018b6967604ecf6cae94d4a0080ef8.tar.gz |
converted most handlers to use new helper classes.
* converted all ExtendedHandler & MultiBackendHandler subclasses
to use GenericHandler + appropriate mixins.
* converted most SimpleHandler subclasses to use StaticHandler.
* changed some hashes to parse_mc2/mc3 methods:
md5_crypt, apr_md5_crypt, most pbkdf2 hashes, sha1_crypt
* changed most hashes to coerce unicode hash strings -> ascii
* changed some internal attribute names for consistency
Diffstat (limited to 'passlib/handlers')
-rw-r--r-- | passlib/handlers/bcrypt.py | 65 | ||||
-rw-r--r-- | passlib/handlers/des_crypt.py | 57 | ||||
-rw-r--r-- | passlib/handlers/digests.py | 17 | ||||
-rw-r--r-- | passlib/handlers/ldap_digests.py | 57 | ||||
-rw-r--r-- | passlib/handlers/md5_crypt.py | 61 | ||||
-rw-r--r-- | passlib/handlers/misc.py | 20 | ||||
-rw-r--r-- | passlib/handlers/mysql.py | 39 | ||||
-rw-r--r-- | passlib/handlers/nthash.py | 58 | ||||
-rw-r--r-- | passlib/handlers/oracle.py | 35 | ||||
-rw-r--r-- | passlib/handlers/pbkdf2.py | 129 | ||||
-rw-r--r-- | passlib/handlers/phpass.py | 58 | ||||
-rw-r--r-- | passlib/handlers/postgres.py | 2 | ||||
-rw-r--r-- | passlib/handlers/sha1_crypt.py | 29 | ||||
-rw-r--r-- | passlib/handlers/sha2_crypt.py | 29 | ||||
-rw-r--r-- | passlib/handlers/sun_md5_crypt.py | 8 |
15 files changed, 262 insertions, 402 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py index 8707b70..428dd87 100644 --- a/passlib/handlers/bcrypt.py +++ b/passlib/handlers/bcrypt.py @@ -21,8 +21,7 @@ try: except ImportError: #pragma: no cover - though should run whole suite w/o pybcrypt installed pybcrypt_hashpw = None #libs -from passlib.utils import os_crypt, classproperty -from passlib.utils.handlers import MultiBackendHandler +from passlib.utils import os_crypt, classproperty, handlers as uh, h64 #pkg #local @@ -33,7 +32,7 @@ __all__ = [ #========================================================= #handler #========================================================= -class bcrypt(MultiBackendHandler): +class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.GenericHandler): """This class implements the BCrypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt, and a variable number of rounds. @@ -66,67 +65,55 @@ class bcrypt(MultiBackendHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "bcrypt" setting_kwds = ("salt", "rounds", "ident") + checksum_chars = 31 + + #--HasManyIdents-- + default_ident = "$2a$" + ident_values = ("$2$", "$2a$") + ident_aliases = {"2":"$2$", "2a": "$2a$"} + #--HasSalt-- min_salt_chars = max_salt_chars = 22 + #--HasRounds-- default_rounds = 12 #current passlib default min_rounds = 4 # bcrypt spec specified minimum - max_rounds = 31 # 32-bit integer limit (real_rounds=1<<rounds) + max_rounds = 31 # 32-bit integer limit (since real_rounds=1<<rounds) rounds_cost = "log2" - checksum_chars = 31 - - #========================================================= - #init - #========================================================= - _extra_init_settings = ("ident",) - - @classmethod - def norm_ident(cls, ident, strict=False): - if not ident: - if strict: - raise ValueError("no ident specified") - ident = "2a" - if ident not in ("2", "2a"): - raise ValueError("invalid ident: %r" % (ident,)) - return ident - #========================================================= #formatting #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and (hash.startswith("$2$") or hash.startswith("$2a$")) - - _pat = re.compile(r""" - ^ - \$(?P<ident>2a?) - \$(?P<rounds>\d{2}) - \$(?P<salt>[A-Za-z0-9./]{22}) - (?P<chk>[A-Za-z0-9./]{31})? - $ - """, re.X) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - m = cls._pat.match(hash) - if not m: + if isinstance(hash, unicode): + hash = hash.encode("ascii") + for ident in cls.ident_values: + if hash.startswith(ident): + break + else: raise ValueError("invalid bcrypt hash") - ident, rounds, salt, chk = m.group("ident", "rounds", "salt", "chk") + rounds, data = hash[len(ident):].split("$") + rval = int(rounds) + if rounds != '%02d' % (rval,): + raise ValueError("invalid bcrypt hash (no rounds padding)") + salt, chk = data[:22], data[22:] return cls( - rounds=int(rounds), + rounds=rval, salt=salt, - checksum=chk, + checksum=chk or None, ident=ident, strict=bool(chk), ) def to_string(self): - return "$%s$%02d$%s%s" % (self.ident, self.rounds, self.salt, self.checksum or '') + return "%s%02d$%s%s" % (self.ident, self.rounds, self.salt, self.checksum or '') #========================================================= #primary interface diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index 9fee4fa..2be69ff 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -58,8 +58,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64, classproperty, os_crypt -from passlib.utils.handlers import MultiBackendHandler, ExtendedHandler +from passlib.utils import h64, classproperty, os_crypt, handlers as uh from passlib.utils.des import mdes_encrypt_int_block #pkg #local @@ -136,7 +135,7 @@ def raw_ext_crypt(secret, rounds, salt): #========================================================= #handler #========================================================= -class des_crypt(MultiBackendHandler): +class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler): """This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. @@ -159,8 +158,11 @@ class des_crypt(MultiBackendHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "des_crypt" setting_kwds = ("salt",) + + #--HasSalt-- min_salt_chars = max_salt_chars = 2 #========================================================= @@ -182,10 +184,9 @@ class des_crypt(MultiBackendHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - m = cls._pat.match(hash) - if not m: - raise ValueError("invalid des-crypt hash") - salt, chk = m.group("salt", "chk") + if isinstance(hash, unicode): + hash = hash.encode("ascii") + salt, chk = hash[:2], hash[2:] return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self): @@ -232,7 +233,7 @@ class des_crypt(MultiBackendHandler): # so as not to reveal weak des keys. given the random salt, this shouldn't be # a very likely issue anyways, but should do something about default rounds generation anyways. -class bsdi_crypt(ExtendedHandler): +class bsdi_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt, and a variable number of rounds. @@ -251,19 +252,20 @@ class bsdi_crypt(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "bsdi_crypt" setting_kwds = ("salt", "rounds") + checksum_chars = 11 + #--HasSalt-- min_salt_chars = max_salt_chars = 4 + #--HasRounds-- default_rounds = 5000 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. @@ -286,6 +288,8 @@ class bsdi_crypt(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid ext-des-crypt hash") @@ -317,7 +321,7 @@ class bsdi_crypt(ExtendedHandler): #========================================================= # #========================================================= -class bigcrypt(ExtendedHandler): +class bigcrypt(uh.HasSalt, uh.GenericHandler): """This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. @@ -332,14 +336,15 @@ class bigcrypt(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "bigcrypt" setting_kwds = ("salt",) - - min_salt_chars = max_salt_chars = 2 - checksum_charset = h64.CHARS #NOTE: checksum chars must be multiple of 11 + #--HasSalt-- + min_salt_chars = max_salt_chars = 2 + #========================================================= #internal helpers #========================================================= @@ -357,17 +362,24 @@ class bigcrypt(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid bigcrypt hash") salt, chk = m.group("salt", "chk") - if chk and len(chk) % 11: - raise ValueError("invalid bigcrypt hash") return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self): return "%s%s" % (self.salt, self.checksum or '') + @classmethod + def norm_checksum(cls, value, strict=False): + value = super(bigcrypt, cls).norm_checksum(value, strict=strict) + if value and len(value) % 11: + raise ValueError("invalid bigcrypt hash") + return value + #========================================================= #backend #========================================================= @@ -392,7 +404,7 @@ class bigcrypt(ExtendedHandler): #========================================================= # #========================================================= -class crypt16(ExtendedHandler): +class crypt16(uh.HasSalt, uh.GenericHandler): """This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. @@ -407,14 +419,14 @@ class crypt16(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "crypt16" setting_kwds = ("salt",) + checksum_chars = 22 + #--HasSalt-- min_salt_chars = max_salt_chars = 2 - checksum_chars = 22 - checksum_charset = h64.CHARS - #========================================================= #internal helpers #========================================================= @@ -432,6 +444,8 @@ class crypt16(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid crypt16 hash") @@ -468,6 +482,7 @@ class crypt16(ExtendedHandler): #run data through des using input of 0 result2 = mdes_encrypt_int_block(key2, 0, salt_value, 5) + #done return h64.encode_dc_int64(result1) + h64.encode_dc_int64(result2) #========================================================= diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py index ff7f220..562036b 100644 --- a/passlib/handlers/digests.py +++ b/passlib/handlers/digests.py @@ -9,8 +9,8 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs +from passlib.utils import handlers as uh from passlib.utils.md4 import md4 -from passlib.utils.handlers import SimpleHandler #pkg #local __all__ = [ @@ -25,14 +25,11 @@ __all__ = [ #========================================================= #helpers for hexidecimal hashes #========================================================= -class HexDigestHash(SimpleHandler): +class HexDigestHash(uh.StaticHandler): "this provides a template for supporting passwords stored as plain hexidecimal hashes" - setting_kwds = () - context_kwds = () - - _hash = None - checksum_chars = None - checksum_charset = "0123456789abcdef" + _hash_func = None #required - hash function + checksum_chars = None #required - size of encoded digest + checksum_charset = uh.HEX_CHARS @classmethod def identify(cls, hash): @@ -47,7 +44,7 @@ class HexDigestHash(SimpleHandler): secret = secret.encode("utf-8") if hash is not None and not cls.identify(hash): raise ValueError("not a %s hash" % (cls.name,)) - return cls._hash(secret).hexdigest() + return cls._hash_func(secret).hexdigest() @classmethod def verify(cls, secret, hash): @@ -61,7 +58,7 @@ def create_hex_hash(hash, digest_name): name = "hex_" + digest_name return type(name, (HexDigestHash,), dict( name=name, - _hash=hash, + _hash_func=hash, checksum_chars=h.digest_size*2, __doc__="""This class implements a plain hexidecimal %s hash, and follows the :ref:`password-hash-api`. diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py index a55d325..eb93e22 100644 --- a/passlib/handlers/ldap_digests.py +++ b/passlib/handlers/ldap_digests.py @@ -10,8 +10,7 @@ import re from warnings import warn #site #libs -from passlib.utils import ALL_BYTE_VALUES -from passlib.utils.handlers import ExtendedHandler, SimpleHandler +from passlib.utils import ALL_BYTE_VALUES, handlers as uh #pkg #local __all__ = [ @@ -26,12 +25,13 @@ __all__ = [ #========================================================= #reference - http://www.openldap.org/doc/admin24/security.html -class _Base64DigestHelper(SimpleHandler): +class _Base64DigestHelper(uh.StaticHandler): "helper for ldap_md5 / ldap_sha1" + #XXX: could combine this with hex digests in digests.py - #_ident - #_hash - #_pat + ident = None #required - prefix identifier + _hash_func = None #required - hash function + _pat = None #required - regexp to recognize hash @classmethod def identify(cls, hash): @@ -45,18 +45,17 @@ class _Base64DigestHelper(SimpleHandler): secret = secret.encode("utf-8") if hash is not None and not cls.identify(hash): raise ValueError("not a %s hash" % (cls.name,)) - return cls._ident + cls._hash(secret).digest().encode("base64").strip() + return cls.ident + cls._hash_func(secret).digest().encode("base64").strip() -class _SaltedBase64DigestHelper(ExtendedHandler): +class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): "helper for ldap_salted_md5 / ldap_salted_sha1" setting_kwds = ("salt",) - #_ident - #_hash - #_pat - #_default_chk + ident = None #required - prefix identifier + _hash_func = None #required - hash function + _pat = None #required - regexp to recognize hash + _stub_checksum = None #required - default checksum to plug in min_salt_chars = max_salt_chars = 4 - salt_charset = ALL_BYTE_VALUES @classmethod def identify(cls, hash): @@ -66,6 +65,8 @@ class _SaltedBase64DigestHelper(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") + if isinstance(hash, unicode): + hash = hash.encode('ascii') m = cls._pat.match(hash) if not m: raise ValueError("not a %s hash" % (cls.name,)) @@ -74,14 +75,14 @@ class _SaltedBase64DigestHelper(ExtendedHandler): return cls(checksum=chk, salt=salt, strict=True) def to_string(self): - return self._ident + ((self.checksum or self._default_chk) + self.salt).encode("base64").strip() + return self.ident + ((self.checksum or self._stub_checksum) + self.salt).encode("base64").strip() def calc_checksum(self, secret): if secret is None: raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") - return self._hash(secret + self.salt).digest() + return self._hash_func(secret + self.salt).digest() #========================================================= #implementations @@ -94,8 +95,8 @@ class ldap_md5(_Base64DigestHelper): name = "ldap_md5" setting_kwds = () - _ident = "{MD5}" - _hash = md5 + ident = "{MD5}" + _hash_func = md5 _pat = re.compile(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$") class ldap_sha1(_Base64DigestHelper): @@ -106,8 +107,8 @@ class ldap_sha1(_Base64DigestHelper): name = "ldap_sha1" setting_kwds = () - _ident = "{SHA}" - _hash = sha1 + ident = "{SHA}" + _hash_func = sha1 _pat = re.compile(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$") class ldap_salted_md5(_SaltedBase64DigestHelper): @@ -123,10 +124,10 @@ class ldap_salted_md5(_SaltedBase64DigestHelper): If specified, it must be a 4 byte string; each byte may have any value from 0x00 .. 0xff. """ name = "ldap_salted_md5" - _ident = "{SMD5}" - _hash = md5 + ident = "{SMD5}" + _hash_func = md5 _pat = re.compile(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27}=)$") - _default_chk = '\x00' * 16 + _stub_checksum = '\x00' * 16 class ldap_salted_sha1(_SaltedBase64DigestHelper): """This class stores passwords using LDAP's salted SHA1 format, and follows the :ref:`password-hash-api`. @@ -141,12 +142,12 @@ class ldap_salted_sha1(_SaltedBase64DigestHelper): If specified, it must be a 4 byte string; each byte may have any value from 0x00 .. 0xff. """ name = "ldap_salted_sha1" - _ident = "{SSHA}" - _hash = sha1 + ident = "{SSHA}" + _hash_func = sha1 _pat = re.compile(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32})$") - _default_chk = '\x00' * 20 + _stub_checksum = '\x00' * 20 -class ldap_plaintext(SimpleHandler): +class ldap_plaintext(uh.StaticHandler): """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`. This class acts much like the generic :class:`!passlib.hash.plaintext` handler, @@ -156,8 +157,6 @@ class ldap_plaintext(SimpleHandler): Unicode passwords will be encoded using utf-8. """ name = "ldap_plaintext" - setting_kwds = () - context_kwds = () _2307_pat = re.compile(r"^\{[a-zA-Z0-9-]+\}.*$") @@ -168,7 +167,7 @@ class ldap_plaintext(SimpleHandler): @classmethod def genhash(cls, secret, hash): if hash is not None and not cls.identify(hash): - raise ValueError("not a valid ldap_cleartext hash") + raise ValueError("not a valid ldap_plaintext hash") if secret is None: raise TypeError("secret must be string") if isinstance(secret, unicode): diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py index cb25f1e..0538b97 100644 --- a/passlib/handlers/md5_crypt.py +++ b/passlib/handlers/md5_crypt.py @@ -9,8 +9,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64, os_crypt, classproperty -from passlib.utils.handlers import ExtendedHandler, MultiBackendHandler +from passlib.utils import h64, os_crypt, classproperty, handlers as uh #pkg #local __all__ = [ @@ -31,7 +30,7 @@ def raw_md5_crypt(secret, salt, apr=False): #validate secret if not isinstance(secret, str): - raise TypeError("secret must be string") + raise TypeError("secret must be an encoded string") #validate salt if len(salt) > 8: @@ -113,7 +112,7 @@ def raw_md5_crypt(secret, salt, apr=False): #encode resulting hash return h64.encode_transposed_bytes(result, _chk_offsets) - + _chk_offsets = ( 12,6,0, 13,7,1, @@ -126,7 +125,7 @@ _chk_offsets = ( #========================================================= #handler #========================================================= -class md5_crypt(MultiBackendHandler): +class md5_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler): """This class implements the MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt. @@ -148,39 +147,23 @@ class md5_crypt(MultiBackendHandler): #========================================================= #algorithm information #========================================================= + #--GenericHandler-- name = "md5_crypt" - #stats: 128 bit checksum, 48 bit salt - setting_kwds = ("salt",) + ident = "$1$" + checksum_chars = 22 + #--HasSalt-- min_salt_chars = 0 max_salt_chars = 8 - checksum_chars = 22 - #========================================================= #internal helpers #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$1$") - - _pat = re.compile(r""" - ^ - \$1 - \$(?P<salt>[A-Za-z0-9./]{,8}) - (\$(?P<chk>[A-Za-z0-9./]{22})?)? - $ - """, re.X) @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 md5-crypt hash") - salt, chk = m.group("salt", "chk") + salt, chk = uh.parse_mc2(hash, cls.ident, cls.name) return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self): @@ -216,7 +199,7 @@ class md5_crypt(MultiBackendHandler): #========================================================= #apache variant of md5-crypt #========================================================= -class apr_md5_crypt(ExtendedHandler): +class apr_md5_crypt(uh.HasSalt, uh.GenericHandler): """This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt. @@ -231,37 +214,23 @@ class apr_md5_crypt(ExtendedHandler): #========================================================= #algorithm information #========================================================= + #--GenericHandler-- name = "apr_md5_crypt" setting_kwds = ("salt",) + ident = "$apr1$" + checksum_chars = 22 + #--HasSalt-- min_salt_chars = 0 max_salt_chars = 8 - checksum_chars = 22 - #========================================================= #internal helpers #========================================================= - _pat = re.compile(r""" - ^ - \$apr1 - \$(?P<salt>[A-Za-z0-9./]{,8}) - (\$(?P<chk>[A-Za-z0-9./]{22})?)? - $ - """, re.X) - - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$apr1$") @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 md5-crypt hash") - salt, chk = m.group("salt", "chk") + salt, chk = uh.parse_mc2(hash, cls.ident, cls.name) return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self): diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py index 2a2d3c5..6deffd8 100644 --- a/passlib/handlers/misc.py +++ b/passlib/handlers/misc.py @@ -8,7 +8,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils.handlers import SimpleHandler +import passlib.utils.handlers as uh #pkg #local __all__ = [ @@ -19,7 +19,7 @@ __all__ = [ #========================================================= #handler #========================================================= -class unix_fallback(SimpleHandler): +class unix_fallback(uh.StaticHandler): """This class fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`. This class does not implement a hash, but instead provides fallback @@ -34,18 +34,14 @@ class unix_fallback(SimpleHandler): all passwords will be allowed through if the hash is an empty string. """ name = "unix_fallback" - setting_kwds = () context_kwds = ("enable_wildcard",) + _stub_config = "!" @classmethod def identify(cls, hash): return hash is not None @classmethod - def genconfig(cls): - return "!" - - @classmethod def genhash(cls, secret, hash, enable_wildcard=False): if secret is None: raise TypeError("secret must be string") @@ -59,14 +55,12 @@ class unix_fallback(SimpleHandler): raise ValueError("no hash provided") return enable_wildcard and not hash -class plaintext(SimpleHandler): +class plaintext(uh.StaticHandler): """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`. Unicode passwords will be encoded using utf-8. """ name = "plaintext" - setting_kwds = () - context_kwds = () @classmethod def identify(cls, hash): @@ -80,12 +74,6 @@ class plaintext(SimpleHandler): secret = secret.encode("utf-8") return secret - @classmethod - def verify(cls, secret, hash): - if hash is None: - raise ValueError("no hash specified") - return hash == cls.genhash(secret, hash) - #========================================================= #eof #========================================================= diff --git a/passlib/handlers/mysql.py b/passlib/handlers/mysql.py index 750c31d..3f83b2d 100644 --- a/passlib/handlers/mysql.py +++ b/passlib/handlers/mysql.py @@ -30,7 +30,7 @@ from warnings import warn #site #libs #pkg -from passlib.utils.handlers import SimpleHandler +import passlib.utils.handlers as uh #local __all__ = [ 'mysql323', @@ -40,7 +40,7 @@ __all__ = [ #========================================================= #backend #========================================================= -class mysql323(SimpleHandler): +class mysql323(uh.StaticHandler): """This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. @@ -51,22 +51,17 @@ class mysql323(SimpleHandler): #class attrs #========================================================= name = "mysql323" - setting_kwds = () + + _pat = re.compile(r"^[0-9a-f]{16}$", re.I) #========================================================= - #formatting + #methods #========================================================= - _pat = re.compile(r"^[0-9a-f]{16}$", re.I) @classmethod def identify(cls, hash): return bool(hash and cls._pat.match(hash)) - #========================================================= - #backend - #========================================================= - #NOTE: using default genconfig - @classmethod def genhash(cls, secret, config): if config and not cls.identify(config): @@ -91,11 +86,6 @@ class mysql323(SimpleHandler): add = (add+tmp) & MASK_32 return "%08x%08x" % (nr1 & MASK_31, nr2 & MASK_31) - #========================================================= - #helpers - #========================================================= - #NOTE: using default encrypt() method - @classmethod def verify(cls, secret, hash): if not hash: @@ -109,7 +99,7 @@ class mysql323(SimpleHandler): #========================================================= #handler #========================================================= -class mysql41(SimpleHandler): +class mysql41(uh.StaticHandler): """This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. @@ -117,25 +107,19 @@ class mysql41(SimpleHandler): The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. """ #========================================================= - #algorithm information + #class attrs #========================================================= name = "mysql41" - setting_kwds = () + _pat = re.compile(r"^\*[0-9A-F]{40}$", re.I) #========================================================= - #formatting + #methods #========================================================= - _pat = re.compile(r"^\*[0-9A-F]{40}$", re.I) @classmethod def identify(cls, hash): return bool(hash and cls._pat.match(hash)) - #========================================================= - #backend - #========================================================= - #NOTE: using default genconfig() method - @classmethod def genhash(cls, secret, config): if config and not cls.identify(config): @@ -145,11 +129,6 @@ class mysql41(SimpleHandler): secret = secret.encode("utf-8") return '*' + sha1(sha1(secret).digest()).hexdigest().upper() - #========================================================= - #helpers - #========================================================= - #NOTE: using default encrypt() method - @classmethod def verify(cls, secret, hash): if not hash: diff --git a/passlib/handlers/nthash.py b/passlib/handlers/nthash.py index 3dafcd4..07c10c2 100644 --- a/passlib/handlers/nthash.py +++ b/passlib/handlers/nthash.py @@ -8,8 +8,8 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs +from passlib.utils import handlers as uh from passlib.utils.md4 import md4 -from passlib.utils.handlers import ExtendedHandler #pkg #local __all__ = [ @@ -19,7 +19,7 @@ __all__ = [ #========================================================= #handler #========================================================= -class nthash(ExtendedHandler): +class nthash(uh.HasManyIdents, uh.GenericHandler): """This class implements the NT Password hash in a manner compatible with the :ref:`modular-crypt-format`, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. @@ -36,64 +36,42 @@ class nthash(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "nthash" setting_kwds = ("ident",) + checksum_charset = uh.LC_HEX_CHARS - #========================================================= - #init - #========================================================= - _extra_init_settings = ("ident",) + _stub_checksum = "0" * 32 - @classmethod - def norm_ident(cls, value, strict=False): - if value is None: - if strict: - raise ValueError("no ident specified") - return "3" - if value not in ("3", "NT"): - raise ValueError("invalid ident") - return value + #--HasManyIdents-- + default_ident = "$3$$" + ident_values = ("$3$$", "$NT$") + ident_aliases = {"3": "$3$$", "NT": "$NT$"} #========================================================= #formatting #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and (hash.startswith("$3$") or hash.startswith("$NT$")) - - _pat = re.compile(r""" - ^ - \$(?P<ident>3\$\$|NT\$) - (?P<chk>[a-f0-9]{32}) - $ - """, re.X) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - m = cls._pat.match(hash) - if not m: + if isinstance(hash, unicode): + hash = hash.encode("ascii") + for ident in cls.ident_values: + if hash.startswith(ident): + break + else: raise ValueError("invalid nthash") - ident, chk = m.group("ident", "chk") - return cls(ident=ident.strip("$"), checksum=chk, strict=True) + chk = hash[len(ident):] + return cls(ident=ident, checksum=chk, strict=True) def to_string(self): - ident = self.ident - if ident == "3": - return "$3$$" + self.checksum - else: - assert ident == "NT" - return "$NT$" + self.checksum + return self.ident + (self.checksum or self._stub_checksum) #========================================================= #primary interface #========================================================= - _stub_checksum = "0" * 32 - - @classmethod - def genconfig(cls, ident=None): - return cls(ident=ident, checksum=cls._stub_checksum).to_string() def calc_checksum(self, secret): if secret is None: diff --git a/passlib/handlers/oracle.py b/passlib/handlers/oracle.py index 5ac788e..b70d20e 100644 --- a/passlib/handlers/oracle.py +++ b/passlib/handlers/oracle.py @@ -11,9 +11,8 @@ from warnings import warn #site #libs #pkg -from passlib.utils import xor_bytes +from passlib.utils import xor_bytes, handlers as uh from passlib.utils.des import des_encrypt_block -from passlib.utils.handlers import ExtendedHandler #local __all__ = [ "oracle10g", @@ -114,6 +113,9 @@ class oracle10(object): def encode(value): "encode according to guess at how oracle encodes strings (see note above)" if not isinstance(value, unicode): + #we can't trust what original encoding was. + #user should have passed us unicode in the first place. + #but try decoding as ascii just to work for most common case. value = value.decode("ascii") return value.upper().encode("utf-16-be") @@ -135,7 +137,7 @@ class oracle10(object): #========================================================= #oracle11 #========================================================= -class oracle11(ExtendedHandler): +class oracle11(uh.HasSalt, uh.GenericHandler): """This class implements the Oracle11g password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. @@ -147,14 +149,25 @@ class oracle11(ExtendedHandler): If not specified, one will be autogenerated (this is recommended). If specified, it must be 20 hexidecimal characters. """ - + #========================================================= + #class attrs + #========================================================= + #--GenericHandler-- name = "oracle11" setting_kwds = ("salt",) + checksum_chars = 40 + checksum_charset = uh.UC_HEX_CHARS + _stub_checksum = '0' * 40 + + #--HasSalt-- min_salt_chars = max_salt_chars = 20 - salt_charset = checksum_charset = "0123456789ABCDEF" - checksum_chars = 40 + salt_charset = uh.UC_HEX_CHARS + + #========================================================= + #methods + #========================================================= _pat = re.compile("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$", re.I) @classmethod @@ -165,14 +178,14 @@ class oracle11(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash provided") + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: - raise ValueError("invalid oracle11g hash") + raise ValueError("invalid oracle11 hash") salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk.upper(), strict=True) - _stub_checksum = '0' * 40 - def to_string(self): return "S:%s%s" % ((self.checksum or self._stub_checksum).upper(), self.salt.upper()) @@ -181,6 +194,10 @@ class oracle11(ExtendedHandler): secret = secret.encode("utf-8") return sha1(secret + unhexlify(self.salt)).hexdigest().upper() + #========================================================= + #eoc + #========================================================= + #========================================================= #eof #========================================================= diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 966466b..249fa4f 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -10,8 +10,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import adapted_b64_encode, adapted_b64_decode, ALL_BYTE_VALUES -from passlib.utils.handlers import ExtendedHandler +from passlib.utils import adapted_b64_encode, adapted_b64_decode, ALL_BYTE_VALUES, handlers as uh from passlib.utils.pbkdf2 import pbkdf2 #pkg #local @@ -26,49 +25,45 @@ __all__ = [ #========================================================= # #========================================================= -class Pbkdf2DigestHandler(ExtendedHandler): +class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): "base class for various pbkdf2_{digest} algorithms" - setting_kwds = ("salt", "rounds") - - _ident = None #subclass specified identifier prefix - _prf = None #subclass specified prf identifier - - #NOTE: max_salt_chars and max_rounds are arbitrarily chosen to provide sanity check. - # the underlying pbkdf2 specifies no bounds for either. + #========================================================= + #class attrs + #========================================================= - #NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends... - # >8 bytes of entropy in salt, >1000 rounds - # increased due to time since rfc established + #--GenericHandler-- + setting_kwds = ("salt", "rounds") + #--HasSalt-- default_salt_chars = 16 min_salt_chars = 0 max_salt_chars = 1024 - salt_charset = ALL_BYTE_VALUES + #--HasRounds-- default_rounds = 6400 min_rounds = 1 max_rounds = 2**32-1 rounds_cost = "linear" - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith(cls._ident) + #--this class-- + _prf = None #subclass specified prf identifier + + #NOTE: max_salt_chars and max_rounds are arbitrarily chosen to provide sanity check. + # the underlying pbkdf2 specifies no bounds for either. + + #NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends... + # >8 bytes of entropy in salt, >1000 rounds + # increased due to time since rfc established + + #========================================================= + #methods + #========================================================= @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - ident = cls._ident - if not hash.startswith(ident): - raise ValueError("invalid %s hash" % (cls.name,)) - parts = hash[len(ident):].split("$") - if len(parts) == 3: - rounds, salt, chk = parts - elif len(parts) == 2: - rounds, salt = parts - chk = None - else: - raise ValueError("invalid %s hash" % (cls.name,)) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) int_rounds = int(rounds) if rounds != str(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) @@ -84,9 +79,9 @@ class Pbkdf2DigestHandler(ExtendedHandler): def to_string(self, withchk=True): salt = adapted_b64_encode(self.salt) if withchk and self.checksum: - return '%s%d$%s$%s' % (self._ident, self.rounds, salt, adapted_b64_encode(self.checksum)) + return '%s%d$%s$%s' % (self.ident, self.rounds, salt, adapted_b64_encode(self.checksum)) else: - return '%s%d$%s' % (self._ident, self.rounds, salt) + return '%s%d$%s' % (self.ident, self.rounds, salt) def calc_checksum(self, secret): if isinstance(secret, unicode): @@ -101,7 +96,7 @@ def create_pbkdf2_hash(hash_name, digest_size): base = Pbkdf2DigestHandler return type(name, (base,), dict( name=name, - _ident=ident, + ident=ident, _prf = prf, checksum_chars=digest_size, encoded_checksum_chars=(digest_size*4+2)//3, @@ -132,7 +127,7 @@ pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64) #========================================================= #dlitz's pbkdf2 hash #========================================================= -class dlitz_pbkdf2_sha1(ExtendedHandler): +class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -152,16 +147,20 @@ class dlitz_pbkdf2_sha1(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "dlitz_pbkdf2_sha1" setting_kwds = ("salt", "rounds") + ident = "$p5k2$" #NOTE: max_salt_chars and max_rounds are arbitrarily chosen to provide sanity check. # underlying algorithm (and reference implementation) allow effectively unbounded values for both of these. + #--HasSalt-- default_salt_chars = 16 min_salt_chars = 0 max_salt_chars = 1024 + #--HasROunds-- default_rounds = 10000 min_rounds = 0 max_rounds = 2**32-1 @@ -171,10 +170,6 @@ class dlitz_pbkdf2_sha1(ExtendedHandler): #formatting #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$p5k2$") - #hash $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g #ident $p5k2$ #rounds c @@ -182,23 +177,11 @@ class dlitz_pbkdf2_sha1(ExtendedHandler): #chk Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g #rounds in lowercase hex, no zero padding - _pat = re.compile(r""" - ^ - \$p5k2 - \$(?P<rounds>[a-f0-9]*) - \$(?P<salt>[A-Za-z0-9./]*) - (\$(?P<chk>[A-Za-z0-9./]{32}))? - $ - """, re.X) - @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 dlitz_pbkdf2_crypt hash") - rounds, salt, chk = m.group("rounds", "salt", "chk") + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): #zero not allowed, nor left-padded with zeroes raise ValueError("invalid dlitz_pbkdf2_crypt hash") rounds = int(rounds, 16) if rounds else 400 @@ -235,7 +218,7 @@ class dlitz_pbkdf2_sha1(ExtendedHandler): #========================================================= #crowd #========================================================= -class atlassian_pbkdf2_sha1(ExtendedHandler): +class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the PBKDF2 hash used by Atlassian. It supports a fixed-length salt, and a fixed number of rounds. @@ -247,34 +230,33 @@ class atlassian_pbkdf2_sha1(ExtendedHandler): If specified, the length must be exactly 16 bytes. If not specified, a salt will be autogenerated (this is recommended). """ + #--GenericHandler-- name = "atlassian_pbkdf2_sha1" setting_kwds =("salt",) - _ident = "{PKCS5S2}" - - min_salt_chars = max_salt_chars = 16 - salt_charset = ALL_BYTE_VALUES + ident = "{PKCS5S2}" checksum_chars = 32 - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith(cls._ident) + _stub_checksum = "\x00" * 32 + + #--HasRawSalt-- + min_salt_chars = max_salt_chars = 16 @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - ident = cls._ident + if isinstance(hash, unicode): + hash = hash.encode("ascii") + ident = cls.ident if not hash.startswith(ident): raise ValueError("invalid %s hash" % (cls.name,)) data = b64decode(hash[len(ident):]) salt, chk = data[:16], data[16:] return cls(salt=salt, checksum=chk, strict=True) - _stub_checksum = "\x00" * 32 - def to_string(self): data = self.salt + (self.checksum or self._stub_checksum) - return self._ident + b64encode(data) + return self.ident + b64encode(data) def calc_checksum(self, secret): #TODO: find out what crowd's policy is re: unicode @@ -286,7 +268,7 @@ class atlassian_pbkdf2_sha1(ExtendedHandler): #========================================================= #grub #========================================================= -class grub_pbkdf2_sha512(ExtendedHandler): +class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements Grub's pbkdf2-hmac-sha512 hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -305,7 +287,7 @@ class grub_pbkdf2_sha512(ExtendedHandler): name = "grub_pbkdf2_sha512" setting_kwds = ("salt", "rounds") - _ident = "grub.pbkdf2.sha512." + ident = "grub.pbkdf2.sha512." #NOTE: max_salt_chars and max_rounds are arbitrarily chosen to provide sanity check. # the underlying pbkdf2 specifies no bounds for either, @@ -314,7 +296,6 @@ class grub_pbkdf2_sha512(ExtendedHandler): default_salt_chars = 64 min_salt_chars = 0 max_salt_chars = 1024 - salt_charset = ALL_BYTE_VALUES default_rounds = 10000 min_rounds = 1 @@ -322,24 +303,10 @@ class grub_pbkdf2_sha512(ExtendedHandler): rounds_cost = "linear" @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith(cls._ident) - - @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - ident = cls._ident - if not hash.startswith(ident): - raise ValueError("invalid %s hash" % (cls.name,)) - parts = hash[len(ident):].split(".") - if len(parts) == 3: - rounds, salt, chk = parts - elif len(parts) == 2: - rounds, salt = parts - chk = None - else: - raise ValueError("invalid %s hash" % (cls.name,)) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name, sep=".") int_rounds = int(rounds) if rounds != str(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) @@ -355,9 +322,9 @@ class grub_pbkdf2_sha512(ExtendedHandler): def to_string(self, withchk=True): salt = hexlify(self.salt).upper() if withchk and self.checksum: - return '%s%d.%s.%s' % (self._ident, self.rounds, salt, hexlify(self.checksum).upper()) + return '%s%d.%s.%s' % (self.ident, self.rounds, salt, hexlify(self.checksum).upper()) else: - return '%s%d.%s' % (self._ident, self.rounds, salt) + return '%s%d.%s' % (self.ident, self.rounds, salt) def calc_checksum(self, secret): #TODO: find out what grub's policy is re: unicode diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py index bb9bfd9..51da27b 100644 --- a/passlib/handlers/phpass.py +++ b/passlib/handlers/phpass.py @@ -15,8 +15,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64 -from passlib.utils.handlers import ExtendedHandler +from passlib.utils import h64, handlers as uh #pkg #local __all__ = [ @@ -26,7 +25,7 @@ __all__ = [ #========================================================= #phpass #========================================================= -class phpass(ExtendedHandler): +class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the PHPass Portable Hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt, and a variable number of rounds. @@ -53,69 +52,48 @@ class phpass(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "phpass" setting_kwds = ("salt", "rounds", "ident") + #--HasSalt-- min_salt_chars = max_salt_chars = 8 + #--HasRounds-- default_rounds = 9 min_rounds = 7 max_rounds = 30 rounds_cost = "log2" - _strict_rounds_bounds = True - _extra_init_settings = ("ident",) - #========================================================= - #instance attrs - #========================================================= - ident = None - - #========================================================= - #init - #========================================================= - @classmethod - def norm_ident(cls, ident, strict=False): - if not ident: - if strict: - raise ValueError("no ident specified") - ident = "P" - if ident not in ("P", "H"): - raise ValueError("invalid ident: %r" % (ident,)) - return ident + #--HasManyIdents-- + default_ident = "$P$" + ident_values = ["$P$", "$H$"] + ident_aliases = {"P":"$P$", "H":"$H$"} #========================================================= #formatting #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and (hash.startswith("$P$") or hash.startswith("$H$")) - #$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0 # $P$ # 9 # IQRaTwmf # eRo7ud9Fh4E2PdI0S3r.L0 - _pat = re.compile(r""" - ^ - \$ - (?P<ident>[PH]) - \$ - (?P<rounds>[A-Za-z0-9./]) - (?P<salt>[A-Za-z0-9./]{8}) - (?P<chk>[A-Za-z0-9./]{22})? - $ - """, re.X) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - m = cls._pat.match(hash) - if not m: + if isinstance(hash, unicode): + hash = hash.encode('ascii') + for ident in cls.ident_values: + if hash.startswith(ident): + break + else: raise ValueError("invalid phpass portable hash") - ident, rounds, salt, chk = m.group("ident", "rounds", "salt", "chk") + data = hash[len(ident):] + rounds, salt, chk = data[0], data[1:9], data[9:] return cls( ident=ident, rounds=h64.decode_6bit(rounds), @@ -125,7 +103,7 @@ class phpass(ExtendedHandler): ) def to_string(self): - return "$%s$%s%s%s" % (self.ident, h64.encode_6bit(self.rounds), self.salt, self.checksum or '') + return "%s%s%s%s" % (self.ident, h64.encode_6bit(self.rounds), self.salt, self.checksum or '') #========================================================= #backend diff --git a/passlib/handlers/postgres.py b/passlib/handlers/postgres.py index 958d027..1e9fc94 100644 --- a/passlib/handlers/postgres.py +++ b/passlib/handlers/postgres.py @@ -71,7 +71,7 @@ class postgres_md5(object): secret = secret.encode("utf-8") if isinstance(user, unicode): user = user.encode("utf-8") - return "md5" + md5(secret + user).hexdigest().lower() + return "md5" + md5(secret + user).hexdigest() @classmethod def verify(cls, secret, hash, user): diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py index a4c4926..b9ebd33 100644 --- a/passlib/handlers/sha1_crypt.py +++ b/passlib/handlers/sha1_crypt.py @@ -13,8 +13,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64 -from passlib.utils.handlers import ExtendedHandler +from passlib.utils import h64, handlers as uh from passlib.utils.pbkdf2 import hmac_sha1 #pkg #local @@ -23,7 +22,7 @@ __all__ = [ #========================================================= #sha1-crypt #========================================================= -class sha1_crypt(ExtendedHandler): +class sha1_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -44,13 +43,18 @@ class sha1_crypt(ExtendedHandler): #========================================================= #class attrs #========================================================= + #--GenericHandler-- name = "sha1_crypt" setting_kwds = ("salt", "rounds") + ident = "$sha1$" + checksum_chars = 28 + #--HasSalt-- default_salt_chars = 8 min_salt_chars = 0 max_salt_chars = 64 + #--HasRounds-- default_rounds = 40000 #current passlib default min_rounds = 1 #really, this should be higher. max_rounds = 4294967295 # 32-bit integer limit @@ -59,27 +63,10 @@ class sha1_crypt(ExtendedHandler): #========================================================= #formatting #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$sha1$") - - _pat = re.compile(r""" - ^ - \$sha1 - \$(?P<rounds>\d+) - \$(?P<salt>[A-Za-z0-9./]{0,64}) - (\$(?P<chk>[A-Za-z0-9./]{28})?)? - $ - """, re.X) @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 sha1_crypt hash") - rounds, salt, chk = m.group("rounds", "salt", "chk") + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): raise ValueError("invalid sha1-crypt hash (zero-padded rounds)") return cls( diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py index 6bd1de0..e5d27af 100644 --- a/passlib/handlers/sha2_crypt.py +++ b/passlib/handlers/sha2_crypt.py @@ -9,8 +9,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64, os_crypt, classproperty -from passlib.utils.handlers import MultiBackendHandler +from passlib.utils import h64, os_crypt, classproperty, handlers as uh #pkg #local __all__ = [ @@ -205,7 +204,7 @@ _512_offsets = ( #========================================================= #handler #========================================================= -class sha256_crypt(MultiBackendHandler): +class sha256_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA256-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -240,16 +239,19 @@ class sha256_crypt(MultiBackendHandler): #========================================================= #algorithm information #========================================================= + #--GenericHandler-- name = "sha256_crypt" - setting_kwds = ("salt", "rounds", "implicit_rounds") + ident = "$5$" + #--HasSalt-- min_salt_chars = 0 max_salt_chars = 16 #TODO: allow salt charset 0-255 except for "\x00\n:$" + #--HasRounds-- default_rounds = 40000 #current passlib default - min_rounds = 1000 + min_rounds = 1000 #other bounds set by spec max_rounds = 999999999 rounds_cost = "linear" @@ -265,9 +267,6 @@ class sha256_crypt(MultiBackendHandler): #========================================================= #parsing #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$5$") #: regexp used to parse hashes _pat = re.compile(r""" @@ -289,8 +288,8 @@ class sha256_crypt(MultiBackendHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - #TODO: write non-regexp based parser, - # and rely on norm_salt etc to handle more of the validation. + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid sha256-crypt hash") @@ -350,7 +349,7 @@ class sha256_crypt(MultiBackendHandler): #========================================================= #sha 512 crypt #========================================================= -class sha512_crypt(MultiBackendHandler): +class sha512_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA512-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -386,6 +385,7 @@ class sha512_crypt(MultiBackendHandler): #algorithm information #========================================================= name = "sha512_crypt" + ident = "$6$" setting_kwds = ("salt", "rounds", "implicit_rounds") @@ -410,9 +410,6 @@ class sha512_crypt(MultiBackendHandler): #========================================================= #parsing #========================================================= - @classmethod - def identify(cls, hash): - return bool(hash) and hash.startswith("$6$") #: regexp used to parse hashes _pat = re.compile(r""" @@ -436,8 +433,8 @@ class sha512_crypt(MultiBackendHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - #TODO: write non-regexp based parser, - # and rely on norm_salt etc to handle more of the validation. + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid sha512-crypt hash") diff --git a/passlib/handlers/sun_md5_crypt.py b/passlib/handlers/sun_md5_crypt.py index 375fc08..fd5e5cd 100644 --- a/passlib/handlers/sun_md5_crypt.py +++ b/passlib/handlers/sun_md5_crypt.py @@ -25,8 +25,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import h64 -from passlib.utils.handlers import ExtendedHandler +from passlib.utils import h64, handlers as uh #pkg #local __all__ = [ @@ -190,7 +189,7 @@ _chk_offsets = ( #========================================================= #handler #========================================================= -class sun_md5_crypt(ExtendedHandler): +class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the Sun-MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. @@ -211,6 +210,7 @@ class sun_md5_crypt(ExtendedHandler): #========================================================= name = "sun_md5_crypt" setting_kwds = ("salt", "rounds") + ident = "$md5$" min_salt_chars = 0 max_salt_chars = 8 @@ -247,6 +247,8 @@ class sun_md5_crypt(ExtendedHandler): def from_string(cls, hash): if not hash: raise ValueError("no hash specified") + if isinstance(hash, unicode): + hash = hash.encode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid sun-md5-crypt hash") |