diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-30 21:19:06 -0400 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-30 21:19:06 -0400 |
commit | cf13872f382961729c922b6852f522364b167099 (patch) | |
tree | 590332de7fbc2779efce2fdbde67aaffc4d645f9 | |
parent | d3650f5a8458d84c4ef7886aced7e99ef3935bfd (diff) | |
download | passlib-cf13872f382961729c922b6852f522364b167099.tar.gz |
all os_crypt hashes now forbidden NULL chars
-rw-r--r-- | passlib/handlers/bcrypt.py | 12 | ||||
-rw-r--r-- | passlib/handlers/des_crypt.py | 27 | ||||
-rw-r--r-- | passlib/handlers/md5_crypt.py | 14 | ||||
-rw-r--r-- | passlib/handlers/sha1_crypt.py | 9 | ||||
-rw-r--r-- | passlib/handlers/sha2_crypt.py | 8 |
5 files changed, 47 insertions, 23 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py index 94fd9fd..77a99da 100644 --- a/passlib/handlers/bcrypt.py +++ b/passlib/handlers/bcrypt.py @@ -29,7 +29,7 @@ except ImportError: #pragma: no cover from passlib.exc import PasslibHashWarning, PasslibSecurityWarning from passlib.utils import bcrypt64, safe_crypt, repeat_string, \ classproperty, rng, getrandstr, test_crypt -from passlib.utils.compat import bytes, u, uascii_to_str, unicode, str_to_uascii +from passlib.utils.compat import bytes, b, u, uascii_to_str, unicode, str_to_uascii import passlib.utils.handlers as uh #pkg @@ -52,6 +52,7 @@ IDENT_2 = u("$2$") IDENT_2A = u("$2a$") IDENT_2X = u("$2x$") IDENT_2Y = u("$2y$") +_BNULL = b('\x00') #========================================================= # handler @@ -266,6 +267,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh. # py3: not supported (patch submitted) if isinstance(secret, unicode): secret = secret.encode("utf-8") + if _BNULL in secret: + raise uh.exc.NullPasswordError(self) config = self._get_config() hash = pybcrypt_hashpw(secret, config) assert hash.startswith(config) and len(hash) == len(config)+31 @@ -278,6 +281,11 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh. # py3: not supported if isinstance(secret, unicode): secret = secret.encode("utf-8") + if _BNULL in secret: + # NOTE: especially important to forbid NULLs for bcryptor, + # since it happily accepts them, and then silently truncates + # the password at first one it encounters :( + raise uh.exc.NullPasswordError(self) if self.ident == IDENT_2: # bcryptor doesn't support $2$ hashes; but we can fake $2$ behavior # using the $2a$ algorithm, by repeating the password until @@ -299,6 +307,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh. "Passlib to use instead.", PasslibSecurityWarning) if isinstance(secret, unicode): secret = secret.encode("utf-8") + if _BNULL in secret: + raise uh.exc.NullPasswordError(self) chk = _builtin_bcrypt(secret, self.ident.strip("$"), self.salt.encode("ascii"), self.rounds) return chk.decode("ascii") diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index 2d206b8..81c689a 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -24,6 +24,8 @@ __all__ = [ #========================================================= # pure-python backend for des_crypt family #========================================================= +_BNULL = b('\x00') + def _crypt_secret_to_key(secret): """convert secret to 64-bit DES key. @@ -53,9 +55,14 @@ def _raw_des_crypt(secret, salt): except ValueError: #pragma: no cover - always caught by class raise ValueError("invalid chars in salt") + # gotta do something - no official policy since this predates unicode + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + assert isinstance(secret, bytes) + # forbidding NULL char because underlying crypt() rejects them too. - if b('\x00') in secret: - raise ValueError("null char in secret") + if _BNULL in secret: + raise uh.exc.NullPasswordError(des_crypt) # convert first 8 bytes of secret string into an integer key_value = _crypt_secret_to_key(secret) @@ -87,9 +94,14 @@ def _raw_bsdi_crypt(secret, rounds, salt): except ValueError: #pragma: no cover - always caught by class raise ValueError("invalid salt") + # gotta do something - no official policy since this predates unicode + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + assert isinstance(secret, bytes) + # forbidding NULL char because underlying crypt() rejects them too. - if b('\x00') in secret: - raise ValueError("secret must be string without null bytes") + if _BNULL in secret: + raise uh.exc.NullPasswordError(bsdi_crypt) # convert secret string into an integer key_value = _bsdi_secret_to_key(secret) @@ -162,9 +174,6 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler): return test_crypt("test", 'abgOeLfPimXQo') def _calc_checksum_builtin(self, secret): - # gotta do something - no official policy since des-crypt predates unicode - if isinstance(secret, unicode): - secret = secret.encode("utf-8") return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii") def _calc_checksum_os_crypt(self, secret): @@ -295,8 +304,6 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler return test_crypt("test", '_/...lLDAxARksGCHin.') def _calc_checksum_builtin(self, secret): - if isinstance(secret, unicode): - secret = secret.encode("utf-8") return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii") def _calc_checksum_os_crypt(self, secret): @@ -363,7 +370,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): def _norm_checksum(self, value): value = super(bigcrypt, self)._norm_checksum(value) if value and len(value) % 11: - raise uh.exc.InvalidHashError(cls) + raise uh.exc.InvalidHashError(self) return value #========================================================= diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py index 5810104..cd413d6 100644 --- a/passlib/handlers/md5_crypt.py +++ b/passlib/handlers/md5_crypt.py @@ -22,9 +22,9 @@ __all__ = [ #========================================================= #pure-python backend #========================================================= -B_NULL = b("\x00") -B_MD5_MAGIC = b("$1$") -B_APR_MAGIC = b("$apr1$") +_BNULL = b("\x00") +_MD5_MAGIC = b("$1$") +_APR_MAGIC = b("$apr1$") # pre-calculated offsets used to speed up C digest stage (see notes below). # sequence generated using the following: @@ -72,6 +72,8 @@ def _raw_md5_crypt(pwd, salt, use_apr=False): if isinstance(pwd, unicode): pwd = pwd.encode("utf-8") assert isinstance(pwd, bytes), "pwd not unicode or bytes" + if _BNULL in pwd: + raise uh.exc.NullPasswordError(md5_crypt) pwd_len = len(pwd) #validate salt - should have been taken care of by caller @@ -84,9 +86,9 @@ def _raw_md5_crypt(pwd, salt, use_apr=False): # load APR specific constants if use_apr: - magic = B_APR_MAGIC + magic = _APR_MAGIC else: - magic = B_MD5_MAGIC + magic = _MD5_MAGIC #===================================================================== # digest B - used as subinput to digest A @@ -111,7 +113,7 @@ def _raw_md5_crypt(pwd, salt, use_apr=False): i = pwd_len evenchar = pwd[:1] while i: - a_ctx_update(B_NULL if i & 1 else evenchar) + a_ctx_update(_BNULL if i & 1 else evenchar) i >>= 1 # finish A diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py index 852b493..36d6780 100644 --- a/passlib/handlers/sha1_crypt.py +++ b/passlib/handlers/sha1_crypt.py @@ -25,6 +25,7 @@ __all__ = [ #sha1-crypt #========================================================= _hmac_sha1 = get_prf("hmac-sha1")[0] +_BNULL = b('\x00') class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`. @@ -100,10 +101,12 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler def _calc_checksum_builtin(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") + if _BNULL in secret: + raise uh.exc.NullPasswordError(self) rounds = self.rounds - #NOTE: this uses a different format than the hash... - result = u("%s$sha1$%s") % (self.salt, rounds) - result = result.encode("ascii") + # NOTE: this seed value is NOT the same as the config string + result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii") + # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC. r = 0 while r < rounds: result = _hmac_sha1(secret, result) diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py index 13de342..4bcf3f2 100644 --- a/passlib/handlers/sha2_crypt.py +++ b/passlib/handlers/sha2_crypt.py @@ -11,7 +11,7 @@ from warnings import warn from passlib.utils import classproperty, h64, safe_crypt, test_crypt, \ repeat_string, to_unicode from passlib.utils.compat import b, bytes, byte_elem_value, irange, u, \ - uascii_to_str, unicode, lmap + uascii_to_str, unicode import passlib.utils.handlers as uh #pkg #local @@ -24,6 +24,7 @@ __all__ = [ # pure-python backend, used by both sha256_crypt & sha512_crypt # when crypt.crypt() backend is not available. #============================================================== +_BNULL = b('\x00') # pre-calculated offsets used to speed up C digest stage (see notes below). # sequence generated using the following: @@ -76,8 +77,9 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False): 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) + if _BNULL in pwd: + raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt) pwd_len = len(pwd) # validate rounds |