diff options
Diffstat (limited to 'passlib/handlers')
-rw-r--r-- | passlib/handlers/bcrypt.py | 13 | ||||
-rw-r--r-- | passlib/handlers/cisco.py | 2 | ||||
-rw-r--r-- | passlib/handlers/des_crypt.py | 245 | ||||
-rw-r--r-- | passlib/handlers/digests.py | 62 | ||||
-rw-r--r-- | passlib/handlers/fshp.py | 8 | ||||
-rw-r--r-- | passlib/handlers/ldap_digests.py | 2 | ||||
-rw-r--r-- | passlib/handlers/md5_crypt.py | 7 | ||||
-rw-r--r-- | passlib/handlers/misc.py | 2 | ||||
-rw-r--r-- | passlib/handlers/pbkdf2.py | 21 | ||||
-rw-r--r-- | passlib/handlers/phpass.py | 2 | ||||
-rw-r--r-- | passlib/handlers/scram.py | 10 | ||||
-rw-r--r-- | passlib/handlers/sha2_crypt.py | 3 |
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 |