def test_crypt(self): "test crypt.crypt() wrappers" from passlib.utils import has_crypt, safe_crypt, test_crypt # test everything is disabled if not has_crypt: self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", "aaqPiZY5xR5l.")) raise self.skipTest("crypt.crypt() not available") # XXX: this assumes *every* crypt() implementation supports des_crypt. # if this fails for some platform, this test will need modifying. # test return type self.assertIsInstance(safe_crypt(u("test"), u("aa")), unicode) # test ascii password h1 = u('aaqPiZY5xR5l.') self.assertEqual(safe_crypt(u('test'), u('aa')), h1) self.assertEqual(safe_crypt(b('test'), b('aa')), h1) # test utf-8 / unicode password h2 = u('aahWwbrUsKZk.') self.assertEqual(safe_crypt(u('test\u1234'), 'aa'), h2) self.assertEqual(safe_crypt(b('test\xe1\x88\xb4'), 'aa'), h2) # test latin-1 password hash = safe_crypt(b('test\xff'), 'aa') if PY3: # py3 supports utf-8 bytes only. self.assertEqual(hash, None) else: # but py2 is fine. self.assertEqual(hash, u('aaOx.5nbTU/.M')) # test rejects null chars in password self.assertRaises(ValueError, safe_crypt, '\x00', 'aa') # check test_crypt() h1x = h1[:-1] + 'x' self.assertTrue(test_crypt("test", h1)) self.assertFalse(test_crypt("test", h1x)) # check crypt returning variant error indicators # some platforms return None on errors, others empty string, # The BSDs in some cases return ":" import passlib.utils as mod orig = mod._crypt try: fake = None mod._crypt = lambda secret, hash: fake for fake in [None, "", ":", ":0", "*0"]: self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", h1)) fake = 'xxx' self.assertEqual(safe_crypt("test", "aa"), "xxx") finally: mod._crypt = orig
def crypt_supports_variant(self, hash): """check if OS crypt is expected to support given ident""" from passlib.handlers.bcrypt import bcrypt, IDENT_2X, IDENT_2Y from passlib.utils import safe_crypt ident = bcrypt.from_string(hash) return (safe_crypt("test", ident + "04$5BJqKfqMQvV7nS.yUguNcu") or "").startswith(ident)
def _calc_checksum_os_crypt(self, secret): config = self.ident + self.salt hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 23 return hash[-22:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string(config=True) hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 29 return hash[-28:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config[:9]) and len(hash) == 20 return hash[-11:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): # NOTE: we let safe_crypt() encode unicode secret -> utf8; # no official policy since des-crypt predates unicode hash = safe_crypt(secret, self.salt) if hash: assert hash.startswith(self.salt) and len(hash) == 13 return hash[2:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string(config=True) hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 29 return hash[-28:] else: return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): config = self.ident + self.salt hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 23 return hash[-22:] else: return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config[:9]) and len(hash) == 20 return hash[-11:] else: return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): # NOTE: safe_crypt encodes unicode secret -> utf8 # no official policy since des-crypt predates unicode hash = safe_crypt(secret, self.salt) if hash: assert hash.startswith(self.salt) and len(hash) == 13 return hash[2:] else: return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): hash = safe_crypt(secret, self.to_string()) if hash: # NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. cs = self.checksum_size assert hash.startswith(self.ident) and hash[-cs - 1] == _UDOLLAR return hash[-cs:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): hash = safe_crypt(secret, self.to_string()) if hash: # NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. cs = self.checksum_size assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR return hash[-cs:] return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): config = self.ident + self.salt hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 23 return hash[-22:] else: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config[:9]) and len(hash) == 20 return hash[-11:] else: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string() hash = safe_crypt(secret, config) if hash is None: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret) if not hash.startswith(config[:9]) or len(hash) != 20: raise uh.exc.CryptBackendError(self, config, hash) return hash[-11:]
def _calc_checksum_os_crypt(self, secret): config = self._get_config() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 31 return hash[-31:] # get here mainly if 1) under py3, and 2) secret is latin-1 or other non-unicode bytes. # in this case, another backend like pybcrypt should be able to get around # py3's limitations. return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): config = self._get_config() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config)+31 return hash[-31:] # get here mainly if 1) under py3, and 2) secret is latin-1 or other non-unicode bytes. # in this case, another backend like pybcrypt should be able to get around # py3's limitations. return self._try_alternate_backends(secret)
def _calc_checksum_os_crypt(self, secret): # NOTE: we let safe_crypt() encode unicode secret -> utf8; # no official policy since des-crypt predates unicode hash = safe_crypt(secret, self.salt) if hash: assert hash.startswith(self.salt) and len(hash) == 13 return hash[2:] else: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): # NOTE: we let safe_crypt() encode unicode secret -> utf8; # no official policy since des-crypt predates unicode hash = safe_crypt(secret, self.salt) if hash is None: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret) if not hash.startswith(self.salt) or len(hash) != 13: raise uh.exc.CryptBackendError(self, self.salt, hash) return hash[2:]
def _calc_checksum_os_crypt(self, secret): hash = safe_crypt(secret, self.to_string()) if hash: # NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. cs = self.checksum_size assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR return hash[-cs:] else: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): hash = safe_crypt(secret, self.to_string()) if hash: # NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. cs = self.checksum_size assert hash.startswith(self.ident) and hash[-cs - 1] == _UDOLLAR return hash[-cs:] else: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret)
def _calc_checksum_os_crypt(self, secret): config = self.to_string() hash = safe_crypt(secret, config) if hash is None: # py3's crypt.crypt() can't handle non-utf8 bytes. # fallback to builtin alg, which is always available. return self._calc_checksum_builtin(secret) # NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. cs = self.checksum_size if not hash.startswith(self.ident) or hash[-cs - 1] != _UDOLLAR: raise uh.exc.CryptBackendError(self, config, hash) return hash[-cs:]
def _calc_checksum_os_crypt(self, secret): config = self._get_config() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 31 return hash[-31:] else: # 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( "password can't be handled by os_crypt, " "recommend installing py-bcrypt.", )
def _calc_checksum_os_crypt(self, secret): config = self._get_config() hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config) + 31 return hash[-31:] else: # 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( "password can't be handled by os_crypt, " "recommend installing py-bcrypt." )
def _calc_checksum_os_crypt(self, secret, config): hash = safe_crypt(secret, config) if hash: assert hash.startswith(config) and len(hash) == len(config)+31 return hash[-31:] else: # NOTE: Have to raise this error because python3's crypt.crypt() only accepts unicode. # This means it can't handle any passwords that aren't either unicode # or utf-8 encoded bytes. However, hashing a password with an alternate # encoding should be a pretty rare edge case; if user needs it, they can just # install bcrypt backend. # XXX: is this the right error type to raise? # maybe have safe_crypt() not swallow UnicodeDecodeError, and have handlers # like sha256_crypt trap it if they have alternate method of handling them? raise uh.exc.MissingBackendError( "non-utf8 encoded passwords can't be handled by crypt.crypt() under python3, " "recommend running `pip install bcrypt`.", )
def test_crypt(self): """test crypt.crypt() wrappers""" from passlib.utils import has_crypt, safe_crypt, test_crypt from passlib.registry import get_supported_os_crypt_schemes, get_crypt_handler # test everything is disabled supported = get_supported_os_crypt_schemes() if not has_crypt: self.assertEqual(supported, ()) self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", "aaqPiZY5xR5l.")) # des_crypt() hash of "test" raise self.skipTest("crypt.crypt() not available") # expect there to be something supported, if crypt() is present if not supported: # NOTE: failures here should be investigated. usually means one of: # 1) at least one of passlib's os_crypt detection routines is giving false negative # 2) crypt() ONLY supports some hash alg which passlib doesn't know about # 3) crypt() is present but completely disabled (never encountered this yet) raise self.fail("crypt() present, but no supported schemes found!") # pick cheap alg if possible, with minimum rounds, to speed up this test. # NOTE: trusting hasher class works properly (should have been verified using it's own UTs) for scheme in ("md5_crypt", "sha256_crypt"): if scheme in supported: break else: scheme = supported[-1] hasher = get_crypt_handler(scheme) if getattr(hasher, "min_rounds", None): hasher = hasher.using(rounds=hasher.min_rounds) # helpers to generate hashes & config strings to work with def get_hash(secret): assert isinstance(secret, unicode) hash = hasher.hash(secret) if isinstance(hash, bytes): # py2 hash = hash.decode("utf-8") assert isinstance(hash, unicode) return hash # test ascii password & return type s1 = u("test") h1 = get_hash(s1) result = safe_crypt(s1, h1) self.assertIsInstance(result, unicode) self.assertEqual(result, h1) self.assertEqual(safe_crypt(to_bytes(s1), to_bytes(h1)), h1) # make sure crypt doesn't just blindly return h1 for whatever we pass in h1x = h1[:-2] + 'xx' self.assertEqual(safe_crypt(s1, h1x), h1) # test utf-8 / unicode password s2 = u('test\u1234') h2 = get_hash(s2) self.assertEqual(safe_crypt(s2, h2), h2) self.assertEqual(safe_crypt(to_bytes(s2), to_bytes(h2)), h2) # test rejects null chars in password self.assertRaises(ValueError, safe_crypt, '\x00', h1) # check test_crypt() self.assertTrue(test_crypt("test", h1)) self.assertFalse(test_crypt("test", h1x)) # check crypt returning variant error indicators # some platforms return None on errors, others empty string, # The BSDs in some cases return ":" import passlib.utils as mod orig = mod._crypt try: retval = None mod._crypt = lambda secret, hash: retval for retval in [None, "", ":", ":0", "*0"]: self.assertEqual(safe_crypt("test", h1), None) self.assertFalse(test_crypt("test", h1)) retval = 'xxx' self.assertEqual(safe_crypt("test", h1), "xxx") self.assertFalse(test_crypt("test", h1)) finally: mod._crypt = orig