summaryrefslogtreecommitdiff
path: root/passlib/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/handlers')
-rw-r--r--passlib/handlers/bcrypt.py13
-rw-r--r--passlib/handlers/cisco.py2
-rw-r--r--passlib/handlers/des_crypt.py245
-rw-r--r--passlib/handlers/digests.py62
-rw-r--r--passlib/handlers/fshp.py8
-rw-r--r--passlib/handlers/ldap_digests.py2
-rw-r--r--passlib/handlers/md5_crypt.py7
-rw-r--r--passlib/handlers/misc.py2
-rw-r--r--passlib/handlers/pbkdf2.py21
-rw-r--r--passlib/handlers/phpass.py2
-rw-r--r--passlib/handlers/scram.py10
-rw-r--r--passlib/handlers/sha2_crypt.py3
12 files changed, 208 insertions, 169 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index f07a194..890c656 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -186,8 +186,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
def _norm_salt(self, salt, **kwds):
salt = super(bcrypt, self)._norm_salt(salt, **kwds)
- if not salt:
- return None
+ assert salt is not None, "HasSalt didn't generate new salt!"
changed, salt = bcrypt64.check_repair_unused(salt)
if changed:
warn(
@@ -250,10 +249,14 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
assert hash.startswith(config) and len(hash) == len(config)+31
return hash[-31:]
else:
- #NOTE: not checking other backends since this is lowest priority one,
- # so they probably aren't available either.
+ # NOTE: it's unlikely any other backend will be available,
+ # but checking before we bail, just in case.
+ for name in self.backends:
+ if name != "os_crypt" and self.has_backend(name):
+ func = getattr(self, "_calc_checksum_" + name)
+ return func(secret)
raise uh.exc.MissingBackendError(
- "encoded password can't be handled by os_crypt, "
+ "password can't be handled by os_crypt, "
"recommend installing py-bcrypt.",
)
diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py
index 184134e..c61a105 100644
--- a/passlib/handlers/cisco.py
+++ b/passlib/handlers/cisco.py
@@ -149,7 +149,7 @@ class cisco_type7(uh.GenericHandler):
else:
raise TypeError("no salt specified")
if not isinstance(salt, int):
- raise TypeError("salt must be an integer")
+ raise uh.exc.ExpectedTypeError(salt, "integer", "salt")
if salt < 0 or salt > self.max_salt_value:
msg = "salt/offset must be in 0..52 range"
if self.relaxed:
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 56102c0..4a19532 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -1,54 +1,4 @@
-"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants
-
-.. note::
-
- for des-crypt, passlib restricts salt characters to just the hash64 charset,
- and salt string size to >= 2 chars; since implementations of des-crypt
- vary in how they handle other characters / sizes...
-
- linux
-
- linux crypt() accepts salt characters outside the hash64 charset,
- and maps them using the following formula (determined by examining crypt's output):
- chr 0..64: v = (c-(1-19)) & 63 = (c+18) & 63
- chr 65..96: v = (c-(65-12)) & 63 = (c+11) & 63
- chr 97..127: v = (c-(97-38)) & 63 = (c+5) & 63
- chr 128..255: same as c-128
-
- invalid salt chars are mirrored back in the resulting hash.
-
- if the salt is too small, it uses a NUL char for the remaining
- character (which is treated the same as the char ``G``)
- when decoding the 12 bit salt. however, it outputs
- a hash string containing the single salt char twice,
- resulting in a corrupted hash.
-
- netbsd
-
- netbsd crypt() uses a 128-byte lookup table,
- which is only initialized for the hash64 values.
- the remaining values < 128 are implicitly zeroed,
- and values > 128 access past the array bounds
- (but seem to return 0).
-
- if the salt string is too small, it reads
- the NULL char (and continues past the end for bsdi crypt,
- though the buffer is usually large enough and NULLed).
- salt strings are output as provided,
- except for any NULs, which are converted to ``.``.
-
- openbsd, freebsd
-
- openbsd crypt() strictly defines the hash64 values as normal,
- and all other char values as 0. salt chars are reported as provided.
-
- if the salt or rounds string is too small,
- it'll read past the end, resulting in unpredictable
- values, though it'll terminate it's encoding
- of the output at the first null.
- this will generally result in a corrupted hash.
-"""
-
+"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
#=========================================================
#imports
#=========================================================
@@ -60,7 +10,7 @@ from warnings import warn
#libs
from passlib.utils import classproperty, h64, h64big, safe_crypt, test_crypt, to_unicode
from passlib.utils.compat import b, bytes, byte_elem_value, u, uascii_to_str, unicode
-from passlib.utils.des import mdes_encrypt_int_block
+from passlib.utils.des import des_encrypt_int_block
import passlib.utils.handlers as uh
#pkg
#local
@@ -72,70 +22,86 @@ __all__ = [
]
#=========================================================
-#pure-python backend
+# pure-python backend for des_crypt family
#=========================================================
def _crypt_secret_to_key(secret):
- "crypt helper which converts lower 7 bits of first 8 chars of secret -> 56-bit des key, padded to 64 bits"
- return sum(
- (byte_elem_value(c) & 0x7f) << (57-8*i)
- for i, c in enumerate(secret[:8])
- )
-
-def raw_crypt(secret, salt):
- "pure-python fallback if stdlib support not present"
+ """convert secret to 64-bit DES key.
+
+ this only uses the first 8 bytes of the secret,
+ and discards the high 8th bit of each byte at that.
+ a null parity bit is inserted after every 7th bit of the output.
+ """
+ # NOTE: this would set the parity bits correctly,
+ # but des_encrypt_int_block() would just ignore them...
+ ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
+ ## for i, c in enumerate(secret[:8]))
+ return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
+ for i, c in enumerate(secret[:8]))
+
+def _raw_des_crypt(secret, salt):
+ "pure-python backed for des_crypt"
assert len(salt) == 2
- #NOTE: technically could accept non-standard salts & single char salt,
- #but no official spec.
+ # NOTE: some OSes will accept non-HASH64 characters in the salt,
+ # but what value they assign these characters varies wildy,
+ # so just rejecting them outright.
+ # NOTE: the same goes for single-character salts...
+ # some OSes duplicate the char, some insert a '.' char,
+ # and openbsd does something which creates an invalid hash.
try:
salt_value = h64.decode_int12(salt)
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid chars in salt")
- #FIXME: ^ this will throws error if bad salt chars are used
- # whereas linux crypt does something (inexplicable) with it
- #convert first 8 bytes of secret string into an integer
+ # forbidding NULL char because underlying crypt() rejects them too.
+ if b('\x00') in secret:
+ raise ValueError("null char in secret")
+
+ # convert first 8 bytes of secret string into an integer
key_value = _crypt_secret_to_key(secret)
- #run data through des using input of 0
- result = mdes_encrypt_int_block(key_value, 0, salt_value, 25)
+ # run data through des using input of 0
+ result = des_encrypt_int_block(key_value, 0, salt_value, 25)
- #run h64 encode on result
+ # run h64 encode on result
return h64big.encode_int64(result)
-def raw_ext_crypt(secret, rounds, salt):
- "ext_crypt() helper which returns checksum only"
+def _bsdi_secret_to_key(secret):
+ "covert secret to DES key used by bsdi_crypt"
+ key_value = _crypt_secret_to_key(secret)
+ idx = 8
+ end = len(secret)
+ while idx < end:
+ next = idx+8
+ tmp_value = _crypt_secret_to_key(secret[idx:next])
+ key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
+ idx = next
+ return key_value
+
+def _raw_bsdi_crypt(secret, rounds, salt):
+ "pure-python backend for bsdi_crypt"
- #decode salt
+ # decode salt
try:
salt_value = h64.decode_int24(salt)
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid salt")
- #validate secret
- if b('\x00') in secret: #pragma: no cover - always caught by class
- #builtin linux crypt doesn't like this, so we don't either
- #XXX: would make more sense to raise ValueError, but want to be compatible w/ stdlib crypt
+ # forbidding NULL char because underlying crypt() rejects them too.
+ if b('\x00') in secret:
raise ValueError("secret must be string without null bytes")
- #convert secret string into an integer
- key_value = _crypt_secret_to_key(secret)
- idx = 8
- end = len(secret)
- while idx < end:
- next = idx+8
- key_value = mdes_encrypt_int_block(key_value, key_value) ^ \
- _crypt_secret_to_key(secret[idx:next])
- idx = next
+ # convert secret string into an integer
+ key_value = _bsdi_secret_to_key(secret)
- #run data through des using input of 0
- result = mdes_encrypt_int_block(key_value, 0, salt_value, rounds)
+ # run data through des using input of 0
+ result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
- #run h64 encode on result
+ # run h64 encode on result
return h64big.encode_int64(result)
#=========================================================
-#handler
+# handlers
#=========================================================
class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
"""This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
@@ -156,21 +122,21 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
You can see which backend is in use by calling the :meth:`get_backend()` method.
"""
-
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "des_crypt"
setting_kwds = ("salt",)
checksum_chars = uh.HASH64_CHARS
+ checksum_size = 11
#--HasSalt--
min_salt_size = max_salt_size = 2
salt_chars = uh.HASH64_CHARS
#=========================================================
- #formatting
+ # formatting
#=========================================================
#FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
@@ -191,7 +157,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
return uascii_to_str(hash)
#=========================================================
- #backend
+ # backend
#=========================================================
backends = ("os_crypt", "builtin")
@@ -205,11 +171,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
# gotta do something - no official policy since des-crypt predates unicode
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- # forbidding nul chars because linux crypt (and most C implementations)
- # won't accept it either.
- if b('\x00') in secret:
- raise ValueError("null char in secret")
- return raw_crypt(secret, self.salt.encode("ascii")).decode("ascii")
+ return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
# NOTE: safe_crypt encodes unicode secret -> utf8
@@ -222,19 +184,9 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
return self._calc_checksum_builtin(secret)
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#handler
-#=========================================================
-
-#FIXME: phpass code notes that even rounds values should be avoided for BSDI-Crypt,
-# 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.
-# http://wiki.call-cc.org/eggref/4/crypt sez even rounds of DES may reveal weak keys.
-# list of semi-weak keys - http://dolphinburger.com/cgi-bin/bsdi-man?proto=1.1&query=bdes&msection=1&apropos=0
-
class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
"""This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
@@ -259,7 +211,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
You can see which backend is in use by calling the :meth:`get_backend()` method.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "bsdi_crypt"
@@ -281,7 +233,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
# but that seems to be an OS policy, not a algorithm limitation.
#=========================================================
- #internal helpers
+ # parsing
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -310,7 +262,36 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return uascii_to_str(hash)
#=========================================================
- #backend
+ # validation
+ #=========================================================
+
+ # flag so CryptContext won't generate even rounds.
+ _avoid_even_rounds = True
+
+ def _norm_rounds(self, rounds):
+ rounds = super(bsdi_crypt, self)._norm_rounds(rounds)
+ # issue warning if app provided an even rounds value
+ if self.use_defaults and not rounds & 1:
+ warn("bsdi_crypt rounds should be odd, "
+ "as even rounds may reveal weak DES keys",
+ uh.exc.PasslibSecurityWarning)
+ return rounds
+
+ @classmethod
+ def _deprecation_detector(cls, **settings):
+ return cls._hash_needs_update
+
+ @classmethod
+ def _hash_needs_update(cls, hash):
+ # mark bsdi_crypt hashes as deprecated if they have even rounds.
+ assert cls.identify(hash)
+ if isinstance(hash, unicode):
+ hash = hash.encode("ascii")
+ rounds = h64.decode_int24(hash[1:5])
+ return not rounds & 1
+
+ #=========================================================
+ # backends
#=========================================================
backends = ("os_crypt", "builtin")
@@ -323,7 +304,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
def _calc_checksum_builtin(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- return raw_ext_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
+ return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
config = self.to_string()
@@ -335,12 +316,9 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return self._calc_checksum_builtin(secret)
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#
-#=========================================================
class bigcrypt(uh.HasSalt, uh.GenericHandler):
"""This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
@@ -354,7 +332,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "bigcrypt"
@@ -367,7 +345,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#=========================================================
- #internal helpers
+ # internal helpers
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -395,29 +373,24 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
return value
#=========================================================
- #backend
+ # backend
#=========================================================
- #TODO: check if os_crypt supports ext-des-crypt.
-
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- chk = raw_crypt(secret, self.salt.encode("ascii"))
+ chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
idx = 8
end = len(secret)
while idx < end:
next = idx + 8
- chk += raw_crypt(secret[idx:next], chk[-11:-9])
+ chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
idx = next
return chk.decode("ascii")
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#
-#=========================================================
class crypt16(uh.HasSalt, uh.GenericHandler):
"""This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
@@ -431,7 +404,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "crypt16"
@@ -444,7 +417,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#=========================================================
- #internal helpers
+ # internal helpers
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -466,10 +439,8 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
return uascii_to_str(hash)
#=========================================================
- #backend
+ # backend
#=========================================================
- #TODO: check if os_crypt supports ext-des-crypt.
-
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
@@ -484,22 +455,22 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
key1 = _crypt_secret_to_key(secret)
#run data through des using input of 0
- result1 = mdes_encrypt_int_block(key1, 0, salt_value, 20)
+ result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
#convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
key2 = _crypt_secret_to_key(secret[8:16])
#run data through des using input of 0
- result2 = mdes_encrypt_int_block(key2, 0, salt_value, 5)
+ result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
#done
chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
return chk.decode("ascii")
#=========================================================
- #eoc
+ # eoc
#=========================================================
#=========================================================
-#eof
+# eof
#=========================================================
diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py
index ec08056..22c1c6a 100644
--- a/passlib/handlers/digests.py
+++ b/passlib/handlers/digests.py
@@ -9,7 +9,7 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
#site
#libs
-from passlib.utils import to_native_str
+from passlib.utils import to_native_str, to_bytes, render_bytes, consteq
from passlib.utils.compat import bascii_to_str, bytes, unicode, str_to_uascii
import passlib.utils.handlers as uh
from passlib.utils.md4 import md4
@@ -76,5 +76,65 @@ hex_sha256 = create_hex_hash(hashlib.sha256, "sha256")
hex_sha512 = create_hex_hash(hashlib.sha512, "sha512")
#=========================================================
+# htdigest
+#=========================================================
+class htdigest(object):
+ """htdigest hash function.
+
+ .. todo::
+ document this hash
+ """
+ name = "htdigest"
+ setting_kwds = ()
+ context_kwds = ("user", "realm")
+
+ @classmethod
+ def encrypt(cls, secret, user, realm, encoding="utf-8"):
+ # NOTE: deliberately written so that raw bytes are passed through
+ # unchanged, encoding only used to handle unicode values.
+ uh.validate_secret(secret)
+ if isinstance(secret, unicode):
+ secret = secret.encode(encoding)
+ user = to_bytes(user, encoding, "user")
+ realm = to_bytes(realm, encoding, "realm")
+ data = render_bytes("%s:%s:%s", user, realm, secret)
+ return hashlib.md5(data).hexdigest()
+
+ @classmethod
+ def _norm_hash(cls, hash):
+ "normalize hash to native string, and validate it"
+ hash = to_native_str(hash, errname="hash")
+ if len(hash) != 32:
+ raise uh.exc.MalformedHashError(cls, "wrong size")
+ for char in hash:
+ if char not in uh.LC_HEX_CHARS:
+ raise uh.exc.MalformedHashError(cls, "invalid chars in hash")
+ return hash
+
+ @classmethod
+ def verify(cls, secret, hash, user, realm, encoding="utf-8"):
+ hash = cls._norm_hash(hash)
+ other = cls.encrypt(secret, user, realm, encoding)
+ return consteq(hash, other)
+
+ @classmethod
+ def identify(cls, hash):
+ try:
+ cls._norm_hash(hash)
+ except ValueError:
+ return False
+ return True
+
+ @classmethod
+ def genconfig(cls):
+ return None
+
+ @classmethod
+ def genhash(cls, secret, config, user, realm, encoding="utf-8"):
+ if config is not None:
+ cls._norm_hash(config)
+ return cls.encrypt(secret, user, realm, encoding)
+
+#=========================================================
#eof
#=========================================================
diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py
index 3404bd8..28be83c 100644
--- a/passlib/handlers/fshp.py
+++ b/passlib/handlers/fshp.py
@@ -68,7 +68,9 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
max_salt_size = None
#--HasRounds--
- default_rounds = 16384 #current passlib default, FSHP uses 4096
+ # FIXME: should probably use different default rounds
+ # based on the variant. setting for default variant (sha256) for now.
+ default_rounds = 50000 #current passlib default, FSHP uses 4096
min_rounds = 1 #set by FSHP
max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
rounds_cost = "linear"
@@ -116,7 +118,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
if not isinstance(variant, int):
raise TypeError("fshp variant must be int or known alias")
if variant not in self._variant_info:
- raise TypeError("unknown fshp variant")
+ raise ValueError("invalid fshp variant")
return variant
@property
@@ -152,7 +154,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
rounds = int(rounds)
try:
data = b64decode(data.encode("ascii"))
- except ValueError:
+ except TypeError:
raise uh.exc.MalformedHashError(cls)
salt = data[:salt_size]
chk = data[salt_size:]
diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py
index 19089ee..f51e482 100644
--- a/passlib/handlers/ldap_digests.py
+++ b/passlib/handlers/ldap_digests.py
@@ -223,7 +223,7 @@ _init_ldap_crypt_handlers()
## global _lcn_host
## if _lcn_host is None:
## from passlib.hosts import host_context
-## schemes = host_context.policy.schemes()
+## schemes = host_context.schemes()
## _lcn_host = [
## "ldap_" + name
## for name in unix_crypt_names
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index 9441bbc..c963155 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -68,14 +68,13 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
#=====================================================================
#validate secret
+ # XXX: not sure what official unicode policy is, using this as default
if isinstance(pwd, unicode):
- # XXX: not sure what official unicode policy is, using this as default
pwd = pwd.encode("utf-8")
- elif not isinstance(pwd, bytes):
- raise TypeError("password must be bytes or unicode")
+ assert isinstance(pwd, bytes), "pwd not unicode or bytes"
pwd_len = len(pwd)
- #validate salt
+ #validate salt - should have been taken care of by caller
assert isinstance(salt, unicode), "salt not unicode"
salt = salt.encode("ascii")
assert len(salt) < 9, "salt too large"
diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py
index cb812ff..7121707 100644
--- a/passlib/handlers/misc.py
+++ b/passlib/handlers/misc.py
@@ -141,6 +141,8 @@ class unix_disabled(object):
# NOTE: config/hash will generally be "!" or "*",
# but we want to preserve it in case it has some other content,
# such as ``"!" + original hash``, which glibc uses.
+ # XXX: should this detect mcf header, or other things re:
+ # local system policy?
return to_native_str(config, errname="config")
else:
return to_native_str(marker or cls.marker, errname="marker")
diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py
index 662bdcd..9980518 100644
--- a/passlib/handlers/pbkdf2.py
+++ b/passlib/handlers/pbkdf2.py
@@ -44,7 +44,7 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen
max_salt_size = 1024
#--HasRounds--
- default_rounds = 6400
+ default_rounds = None # set by subclass
min_rounds = 1
max_rounds = 2**32-1
rounds_cost = "linear"
@@ -84,7 +84,7 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen
secret = secret.encode("utf-8")
return pbkdf2(secret, self.salt, self.rounds, self.checksum_size, self._prf)
-def create_pbkdf2_hash(hash_name, digest_size, ident=None):
+def create_pbkdf2_hash(hash_name, digest_size, rounds=6400, ident=None):
"create new Pbkdf2DigestHandler subclass for a specific hash"
name = 'pbkdf2_' + hash_name
if ident is None:
@@ -95,6 +95,7 @@ def create_pbkdf2_hash(hash_name, digest_size, ident=None):
name=name,
ident=ident,
_prf = prf,
+ default_rounds=rounds,
checksum_size=digest_size,
encoded_checksum_size=(digest_size*4+2)//3,
__doc__="""This class implements a generic ``PBKDF2-%(prf)s``-based password hash, and follows the :ref:`password-hash-api`.
@@ -115,15 +116,15 @@ def create_pbkdf2_hash(hash_name, digest_size, ident=None):
:param rounds:
Optional number of rounds to use.
Defaults to %(dr)d, but must be within ``range(1,1<<32)``.
- """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=base.default_rounds)
+ """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=rounds)
))
#---------------------------------------------------------
#derived handlers
#---------------------------------------------------------
-pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, ident=u("$pbkdf2$"))
-pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32)
-pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64)
+pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 32000, ident=u("$pbkdf2$"))
+pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 4000)
+pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 3200)
ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$")
ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$")
@@ -173,8 +174,8 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic
min_salt_size = 0
max_salt_size = 1024
- #--HasROunds--
- default_rounds = 10000
+ #--HasRounds--
+ default_rounds = 20000
min_rounds = 1
max_rounds = 2**32-1
rounds_cost = "linear"
@@ -260,8 +261,8 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
max_salt_size = 1024
salt_chars = uh.HASH64_CHARS
- #--HasROunds--
- default_rounds = 10000
+ #--HasRounds--
+ default_rounds = 20000
min_rounds = 1
max_rounds = 2**32-1
rounds_cost = "linear"
diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py
index c093255..00a4e33 100644
--- a/passlib/handlers/phpass.py
+++ b/passlib/handlers/phpass.py
@@ -64,7 +64,7 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#--HasRounds--
- default_rounds = 9
+ default_rounds = 16
min_rounds = 7
max_rounds = 30
rounds_cost = "log2"
diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py
index e7919a2..036d7c2 100644
--- a/passlib/handlers/scram.py
+++ b/passlib/handlers/scram.py
@@ -285,7 +285,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
raise ValueError("SCRAM limits algorithm names to "
"9 characters: %r" % (alg,))
if not isinstance(digest, bytes):
- raise TypeError("digests must be raw bytes")
+ raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests")
# TODO: verify digest size (if digest is known)
if 'sha-1' not in checksum:
# NOTE: required because of SCRAM spec.
@@ -374,9 +374,8 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
else:
failed = True
if correct and failed:
- warning("scram hash verified inconsistently, may be corrupted",
- PasslibHashWarning)
- return False
+ raise ValueError("scram hash verified inconsistently, "
+ "may be corrupted")
else:
return correct
else:
@@ -385,7 +384,8 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
if alg in chkmap:
other = self._calc_checksum(secret, alg)
return consteq(other, chkmap[alg])
- # there should *always* be at least sha-1.
+ # there should always be sha-1 at the very least,
+ # or something went wrong inside _norm_algs()
raise AssertionError("sha-1 digest not found!")
#=========================================================
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index e344912..29a18de 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -253,7 +253,6 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
max_salt_size = 16
salt_chars = uh.HASH64_CHARS
- default_rounds = 40000 # current passlib default
min_rounds = 1000 # bounds set by spec
max_rounds = 999999999 # bounds set by spec
rounds_cost = "linear"
@@ -393,6 +392,7 @@ class sha256_crypt(_SHA2_Common):
name = "sha256_crypt"
ident = u("$5$")
checksum_size = 43
+ default_rounds = 80000 # current passlib default
#=========================================================
# backends
@@ -448,6 +448,7 @@ class sha512_crypt(_SHA2_Common):
ident = u("$6$")
checksum_size = 86
_cdb_use_512 = True
+ default_rounds = 60000 # current passlib default
#=========================================================
# backend