class SaltedHash(uh.HasSalt, uh.GenericHandler): """test algorithm with a salt""" name = "salted_test_hash" setting_kwds = ("salt",) min_salt_size = 2 max_salt_size = 4 checksum_size = 40 salt_chars = checksum_chars = uh.LOWER_HEX_CHARS _hash_regex = re.compile(u("^@salt[0-9a-f]{42,44}$")) @classmethod def from_string(cls, hash): if not cls.identify(hash): raise uh.exc.InvalidHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") return cls(salt=hash[5:-40], checksum=hash[-40:]) _stub_checksum = u('0') * 40 def to_string(self): hash = u("@salt%s%s") % (self.salt, self.checksum or self._stub_checksum) return uascii_to_str(hash) def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") return str_to_uascii(hashlib.sha1(data).hexdigest())
def test_to_bytes(self): """test to_bytes()""" from lib.passlib.utils import to_bytes # check unicode inputs self.assertEqual(to_bytes(u('abc')), b('abc')) self.assertEqual(to_bytes(u('\x00\xff')), b('\x00\xc3\xbf')) # check unicode w/ encodings self.assertEqual(to_bytes(u('\x00\xff'), 'latin-1'), b('\x00\xff')) self.assertRaises(ValueError, to_bytes, u('\x00\xff'), 'ascii') # check bytes inputs self.assertEqual(to_bytes(b('abc')), b('abc')) self.assertEqual(to_bytes(b('\x00\xff')), b('\x00\xff')) self.assertEqual(to_bytes(b('\x00\xc3\xbf')), b('\x00\xc3\xbf')) # check byte inputs ignores enocding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"), b('\x00\xc3\xbf')) # check bytes transcoding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "", "utf-8"), b('\x00\xff')) # check other self.assertRaises(AssertionError, to_bytes, 'abc', None) self.assertRaises(TypeError, to_bytes, None)
def test_is_ascii_safe(self): """test is_ascii_safe()""" from lib.passlib.utils import is_ascii_safe self.assertTrue(is_ascii_safe(b("\x00abc\x7f"))) self.assertTrue(is_ascii_safe(u("\x00abc\x7f"))) self.assertFalse(is_ascii_safe(b("\x00abc\x80"))) self.assertFalse(is_ascii_safe(u("\x00abc\x80")))
def test_10_identify(self): """test GenericHandler.identify()""" class d1(uh.GenericHandler): @classmethod def from_string(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") if hash == u('a'): return cls(checksum=hash) else: raise ValueError # check fallback self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) self.assertFalse(d1.identify('')) self.assertTrue(d1.identify('a')) self.assertFalse(d1.identify('b')) # check regexp d1._hash_regex = re.compile(u('@.')) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) self.assertTrue(d1.identify('@a')) self.assertFalse(d1.identify('a')) del d1._hash_regex # check ident-based d1.ident = u('!') self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) self.assertTrue(d1.identify('!a')) self.assertFalse(d1.identify('a')) del d1.ident
def test_to_native_str(self): """test to_native_str()""" from lib.passlib.utils import to_native_str # test plain ascii self.assertEqual(to_native_str(u('abc'), 'ascii'), 'abc') self.assertEqual(to_native_str(b('abc'), 'ascii'), 'abc') # test invalid ascii if PY3: self.assertEqual(to_native_str(u('\xE0'), 'ascii'), '\xE0') self.assertRaises(UnicodeDecodeError, to_native_str, b('\xC3\xA0'), 'ascii') else: self.assertRaises(UnicodeEncodeError, to_native_str, u('\xE0'), 'ascii') self.assertEqual(to_native_str(b('\xC3\xA0'), 'ascii'), '\xC3\xA0') # test latin-1 self.assertEqual(to_native_str(u('\xE0'), 'latin-1'), '\xE0') self.assertEqual(to_native_str(b('\xE0'), 'latin-1'), '\xE0') # test utf-8 self.assertEqual(to_native_str(u('\xE0'), 'utf-8'), '\xE0' if PY3 else '\xC3\xA0') self.assertEqual(to_native_str(b('\xC3\xA0'), 'utf-8'), '\xE0' if PY3 else '\xC3\xA0') # other types rejected self.assertRaises(TypeError, to_native_str, None, 'ascii')
def test_02_handler_wrapper(self): """test Hasher-compatible handler wrappers""" if not has_django14: raise self.skipTest("Django >= 1.4 not installed") from lib.passlib.ext.django.utils import get_passlib_hasher from django.contrib.auth import hashers # should return native django hasher if available hasher = get_passlib_hasher("hex_md5") self.assertIsInstance(hasher, hashers.UnsaltedMD5PasswordHasher) hasher = get_passlib_hasher("django_bcrypt") self.assertIsInstance(hasher, hashers.BCryptPasswordHasher) # otherwise should return wrapper from lib.passlib.hash import sha256_crypt hasher = get_passlib_hasher("sha256_crypt") self.assertEqual(hasher.algorithm, "passlib_sha256_crypt") # and wrapper should return correct hash encoded = hasher.encode("stub") self.assertTrue(sha256_crypt.verify("stub", encoded)) self.assertTrue(hasher.verify("stub", encoded)) self.assertFalse(hasher.verify("xxxx", encoded)) # test wrapper accepts options encoded = hasher.encode("stub", "abcd"*4, iterations=1234) self.assertEqual(encoded, "$5$rounds=1234$abcdabcdabcdabcd$" "v2RWkZQzctPdejyRqmmTDQpZN6wTh7.RUy9zF2LftT6") self.assertEqual(hasher.safe_summary(encoded), {'algorithm': 'sha256_crypt', 'salt': u('abcdab**********'), 'iterations': 1234, 'hash': u('v2RWkZ*************************************'), })
def to_string(self): if self.rounds == 5000 and self.implicit_rounds: hash = u("%s%s$%s") % (self.ident, self.salt, self.checksum or u('')) else: hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds, self.salt, self.checksum or u('')) return uascii_to_str(hash)
def test_lmhash(self): from lib.passlib.win32 import raw_lmhash for secret, hash in [ ("OLDPASSWORD", u("c9b81d939d6fd80cd408e6b105741864")), ("NEWPASSWORD", u('09eeab5aa415d6e4d408e6b105741864')), ("welcome", u("c23413a8a1e7665faad3b435b51404ee")), ]: result = raw_lmhash(secret, hex=True) self.assertEqual(result, hash)
class ldap_md5(_Base64DigestHelper): """This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords. """ name = "ldap_md5" ident = u("{MD5}") _hash_func = md5 _hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$"))
class ldap_sha1(_Base64DigestHelper): """This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords. """ name = "ldap_sha1" ident = u("{SHA}") _hash_func = sha1 _hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$"))
def test_nthash(self): warnings.filterwarnings("ignore", r"nthash\.raw_nthash\(\) is deprecated") from lib.passlib.win32 import raw_nthash for secret, hash in [ ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), ]: result = raw_nthash(secret, hex=True) self.assertEqual(result, hash)
def test_91_parsehash(self): """test parsehash()""" # NOTE: this just tests some existing GenericHandler classes from lib.passlib import hash # # parsehash() # # simple hash w/ salt result = hash.des_crypt.parsehash("OgAwTx2l6NADI") self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')}) # parse rounds and extra implicit_rounds flag h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9' s = u('LKO/Ute40T3FNF95') c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9') result = hash.sha256_crypt.parsehash(h) self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True, checksum=c)) # omit checksum result = hash.sha256_crypt.parsehash(h, checksum=False) self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True)) # sanitize result = hash.sha256_crypt.parsehash(h, sanitize=True) self.assertEqual(result, dict(rounds=5000, implicit_rounds=True, salt=u('LK**************'), checksum=u('U0pr***************************************'))) # parse w/o implicit rounds flag result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3') self.assertEqual(result, dict( checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), salt=u('uy/jIAhCetNCTtb0'), rounds=10428, )) # parsing of raw checksums & salts h1 = '$pbkdf2$60000$DoEwpvQeA8B4T.k951yLUQ$O26Y3/NJEiLCVaOVPxGXshyjW8k' result = hash.pbkdf2_sha1.parsehash(h1) self.assertEqual(result, dict( checksum=b(';n\x98\xdf\xf3I\x12"\xc2U\xa3\x95?\x11\x97\xb2\x1c\xa3[\xc9'), rounds=60000, salt=b('\x0e\x810\xa6\xf4\x1e\x03\xc0xO\xe9=\xe7\\\x8bQ'), )) # sanitizing of raw checksums & salts result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True) self.assertEqual(result, dict( checksum=u('O26************************'), rounds=60000, salt=u('Do********************'), ))
def test_11_norm_checksum(self): """test GenericHandler checksum handling""" # setup helpers class d1(uh.GenericHandler): name = 'd1' checksum_size = 4 checksum_chars = u('xz') _stub_checksum = u('z')*4 def norm_checksum(*a, **k): return d1(*a, **k).checksum # too small self.assertRaises(ValueError, norm_checksum, u('xxx')) # right size self.assertEqual(norm_checksum(u('xxxx')), u('xxxx')) self.assertEqual(norm_checksum(u('xzxz')), u('xzxz')) # too large self.assertRaises(ValueError, norm_checksum, u('xxxxx')) # wrong chars self.assertRaises(ValueError, norm_checksum, u('xxyx')) # wrong type self.assertRaises(TypeError, norm_checksum, b('xxyx')) # relaxed with self.assertWarningList("checksum should be unicode"): self.assertEqual(norm_checksum(b('xxzx'), relaxed=True), u('xxzx')) self.assertRaises(TypeError, norm_checksum, 1, relaxed=True) # test _stub_checksum behavior self.assertIs(norm_checksum(u('zzzz')), None)
def to_string(self, _withchk=True): ss = u('') if self.bare_salt else u('$') rounds = self.rounds if rounds > 0: hash = u("$md5,rounds=%d$%s%s") % (rounds, self.salt, ss) else: hash = u("$md5$%s%s") % (self.salt, ss) if _withchk: chk = self.checksum hash = u("%s$%s") % (hash, chk) return uascii_to_str(hash)
def to_string(self, withchk=True): ss = u('') if self.bare_salt else u('$') rounds = self.rounds if rounds > 0: hash = u("$md5,rounds=%d$%s%s") % (rounds, self.salt, ss) else: hash = u("$md5$%s%s") % (self.salt, ss) if withchk: chk = self.checksum if chk: hash = u("%s$%s") % (hash, chk) return uascii_to_str(hash)
class d1(uh.StaticHandler): name = "d1" context_kwds = ("flag",) _hash_prefix = u("_") checksum_chars = u("ab") checksum_size = 1 def __init__(self, flag=False, **kwds): super(d1, self).__init__(**kwds) self.flag = flag def _calc_checksum(self, secret): return u('b') if self.flag else u('a')
def _calc_checksum(self, secret): if self.checksum: # NOTE: hash will generally be "!", but we want to preserve # it in case it's something else, like "*". return self.checksum else: return u("!")
def from_string(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") if hash == u('a'): return cls(checksum=hash) else: raise ValueError
class ldap_plaintext(plaintext): """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, except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix used by RFC2307 passwords. The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the following additional contextual keyword: :type encoding: str :param encoding: This controls the character encoding to use (defaults to ``utf-8``). This encoding will be used to encode :class:`!unicode` passwords under Python 2, and decode :class:`!bytes` hashes under Python 3. .. versionchanged:: 1.6 The ``encoding`` keyword was added. """ # NOTE: this subclasses plaintext, since all it does differently # is override identify() name = "ldap_plaintext" _2307_pat = re.compile(u(r"^\{\w+\}.*$")) @classmethod def identify(cls, hash): # NOTE: identifies all strings EXCEPT those with {XXX} prefix hash = uh.to_unicode_for_identify(hash) return bool(hash) and cls._2307_pat.match(hash) is None
def to_string(self, withchk=True): salt = hexlify(self.salt).decode("ascii").upper() if withchk and self.checksum: chk = hexlify(self.checksum).decode("ascii").upper() else: chk = None return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u("."))
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. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords. """ #=================================================================== # class attrs #=================================================================== name = "mysql41" _hash_prefix = u("*") checksum_chars = uh.HEX_CHARS checksum_size = 40 #=================================================================== # methods #=================================================================== @classmethod def _norm_hash(cls, hash): return hash.upper() def _calc_checksum(self, secret): # FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper()
def from_string(cls, hash): rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."), handler=cls) salt = unhexlify(salt.encode("ascii")) if chk: chk = unhexlify(chk.encode("ascii")) return cls(rounds=rounds, salt=salt, checksum=chk)
def test_norm_hash_name(self): """test norm_hash_name()""" from itertools import chain from lib.passlib.utils.pbkdf2 import norm_hash_name, _nhn_hash_names # test formats for format in self.ndn_formats: norm_hash_name("md4", format) self.assertRaises(ValueError, norm_hash_name, "md4", None) self.assertRaises(ValueError, norm_hash_name, "md4", "fake") # test types self.assertEqual(norm_hash_name(u("MD4")), "md4") self.assertEqual(norm_hash_name(b("MD4")), "md4") self.assertRaises(TypeError, norm_hash_name, None) # test selected results with catch_warnings(): warnings.filterwarnings("ignore", '.*unknown hash') for row in chain(_nhn_hash_names, self.ndn_values): for idx, format in enumerate(self.ndn_formats): correct = row[idx] for value in row: result = norm_hash_name(value, format) self.assertEqual( result, correct, "name=%r, format=%r:" % (value, format))
class postgres_md5(uh.HasUserContext, uh.StaticHandler): """This class implements the Postgres MD5 Password hash, and follows the :ref:`password-hash-api`. It does a single round of hashing, and relies on the username as the salt. The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the following additional contextual keywords: :type user: str :param user: name of postgres user account this password is associated with. """ #=================================================================== # algorithm information #=================================================================== name = "postgres_md5" _hash_prefix = u("md5") checksum_chars = uh.HEX_CHARS checksum_size = 32 #=================================================================== # primary interface #=================================================================== def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") user = to_bytes(self.user, "utf-8", param="user") return str_to_uascii(md5(secret + user).hexdigest())
class apr_md5_crypt(_MD5_Common): """This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords: :type salt: str :param salt: Optional salt string. If not specified, one will be autogenerated (this is recommended). If specified, it must be 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``salt`` strings that are too long. .. versionadded:: 1.6 """ #=================================================================== # class attrs #=================================================================== name = "apr_md5_crypt" ident = u("$apr1$") #=================================================================== # methods #=================================================================== def _calc_checksum(self, secret): return _raw_md5_crypt(secret, self.salt, use_apr=True)
def genseed(value=None): """generate prng seed value from system resources""" from hashlib import sha512 if hasattr(value, "getstate") and hasattr(value, "getrandbits"): # caller passed in RNG as seed value try: value = value.getstate() except NotImplementedError: # this method throws error for e.g. SystemRandom instances, # so fall back to extracting 4k of state value = value.getrandbits(1 << 15) text = u("%s %s %s %.15f %.15f %s") % ( # if caller specified a seed value, mix it in value, # add current process id # NOTE: not available in some environments, e.g. GAE os.getpid() if hasattr(os, "getpid") else None, # id of a freshly created object. # (at least 1 byte of which should be hard to predict) id(object()), # the current time, to whatever precision os uses time.time(), tick(), # if urandom available, might as well mix some bytes in. os.urandom(32).decode("latin-1") if has_urandom else 0, ) # hash it all up and return it as int/long return int(sha512(text.encode("utf-8")).hexdigest(), 16)
def test_secret_param(self): """'secret' parameter""" def run_scrypt(secret): return hexstr(scrypt_mod.scrypt(secret, "salt", 2, 2, 2, 16)) # unicode TEXT = u("abc\u00defg") self.assertEqual(run_scrypt(TEXT), '05717106997bfe0da42cf4779a2f8bd8') # utf8 bytes TEXT_UTF8 = b'abc\xc3\x9efg' self.assertEqual(run_scrypt(TEXT_UTF8), '05717106997bfe0da42cf4779a2f8bd8') # latin1 bytes TEXT_LATIN1 = b'abc\xdefg' self.assertEqual(run_scrypt(TEXT_LATIN1), '770825d10eeaaeaf98e8a3c40f9f441d') # accept empty string self.assertEqual(run_scrypt(""), 'ca1399e5fae5d3b9578dcd2b1faff6e2') # reject other types self.assertRaises(TypeError, run_scrypt, None) self.assertRaises(TypeError, run_scrypt, 1)
def from_string(cls, hash): ident, tail = cls._parse_ident(hash) if ident == IDENT_2X: raise ValueError("crypt_blowfish's buggy '2x' hashes are not " "currently supported") rounds_str, data = tail.split(u("$")) rounds = int(rounds_str) if rounds_str != u('%02d') % (rounds, ): raise uh.exc.MalformedHashError(cls, "malformed cost field") salt, chk = data[:22], data[22:] return cls( rounds=rounds, salt=salt, checksum=chk or None, ident=ident, )
def _init_ldap_crypt_handlers(): # NOTE: I don't like to implicitly modify globals() like this, # but don't want to write out all these handlers out either :) g = globals() for wname in unix_crypt_schemes: name = 'ldap_' + wname g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True) del g
def test_12_ident(self): # test ident is proxied h = uh.PrefixWrapper("h2", "ldap_md5", "{XXX}") self.assertEqual(h.ident, u("{XXX}{MD5}")) self.assertIs(h.ident_values, None) # test lack of ident means no proxy h = uh.PrefixWrapper("h2", "des_crypt", "{XXX}") self.assertIs(h.ident, None) self.assertIs(h.ident_values, None) # test orig_prefix disabled ident proxy h = uh.PrefixWrapper("h1", "ldap_md5", "{XXX}", "{MD5}") self.assertIs(h.ident, None) self.assertIs(h.ident_values, None) # test custom ident overrides default h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{X") self.assertEqual(h.ident, u("{X")) self.assertIs(h.ident_values, None) # test custom ident must match h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{XXX}A") self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5", "{XXX}", ident="{XY") self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5", "{XXX}", ident="{XXXX") # test ident_values is proxied h = uh.PrefixWrapper("h4", "phpass", "{XXX}") self.assertIs(h.ident, None) self.assertEqual(h.ident_values, [ u("{XXX}$P$"), u("{XXX}$H$") ]) # test ident=True means use prefix even if hash has no ident. h = uh.PrefixWrapper("h5", "des_crypt", "{XXX}", ident=True) self.assertEqual(h.ident, u("{XXX}")) self.assertIs(h.ident_values, None) # ... but requires prefix self.assertRaises(ValueError, uh.PrefixWrapper, "h6", "des_crypt", ident=True) # orig_prefix + HasManyIdent - warning with self.assertWarningList("orig_prefix.*may not work correctly"): h = uh.PrefixWrapper("h7", "phpass", orig_prefix="$", prefix="?") self.assertEqual(h.ident_values, None) # TODO: should output (u("?P$"), u("?H$"))) self.assertEqual(h.ident, None)
def test_crypt(self): """test crypt.crypt() wrappers""" from lib.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 lib.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 test_12_norm_checksum_raw(self): """test GenericHandler + HasRawChecksum mixin""" class d1(uh.HasRawChecksum, uh.GenericHandler): name = 'd1' checksum_size = 4 _stub_checksum = b('0')*4 def norm_checksum(*a, **k): return d1(*a, **k).checksum # test bytes self.assertEqual(norm_checksum(b('1234')), b('1234')) # test unicode self.assertRaises(TypeError, norm_checksum, u('xxyx')) self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True) # test _stub_checksum behavior self.assertIs(norm_checksum(b('0')*4), None)
def test_00_static_handler(self): """test StaticHandler class""" class d1(uh.StaticHandler): name = "d1" context_kwds = ("flag",) _hash_prefix = u("_") checksum_chars = u("ab") checksum_size = 1 def __init__(self, flag=False, **kwds): super(d1, self).__init__(**kwds) self.flag = flag def _calc_checksum(self, secret): return u('b') if self.flag else u('a') # check default identify method self.assertTrue(d1.identify(u('_a'))) self.assertTrue(d1.identify(b('_a'))) self.assertTrue(d1.identify(u('_b'))) self.assertFalse(d1.identify(u('_c'))) self.assertFalse(d1.identify(b('_c'))) self.assertFalse(d1.identify(u('a'))) self.assertFalse(d1.identify(u('b'))) self.assertFalse(d1.identify(u('c'))) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) # check default genconfig method self.assertIs(d1.genconfig(), None) # check default verify method self.assertTrue(d1.verify('s', b('_a'))) self.assertTrue(d1.verify('s',u('_a'))) self.assertFalse(d1.verify('s', b('_b'))) self.assertFalse(d1.verify('s',u('_b'))) self.assertTrue(d1.verify('s', b('_b'), flag=True)) self.assertRaises(ValueError, d1.verify, 's', b('_c')) self.assertRaises(ValueError, d1.verify, 's', u('_c')) # check default encrypt method self.assertEqual(d1.encrypt('s'), '_a') self.assertEqual(d1.encrypt('s', flag=True), '_b')
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 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) r += 1 return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__): """create new Pbkdf2DigestHandler subclass for a specific hash""" name = 'pbkdf2_' + hash_name if ident is None: ident = u("$pbkdf2-%s$") % (hash_name,) prf = "hmac-%s" % (hash_name,) base = Pbkdf2DigestHandler return type(name, (base,), dict( __module__=module, # so ABCMeta won't clobber it. 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`. It supports a variable-length salt, and a variable number of rounds. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords: :type salt: bytes :param salt: Optional salt bytes. If specified, the length must be between 0-1024 bytes. If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended). :type salt_size: int :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any value between 0 and 1024. :type rounds: int :param rounds: Optional number of rounds to use. Defaults to %(dr)d, but must be within ``range(1,1<<32)``. :type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``rounds`` that are too small or too large, and ``salt`` strings that are too long. .. versionadded:: 1.6 """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=rounds) ))
def _calc_checksum(self, secret): # FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") MASK_32 = 0xffffffff MASK_31 = 0x7fffffff WHITE = b(' \t') nr1 = 0x50305735 nr2 = 0x12345671 add = 7 for c in secret: if c in WHITE: continue tmp = byte_elem_value(c) nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32 add = (add+tmp) & MASK_32 return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
:type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``rounds`` that are too small or too large, and ``salt`` strings that are too long. .. versionadded:: 1.6 """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=rounds) )) #------------------------------------------------------------------------ # derived handlers #------------------------------------------------------------------------ pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$")) pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000) pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000) ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True) ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True) ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True) #============================================================================= # cryptacular's pbkdf2 hash #============================================================================= # bytes used by cta hash for base64 values 63 & 64 CTA_ALTCHARS = b("-_") class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
# it in case it's something else, like "*". return self.checksum else: return u("!") @classmethod def verify(cls, secret, hash, enable_wildcard=False): uh.validate_secret(secret) if not isinstance(hash, base_string_types): raise uh.exc.ExpectedStringError(hash, "hash") elif hash: return False else: return enable_wildcard _MARKER_CHARS = u("*!") _MARKER_BYTES = b("*!") class unix_disabled(uh.PasswordHash): """This class provides disabled password behavior for unix shadow files, and follows the :ref:`password-hash-api`. This class does not implement a hash, but instead matches the "disabled account" strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password will simply return the disabled account marker. It will reject all passwords, no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.encrypt` method supports one optional keyword: :type marker: str :param marker: Optional marker string which overrides the platform default
def to_string(self): hash = u("@salt%s%s") % (self.salt, self.checksum or self._stub_checksum) return uascii_to_str(hash)
"""passlib.handlers.roundup - Roundup issue tracker hashes""" #============================================================================= # imports #============================================================================= # core import logging; log = logging.getLogger(__name__) # site # pkg import lib.passlib.utils.handlers as uh from lib.passlib.utils.compat import u # local __all__ = [ "roundup_plaintext", "ldap_hex_md5", "ldap_hex_sha1", ] #============================================================================= # #============================================================================= roundup_plaintext = uh.PrefixWrapper("roundup_plaintext", "plaintext", prefix=u("{plaintext}"), lazy=True) # NOTE: these are here because they're currently only known to be used by roundup ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u("{MD5}"), lazy=True) ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u("{SHA}"), lazy=True) #============================================================================= # eof #=============================================================================
dc = hash_const(odd + hash_const(dc + even).digest()).digest() # if rounds was odd, do one last round (since we started at 0, # last round will be an even-numbered round) if tail & 1: dc = hash_const(dc + data[pairs][0]).digest() #=================================================================== # encode digest using appropriate transpose map #=================================================================== return h64.encode_transposed_bytes(dc, transpose_map).decode("ascii") #============================================================================= # handlers #============================================================================= _UROUNDS = u("rounds=") _UDOLLAR = u("$") _UZERO = u("0") class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """class containing common code shared by sha256_crypt & sha512_crypt""" #=================================================================== # class attrs #=================================================================== # name - set by subclass setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size") # ident - set by subclass checksum_chars = uh.HASH64_CHARS # checksum_size - set by subclass
# site # pkg from lib.passlib import hash from lib.passlib.utils import repeat_string from lib.passlib.utils.compat import irange, PY3, u, get_method_function from lib.passlib.tests.utils import TestCase, HandlerCase, skipUnless, \ TEST_MODE, b, catch_warnings, UserHandlerMixin, randintgauss, EncodingHandlerMixin from lib.passlib.tests.test_handlers import UPASS_WAV, UPASS_USD, UPASS_TABLE # module #============================================================================= # django #============================================================================= # standard string django uses UPASS_LETMEIN = u('l\xe8tmein') def vstr(version): return ".".join(str(e) for e in version) class _DjangoHelper(object): # NOTE: not testing against Django < 1.0 since it doesn't support # most of these hash formats. # flag that hash wasn't added until specified version min_django_version = () def fuzz_verifier_django(self): from lib.passlib.tests.test_ext_django import DJANGO_VERSION # check_password() not added until 1.0 min_django_version = max(self.min_django_version, (1,0))
return uascii_to_str(hash) def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") return str_to_uascii(hashlib.sha1(data).hexdigest()) #============================================================================= # test sample algorithms - really a self-test of HandlerCase #============================================================================= # TODO: provide data samples for algorithms # (positive knowns, negative knowns, invalid identify) UPASS_TEMP = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') class UnsaltedHashTest(HandlerCase): handler = UnsaltedHash known_correct_hashes = [ ("password", "61cfd32684c47de231f1f982c214e884133762c0"), (UPASS_TEMP, '96b329d120b97ff81ada770042e44ba87343ad2b'), ] def test_bad_kwds(self): if not PY_MAX_25: # annoyingly, py25's ``super().__init__()`` doesn't throw TypeError # when passing unknown keywords to object. just ignoring # this issue for now, since it's a minor border case. self.assertRaises(TypeError, UnsaltedHash, salt='x')
def test_50_norm_ident(self): """test GenericHandler + HasManyIdents""" # setup helpers class d1(uh.HasManyIdents, uh.GenericHandler): name = 'd1' setting_kwds = ('ident',) default_ident = u("!A") ident_values = [ u("!A"), u("!B") ] ident_aliases = { u("A"): u("!A")} def norm_ident(**k): return d1(**k).ident # check ident=None self.assertRaises(TypeError, norm_ident) self.assertRaises(TypeError, norm_ident, ident=None) self.assertEqual(norm_ident(use_defaults=True), u('!A')) # check valid idents self.assertEqual(norm_ident(ident=u('!A')), u('!A')) self.assertEqual(norm_ident(ident=u('!B')), u('!B')) self.assertRaises(ValueError, norm_ident, ident=u('!C')) # check aliases self.assertEqual(norm_ident(ident=u('A')), u('!A')) # check invalid idents self.assertRaises(ValueError, norm_ident, ident=u('B')) # check identify is honoring ident system self.assertTrue(d1.identify(u("!Axxx"))) self.assertTrue(d1.identify(u("!Bxxx"))) self.assertFalse(d1.identify(u("!Cxxx"))) self.assertFalse(d1.identify(u("A"))) self.assertFalse(d1.identify(u(""))) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) # check default_ident missing is detected. d1.default_ident = None self.assertRaises(AssertionError, norm_ident, use_defaults=True)
def to_string(self): hash = u("%s%s") % (self.salt, self.checksum or u('')) return uascii_to_str(hash)
def to_string(self): hash = u("%s%s%s%s") % (self.ident, h64.encode_int6(self.rounds).decode("ascii"), self.salt, self.checksum or u('')) return uascii_to_str(hash)
def _calc_checksum(self, secret): return u('b') if self.flag else u('a')