summaryrefslogtreecommitdiff
path: root/passlib/handlers
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-04-05 16:07:22 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-04-05 16:07:22 -0400
commit7ddd256b16018b6967604ecf6cae94d4a0080ef8 (patch)
tree2c0c546d4c85786a610884eda3ce83a48a9dd108 /passlib/handlers
parenta4fce8f8e87530ae2b1de581a7e490176003e8a2 (diff)
downloadpasslib-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.py65
-rw-r--r--passlib/handlers/des_crypt.py57
-rw-r--r--passlib/handlers/digests.py17
-rw-r--r--passlib/handlers/ldap_digests.py57
-rw-r--r--passlib/handlers/md5_crypt.py61
-rw-r--r--passlib/handlers/misc.py20
-rw-r--r--passlib/handlers/mysql.py39
-rw-r--r--passlib/handlers/nthash.py58
-rw-r--r--passlib/handlers/oracle.py35
-rw-r--r--passlib/handlers/pbkdf2.py129
-rw-r--r--passlib/handlers/phpass.py58
-rw-r--r--passlib/handlers/postgres.py2
-rw-r--r--passlib/handlers/sha1_crypt.py29
-rw-r--r--passlib/handlers/sha2_crypt.py29
-rw-r--r--passlib/handlers/sun_md5_crypt.py8
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")