From 56b5e378022fbbfdd79a994324af23cb5527f5c9 Mon Sep 17 00:00:00 2001 From: Polina Date: Thu, 2 Jul 2026 18:51:54 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?venv/Lib/site-packages/passlib/tests=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../passlib/tests/test_handlers_bcrypt.py | 688 ++++++++++++++++++ .../passlib/tests/test_handlers_cisco.py | 457 ++++++++++++ .../passlib/tests/test_handlers_django.py | 413 +++++++++++ .../passlib/tests/test_handlers_pbkdf2.py | 480 ++++++++++++ .../passlib/tests/test_handlers_scrypt.py | 111 +++ 5 files changed, 2149 insertions(+) create mode 100644 venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py create mode 100644 venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py create mode 100644 venv/Lib/site-packages/passlib/tests/test_handlers_django.py create mode 100644 venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py create mode 100644 venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py diff --git a/venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py b/venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py new file mode 100644 index 0000000..64fc8bf --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py @@ -0,0 +1,688 @@ +"""passlib.tests.test_handlers - tests for passlib hash algorithms""" +#============================================================================= +# imports +#============================================================================= +from __future__ import with_statement +# core +import logging; log = logging.getLogger(__name__) +import os +import warnings +# site +# pkg +from passlib import hash +from passlib.handlers.bcrypt import IDENT_2, IDENT_2X +from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input +from passlib.utils.compat import irange, PY3 +from passlib.tests.utils import HandlerCase, TEST_MODE +from passlib.tests.test_handlers import UPASS_TABLE +# module + +#============================================================================= +# bcrypt +#============================================================================= +class _bcrypt_test(HandlerCase): + """base for BCrypt test cases""" + handler = hash.bcrypt + reduce_default_rounds = True + fuzz_salts_need_bcrypt_repair = True + + known_correct_hashes = [ + # + # from JTR 1.7.9 + # + ('U*U*U*U*', '$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O'), + ('U*U***U', '$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq'), + ('U*U***U*', '$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi'), + ('*U*U*U*U', '$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2'), + ('', '$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO'), + + # + # test vectors from http://www.openwall.com/crypt v1.2 + # note that this omits any hashes that depend on crypt_blowfish's + # various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password, + # and any 2x hashes); and only contain hashes which are correct + # under both crypt_blowfish 1.2 AND OpenBSD. + # + ('U*U', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW'), + ('U*U*', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK'), + ('U*U*U', '$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a'), + ('', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'), + ('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + '0123456789chars after 72 are ignored', + '$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui'), + (b'\xa3', + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), + (b'\xff\xa3345', + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e'), + (b'\xa3ab', + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS'), + (b'\xaa'*72 + b'chars after 72 are ignored as usual', + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6'), + (b'\xaa\x55'*36, + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy'), + (b'\x55\xaa\xff'*24, + '$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe'), + + # keeping one of their 2y tests, because we are supporting that. + (b'\xa3', + '$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), + + # + # 8bit bug (fixed in 2y/2b) + # + + # NOTE: see assert_lacks_8bit_bug() for origins of this test vector. + (b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"), + + # + # bsd wraparound bug (fixed in 2b) + # + + # NOTE: if backend is vulnerable, password will hash the same as '0'*72 + # ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"), + # rather than same as ("0123456789"*8)[:72] + # 255 should be sufficient, but checking + (("0123456789"*26)[:254], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), + (("0123456789"*26)[:255], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), + (("0123456789"*26)[:256], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), + (("0123456789"*26)[:257], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), + + + # + # from py-bcrypt tests + # + ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), + ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'), + ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'), + ('abcdefghijklmnopqrstuvwxyz', + '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'), + ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', + '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'), + + # + # custom test vectors + # + + # ensures utf-8 used for unicode + (UPASS_TABLE, + '$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), + + # ensure 2b support + (UPASS_TABLE, + '$2b$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), + + ] + + if TEST_MODE("full"): + # + # add some extra tests related to 2/2a + # + CONFIG_2 = '$2$05$' + '.'*22 + CONFIG_A = '$2a$05$' + '.'*22 + known_correct_hashes.extend([ + ("", CONFIG_2 + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), + ("", CONFIG_A + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), + ("abc", CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ("abc", CONFIG_A + 'ev6gDwpVye3oMCUpLY85aTpfBNHD0Ga'), + ("abc"*23, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ("abc"*23, CONFIG_A + '2kIdfSj/4/R/Q6n847VTvc68BXiRYZC'), + ("abc"*24, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ("abc"*24, CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ("abc"*24+'x', CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ("abc"*24+'x', CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), + ]) + + known_correct_configs = [ + ('$2a$04$uM6csdM8R9SXTex/gbTaye', UPASS_TABLE, + '$2a$04$uM6csdM8R9SXTex/gbTayezuvzFEufYGd2uB6of7qScLjQ4GwcD4G'), + ] + + known_unidentified_hashes = [ + # invalid minor version + "$2f$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", + "$2`$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", + ] + + known_malformed_hashes = [ + # bad char in otherwise correct hash + # \/ + "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", + + # unsupported (but recognized) minor version + "$2x$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", + + # rounds not zero-padded (py-bcrypt rejects this, therefore so do we) + '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.' + + # NOTE: salts with padding bits set are technically malformed, + # but we can reliably correct & issue a warning for that. + ] + + platform_crypt_support = [ + ("freedbsd|openbsd|netbsd", True), + ("darwin", False), + ("linux", None), # may be present via addon, e.g. debian's libpam-unix2 + ("solaris", None), # depends on system policy + ] + + #=================================================================== + # override some methods + #=================================================================== + def setUp(self): + # ensure builtin is enabled for duration of test. + if TEST_MODE("full") and self.backend == "builtin": + key = "PASSLIB_BUILTIN_BCRYPT" + orig = os.environ.get(key) + if orig: + self.addCleanup(os.environ.__setitem__, key, orig) + else: + self.addCleanup(os.environ.__delitem__, key) + os.environ[key] = "true" + + super(_bcrypt_test, self).setUp() + + # silence this warning, will come up a bunch during testing of old 2a hashes. + warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") + + def populate_settings(self, kwds): + # builtin is still just way too slow. + if self.backend == "builtin": + kwds.setdefault("rounds", 4) + super(_bcrypt_test, self).populate_settings(kwds) + + #=================================================================== + # fuzz testing + #=================================================================== + 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) + + fuzz_verifiers = HandlerCase.fuzz_verifiers + ( + "fuzz_verifier_bcrypt", + "fuzz_verifier_pybcrypt", + "fuzz_verifier_bcryptor", + ) + + def fuzz_verifier_bcrypt(self): + # test against bcrypt, if available + from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, _detect_pybcrypt + from passlib.utils import to_native_str, to_bytes + try: + import bcrypt + except ImportError: + return + if _detect_pybcrypt(): + return + def check_bcrypt(secret, hash): + """bcrypt""" + secret = to_bytes(secret, self.FuzzHashGenerator.password_encoding) + if hash.startswith(IDENT_2B): + # bcrypt <1.1 lacks 2B support + hash = IDENT_2A + hash[4:] + elif hash.startswith(IDENT_2): + # bcrypt doesn't support $2$ hashes; but we can fake it + # using the $2a$ algorithm, by repeating the password until + # it's 72 chars in length. + hash = IDENT_2A + hash[3:] + if secret: + secret = repeat_string(secret, 72) + elif hash.startswith(IDENT_2Y) and bcrypt.__version__ == "3.0.0": + hash = IDENT_2B + hash[4:] + hash = to_bytes(hash) + try: + return bcrypt.hashpw(secret, hash) == hash + except ValueError: + raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret)) + return check_bcrypt + + def fuzz_verifier_pybcrypt(self): + # test against py-bcrypt, if available + from passlib.handlers.bcrypt import ( + IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, + _PyBcryptBackend, + ) + from passlib.utils import to_native_str + + loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False) + if not loaded: + return + + from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod + + lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2 + + def check_pybcrypt(secret, hash): + """pybcrypt""" + secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) + if len(secret) > 200: # vulnerable to wraparound bug + secret = secret[:200] + if hash.startswith((IDENT_2B, IDENT_2Y)): + hash = IDENT_2A + hash[4:] + try: + if lock: + with lock: + return bcrypt_mod.hashpw(secret, hash) == hash + else: + return bcrypt_mod.hashpw(secret, hash) == hash + except ValueError: + raise ValueError("py-bcrypt rejected hash: %r" % (hash,)) + return check_pybcrypt + + def fuzz_verifier_bcryptor(self): + # test against bcryptor if available + from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y, IDENT_2B + from passlib.utils import to_native_str + try: + from bcryptor.engine import Engine + except ImportError: + return + def check_bcryptor(secret, hash): + """bcryptor""" + secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) + if hash.startswith((IDENT_2B, IDENT_2Y)): + hash = IDENT_2A + hash[4:] + elif hash.startswith(IDENT_2): + # bcryptor doesn't support $2$ hashes; but we can fake it + # using the $2a$ algorithm, by repeating the password until + # it's 72 chars in length. + hash = IDENT_2A + hash[3:] + if secret: + secret = repeat_string(secret, 72) + return Engine(False).hash_key(secret, hash) == hash + return check_bcryptor + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def generate(self): + opts = super(_bcrypt_test.FuzzHashGenerator, self).generate() + + secret = opts['secret'] + other = opts['other'] + settings = opts['settings'] + ident = settings.get('ident') + + if ident == IDENT_2X: + # 2x is just recognized, not supported. don't test with it. + del settings['ident'] + + elif ident == IDENT_2 and other and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret): + # avoid false failure due to flaw in 0-revision bcrypt: + # repeated strings like 'abc' and 'abcabc' hash identically. + opts['secret'], opts['other'] = self.random_password_pair() + + return opts + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(5, 8, 6, 1) + + #=================================================================== + # custom tests + #=================================================================== + known_incorrect_padding = [ + # password, bad hash, good hash + + # 2 bits of salt padding set +# ("loppux", # \/ +# "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C", +# "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C"), + ("test", # \/ + '$2a$04$oaQbBqq8JnSM1NHRPQGXORY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO', + '$2a$04$oaQbBqq8JnSM1NHRPQGXOOY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO'), + + # all 4 bits of salt padding set +# ("Passlib11", # \/ +# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK", +# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK"), + ("test", # \/ + "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS", + "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), + + # bad checksum padding + ("test", # \/ + "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV", + "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), + ] + + def test_90_bcrypt_padding(self): + """test passlib correctly handles bcrypt padding bits""" + self.require_TEST_MODE("full") + # + # prevents reccurrence of issue 25 (https://code.google.com/p/passlib/issues/detail?id=25) + # were some unused bits were incorrectly set in bcrypt salt strings. + # (fixed since 1.5.3) + # + bcrypt = self.handler + corr_desc = ".*incorrectly set padding bits" + + # + # test hash() / genconfig() don't generate invalid salts anymore + # + def check_padding(hash): + assert hash.startswith(("$2a$", "$2b$")) and len(hash) >= 28, \ + "unexpectedly malformed hash: %r" % (hash,) + self.assertTrue(hash[28] in '.Oeu', + "unused bits incorrectly set in hash: %r" % (hash,)) + for i in irange(6): + check_padding(bcrypt.genconfig()) + for i in irange(3): + check_padding(bcrypt.using(rounds=bcrypt.min_rounds).hash("bob")) + + # + # test genconfig() corrects invalid salts & issues warning. + # + with self.assertWarningList(["salt too large", corr_desc]): + hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True) + self.assertEqual(hash, "$2b$05$" + "." * (22 + 31)) + + # + # test public methods against good & bad hashes + # + samples = self.known_incorrect_padding + for pwd, bad, good in samples: + + # make sure genhash() corrects bad configs, leaves good unchanged + with self.assertWarningList([corr_desc]): + self.assertEqual(bcrypt.genhash(pwd, bad), good) + with self.assertWarningList([]): + self.assertEqual(bcrypt.genhash(pwd, good), good) + + # make sure verify() works correctly with good & bad hashes + with self.assertWarningList([corr_desc]): + self.assertTrue(bcrypt.verify(pwd, bad)) + with self.assertWarningList([]): + self.assertTrue(bcrypt.verify(pwd, good)) + + # make sure normhash() corrects bad hashes, leaves good unchanged + with self.assertWarningList([corr_desc]): + self.assertEqual(bcrypt.normhash(bad), good) + with self.assertWarningList([]): + self.assertEqual(bcrypt.normhash(good), good) + + # make sure normhash() leaves non-bcrypt hashes alone + self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc") + + def test_needs_update_w_padding(self): + """needs_update corrects bcrypt padding""" + # NOTE: see padding test above for details about issue this detects + bcrypt = self.handler.using(rounds=4) + + # PASS1 = "test" + # bad contains invalid 'c' char at end of salt: + # \/ + BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" + GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" + + self.assertTrue(bcrypt.needs_update(BAD1)) + self.assertFalse(bcrypt.needs_update(GOOD1)) + + #=================================================================== + # eoc + #=================================================================== + +# create test cases for specific backends +bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt") +bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt") +bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor") + +class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")): + + # os crypt doesn't support non-utf8 secret bytes + known_correct_hashes = [row for row in _bcrypt_test.known_correct_hashes + if is_safe_crypt_input(row[0])] + + # os crypt backend doesn't currently implement a per-call fallback if it fails + has_os_crypt_fallback = False + +bcrypt_builtin_test = _bcrypt_test.create_backend_case("builtin") + +#============================================================================= +# bcrypt +#============================================================================= +class _bcrypt_sha256_test(HandlerCase): + "base for BCrypt-SHA256 test cases" + handler = hash.bcrypt_sha256 + reduce_default_rounds = True + forbidden_characters = None + fuzz_salts_need_bcrypt_repair = True + + known_correct_hashes = [ + #------------------------------------------------------------------- + # custom test vectors for old v1 format + #------------------------------------------------------------------- + + # empty + ("", + '$bcrypt-sha256$2a,5$E/e/2AOhqM5W/KJTFQzLce$F6dYSxOdAEoJZO2eoHUZWZljW/e0TXO'), + + # ascii + ("password", + '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), + + # unicode / utf8 + (UPASS_TABLE, + '$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), + (UPASS_TABLE.encode("utf-8"), + '$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), + + # ensure 2b support + ("password", + '$bcrypt-sha256$2b,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), + (UPASS_TABLE, + '$bcrypt-sha256$2b,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), + + # test >72 chars is hashed correctly -- under bcrypt these hash the same. + # NOTE: test_60_truncate_size() handles this already, this is just for overkill :) + (repeat_string("abc123", 72), + '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$r/hyEtqJ0teqPEmfTLoZ83ciAI1Q74.'), + (repeat_string("abc123", 72) + "qwr", + '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$021KLEif6epjot5yoxk0m8I0929ohEa'), + (repeat_string("abc123", 72) + "xyz", + '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$7.1kgpHduMGEjvM3fX6e/QCvfn6OKja'), + + #------------------------------------------------------------------- + # custom test vectors for v2 format + # TODO: convert to v2 format + #------------------------------------------------------------------- + + # empty + ("", + '$bcrypt-sha256$v=2,t=2b,r=5$E/e/2AOhqM5W/KJTFQzLce$WFPIZKtDDTriqWwlmRFfHiOTeheAZWe'), + + # ascii + ("password", + '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'), + + # unicode / utf8 + (UPASS_TABLE, + '$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'), + (UPASS_TABLE.encode("utf-8"), + '$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'), + + # test >72 chars is hashed correctly -- under bcrypt these hash the same. + # NOTE: test_60_truncate_size() handles this already, this is just for overkill :) + (repeat_string("abc123", 72), + '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zu1cloESVFIOsUIo7fCEgkdHaI9SSue'), + (repeat_string("abc123", 72) + "qwr", + '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$CBF9csfEdW68xv3DwE6xSULXMtqEFP.'), + (repeat_string("abc123", 72) + "xyz", + '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zC/1UDUG2ofEXB6Onr2vvyFzfhEOS3S'), + ] + + known_correct_configs =[ + # v1 + ('$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe', + "password", '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), + # v2 + ('$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe', + "password", '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'), + ] + + known_malformed_hashes = [ + #------------------------------------------------------------------- + # v1 format + #------------------------------------------------------------------- + + # bad char in otherwise correct hash + # \/ + '$bcrypt-sha256$2a,5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unrecognized bcrypt variant + '$bcrypt-sha256$2c,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unsupported bcrypt variant + '$bcrypt-sha256$2x,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # rounds zero-padded + '$bcrypt-sha256$2a,05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # config string w/ $ added + '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$', + + #------------------------------------------------------------------- + # v2 format + #------------------------------------------------------------------- + + # bad char in otherwise correct hash + # \/ + '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unsupported version (for this format) + '$bcrypt-sha256$v=1,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unrecognized version + '$bcrypt-sha256$v=3,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unrecognized bcrypt variant + '$bcrypt-sha256$v=2,t=2c,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # unsupported bcrypt variant + '$bcrypt-sha256$v=2,t=2a,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + '$bcrypt-sha256$v=2,t=2x,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # rounds zero-padded + '$bcrypt-sha256$v=2,t=2b,r=05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', + + # config string w/ $ added + '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$', + ] + + #=================================================================== + # override some methods -- cloned from bcrypt + #=================================================================== + def setUp(self): + # ensure builtin is enabled for duration of test. + if TEST_MODE("full") and self.backend == "builtin": + key = "PASSLIB_BUILTIN_BCRYPT" + orig = os.environ.get(key) + if orig: + self.addCleanup(os.environ.__setitem__, key, orig) + else: + self.addCleanup(os.environ.__delitem__, key) + os.environ[key] = "enabled" + super(_bcrypt_sha256_test, self).setUp() + warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") + + def populate_settings(self, kwds): + # builtin is still just way too slow. + if self.backend == "builtin": + kwds.setdefault("rounds", 4) + super(_bcrypt_sha256_test, self).populate_settings(kwds) + + #=================================================================== + # override ident tests for now + #=================================================================== + + def require_many_idents(self): + raise self.skipTest("multiple idents not supported") + + def test_30_HasOneIdent(self): + # forbidding ident keyword, we only support "2b" for now + handler = self.handler + handler(use_defaults=True) + self.assertRaises(ValueError, handler, ident="$2y$", use_defaults=True) + + #=================================================================== + # fuzz testing -- cloned from bcrypt + #=================================================================== + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(5, 8, 6, 1) + + def random_ident(self): + return "2b" + + #=================================================================== + # custom tests + #=================================================================== + + def test_using_version(self): + # default to v2 + handler = self.handler + self.assertEqual(handler.version, 2) + + # allow v1 explicitly + subcls = handler.using(version=1) + self.assertEqual(subcls.version, 1) + + # forbid unknown ver + self.assertRaises(ValueError, handler.using, version=999) + + # allow '2a' only for v1 + subcls = handler.using(version=1, ident="2a") + self.assertRaises(ValueError, handler.using, ident="2a") + + def test_calc_digest_v2(self): + """ + test digest calc v2 matches bcrypt() + """ + from passlib.hash import bcrypt + from passlib.crypto.digest import compile_hmac + from passlib.utils.binary import b64encode + + # manually calc intermediary digest + salt = "nyKYxTAvjmy6lMDYMl11Uu" + secret = "test" + temp_digest = compile_hmac("sha256", salt.encode("ascii"))(secret.encode("ascii")) + temp_digest = b64encode(temp_digest).decode("ascii") + self.assertEqual(temp_digest, "J5TlyIDm+IcSWmKiDJm+MeICndBkFVPn4kKdJW8f+xY=") + + # manually final hash from intermediary + # XXX: genhash() could be useful here + bcrypt_digest = bcrypt(ident="2b", salt=salt, rounds=12)._calc_checksum(temp_digest) + self.assertEqual(bcrypt_digest, "M0wE0Ov/9LXoQFCe.jRHu3MSHPF54Ta") + self.assertTrue(bcrypt.verify(temp_digest, "$2b$12$" + salt + bcrypt_digest)) + + # confirm handler outputs same thing. + # XXX: genhash() could be useful here + result = self.handler(ident="2b", salt=salt, rounds=12)._calc_checksum(secret) + self.assertEqual(result, bcrypt_digest) + + #=================================================================== + # eoc + #=================================================================== + +# create test cases for specific backends +bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt") +bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt") +bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor") + +class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")): + + @classmethod + def _get_safe_crypt_handler_backend(cls): + return bcrypt_os_crypt_test._get_safe_crypt_handler_backend() + + has_os_crypt_fallback = False + +bcrypt_sha256_builtin_test = _bcrypt_sha256_test.create_backend_case("builtin") + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py b/venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py new file mode 100644 index 0000000..ea6594b --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py @@ -0,0 +1,457 @@ +""" +passlib.tests.test_handlers_cisco - tests for Cisco-specific algorithms +""" +#============================================================================= +# imports +#============================================================================= +from __future__ import absolute_import, division, print_function +# core +import logging +log = logging.getLogger(__name__) +# site +# pkg +from passlib import hash, exc +from passlib.utils.compat import u +from .utils import UserHandlerMixin, HandlerCase, repeat_string +from .test_handlers import UPASS_TABLE +# module +__all__ = [ + "cisco_pix_test", + "cisco_asa_test", + "cisco_type7_test", +] +#============================================================================= +# shared code for cisco PIX & ASA +#============================================================================= + +class _PixAsaSharedTest(UserHandlerMixin, HandlerCase): + """ + class w/ shared info for PIX & ASA tests. + """ + __unittest_skip = True # for TestCase + requires_user = False # for UserHandlerMixin + + #: shared list of hashes which should be identical under pix & asa7 + #: (i.e. combined secret + user < 17 bytes) + pix_asa_shared_hashes = [ + # + # http://www.perlmonks.org/index.pl?node_id=797623 + # + (("cisco", ""), "2KFQnbNIdI.2KYOU"), # confirmed ASA 9.6 + + # + # http://www.hsc.fr/ressources/breves/pix_crack.html.en + # + (("hsc", ""), "YtT8/k6Np8F1yz2c"), # confirmed ASA 9.6 + + # + # www.freerainbowtables.com/phpBB3/viewtopic.php?f=2&t=1441 + # + (("", ""), "8Ry2YjIyt7RRXU24"), # confirmed ASA 9.6 + (("cisco", "john"), "hN7LzeyYjw12FSIU"), + (("cisco", "jack"), "7DrfeZ7cyOj/PslD"), + + # + # http://comments.gmane.org/gmane.comp.security.openwall.john.user/2529 + # + (("ripper", "alex"), "h3mJrcH0901pqX/m"), + (("cisco", "cisco"), "3USUcOPFUiMCO4Jk"), + (("cisco", "cisco1"), "3USUcOPFUiMCO4Jk"), + (("CscFw-ITC!", "admcom"), "lZt7HSIXw3.QP7.R"), + ("cangetin", "TynyB./ftknE77QP"), + (("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"), + + # + # http://openwall.info/wiki/john/sample-hashes + # + (("phonehome", "rharris"), "zyIIMSYjiPm0L7a6"), + + # + # http://www.openwall.com/lists/john-users/2010/08/08/3 + # + (("cangetin", ""), "TynyB./ftknE77QP"), + (("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"), + + # + # from JTR 1.7.9 + # + ("test1", "TRPEas6f/aa6JSPL"), + ("test2", "OMT6mXmAvGyzrCtp"), + ("test3", "gTC7RIy1XJzagmLm"), + ("test4", "oWC1WRwqlBlbpf/O"), + ("password", "NuLKvvWGg.x9HEKO"), + ("0123456789abcdef", ".7nfVBEIEu4KbF/1"), + + # + # http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html#wp5472 + # + (("1234567890123456", ""), "feCkwUGktTCAgIbD"), # canonical source + (("watag00s1am", ""), "jMorNbK0514fadBh"), # canonical source + + # + # custom + # + (("cisco1", "cisco1"), "jmINXNH6p1BxUppp"), + + # ensures utf-8 used for unicode + (UPASS_TABLE, 'CaiIvkLMu2TOHXGT'), + + # + # passlib reference vectors + # + # Some of these have been confirmed on various ASA firewalls, + # and the exact version is noted next to each hash. + # Would like to verify these under more PIX & ASA versions. + # + # Those without a note are generally an extrapolation, + # to ensure the code stays consistent, but for various reasons, + # hasn't been verified. + # + # * One such case is usernames w/ 1 & 2 digits -- + # ASA (9.6 at least) requires 3+ digits in username. + # + # The following hashes (below 13 chars) should be identical for PIX/ASA. + # Ones which differ are listed separately in the known_correct_hashes + # list for the two test classes. + # + + # 4 char password + (('1234', ''), 'RLPMUQ26KL4blgFN'), # confirmed ASA 9.6 + + # 8 char password + (('01234567', ''), '0T52THgnYdV1tlOF'), # confirmed ASA 9.6 + (('01234567', '3'), '.z0dT9Alkdc7EIGS'), + (('01234567', '36'), 'CC3Lam53t/mHhoE7'), + (('01234567', '365'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6 + (('01234567', '3333'), '.z0dT9Alkdc7EIGS'), # confirmed ASA 9.6 + (('01234567', '3636'), 'CC3Lam53t/mHhoE7'), # confirmed ASA 9.6 + (('01234567', '3653'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6 + (('01234567', 'adm'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6 + (('01234567', 'adma'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6 + (('01234567', 'admad'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6 + (('01234567', 'user'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6 + (('01234567', 'user1234'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6 + + # 12 char password + (('0123456789ab', ''), 'S31BxZOGlAigndcJ'), # confirmed ASA 9.6 + (('0123456789ab', '36'), 'wFqSX91X5.YaRKsi'), + (('0123456789ab', '365'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6 + (('0123456789ab', '3333'), 'mcXPL/vIZcIxLUQs'), # confirmed ASA 9.6 + (('0123456789ab', '3636'), 'wFqSX91X5.YaRKsi'), # confirmed ASA 9.6 + (('0123456789ab', '3653'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6 + (('0123456789ab', 'user'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6 + (('0123456789ab', 'user1234'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6 + + # NOTE: remaining reference vectors for 13+ char passwords + # are split up between cisco_pix & cisco_asa tests. + + # unicode passwords + # ASA supposedly uses utf-8 encoding, but entering non-ascii + # chars is error-prone, and while UTF-8 appears to be intended, + # observed behaviors include: + # * ssh cli stripping non-ascii chars entirely + # * ASDM web iface double-encoding utf-8 strings + ((u("t\xe1ble").encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'), + ((u("t\xe1ble").encode("utf-8").decode("latin-1").encode("utf-8"), + 'user'), 'cMvFC2XVBmK/68yB'), # confirmed ASA 9.6 when typed into ASDM + ] + + def test_calc_digest_spoiler(self): + """ + _calc_checksum() -- spoil oversize passwords during verify + + for details, see 'spoil_digest' flag instead that function. + this helps cisco_pix/cisco_asa implement their policy of + ``.truncate_verify_reject=True``. + """ + def calc(secret, for_hash=False): + return self.handler(use_defaults=for_hash)._calc_checksum(secret) + + # short (non-truncated) password + short_secret = repeat_string("1234", self.handler.truncate_size) + short_hash = calc(short_secret) + + # longer password should have totally different hash, + # to prevent verify from matching (i.e. "spoiled"). + long_secret = short_secret + "X" + long_hash = calc(long_secret) + self.assertNotEqual(long_hash, short_hash) + + # spoiled hash should depend on whole secret, + # so that output isn't predictable + alt_long_secret = short_secret + "Y" + alt_long_hash = calc(alt_long_secret) + self.assertNotEqual(alt_long_hash, short_hash) + self.assertNotEqual(alt_long_hash, long_hash) + + # for hash(), should throw error if password too large + calc(short_secret, for_hash=True) + self.assertRaises(exc.PasswordSizeError, calc, long_secret, for_hash=True) + self.assertRaises(exc.PasswordSizeError, calc, alt_long_secret, for_hash=True) + +#============================================================================= +# cisco pix +#============================================================================= +class cisco_pix_test(_PixAsaSharedTest): + handler = hash.cisco_pix + + #: known correct pix hashes + known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [ + # + # passlib reference vectors (PIX-specific) + # + # NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors, + # and general notes about the 'passlib reference vectors' test set. + # + # All of the following are PIX-specific, as ASA starts + # to use a different padding size at 13 characters. + # + # TODO: these need confirming w/ an actual PIX system. + # + + # 13 char password + (('0123456789abc', ''), 'eacOpB7vE7ZDukSF'), + (('0123456789abc', '3'), 'ylJTd/qei66WZe3w'), + (('0123456789abc', '36'), 'hDx8QRlUhwd6bU8N'), + (('0123456789abc', '365'), 'vYOOtnkh1HXcMrM7'), + (('0123456789abc', '3333'), 'ylJTd/qei66WZe3w'), + (('0123456789abc', '3636'), 'hDx8QRlUhwd6bU8N'), + (('0123456789abc', '3653'), 'vYOOtnkh1HXcMrM7'), + (('0123456789abc', 'user'), 'f4/.SALxqDo59mfV'), + (('0123456789abc', 'user1234'), 'f4/.SALxqDo59mfV'), + + # 14 char password + (('0123456789abcd', ''), '6r8888iMxEoPdLp4'), + (('0123456789abcd', '3'), 'f5lvmqWYj9gJqkIH'), + (('0123456789abcd', '36'), 'OJJ1Khg5HeAYBH1c'), + (('0123456789abcd', '365'), 'OJJ1Khg5HeAYBH1c'), + (('0123456789abcd', '3333'), 'f5lvmqWYj9gJqkIH'), + (('0123456789abcd', '3636'), 'OJJ1Khg5HeAYBH1c'), + (('0123456789abcd', '3653'), 'OJJ1Khg5HeAYBH1c'), + (('0123456789abcd', 'adm'), 'DbPLCFIkHc2SiyDk'), + (('0123456789abcd', 'adma'), 'DbPLCFIkHc2SiyDk'), + (('0123456789abcd', 'user'), 'WfO2UiTapPkF/FSn'), + (('0123456789abcd', 'user1234'), 'WfO2UiTapPkF/FSn'), + + # 15 char password + (('0123456789abcde', ''), 'al1e0XFIugTYLai3'), + (('0123456789abcde', '3'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', '36'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', '365'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', '3333'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', '3636'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', '3653'), 'lYbwBu.f82OIApQB'), + (('0123456789abcde', 'adm'), 'KgKx1UQvdR/09i9u'), + (('0123456789abcde', 'adma'), 'KgKx1UQvdR/09i9u'), + (('0123456789abcde', 'user'), 'qLopkenJ4WBqxaZN'), + (('0123456789abcde', 'user1234'), 'qLopkenJ4WBqxaZN'), + + # 16 char password + (('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', '36'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', '365'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', '3333'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', '3636'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', '3653'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', 'user'), '.7nfVBEIEu4KbF/1'), + (('0123456789abcdef', 'user1234'), '.7nfVBEIEu4KbF/1'), + ] + + +#============================================================================= +# cisco asa +#============================================================================= +class cisco_asa_test(_PixAsaSharedTest): + handler = hash.cisco_asa + + known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [ + # + # passlib reference vectors (ASA-specific) + # + # NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors, + # and general notes about the 'passlib reference vectors' test set. + # + + # 13 char password + # NOTE: past this point, ASA pads to 32 bytes instead of 16 + # for all cases where user is set (secret + 4 bytes > 16), + # but still uses 16 bytes for enable pwds (secret <= 16). + # hashes w/ user WON'T match PIX, but "enable" passwords will. + (('0123456789abc', ''), 'eacOpB7vE7ZDukSF'), # confirmed ASA 9.6 + (('0123456789abc', '36'), 'FRV9JG18UBEgX0.O'), + (('0123456789abc', '365'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6 + (('0123456789abc', '3333'), 'NmrkP98nT7RAeKZz'), # confirmed ASA 9.6 + (('0123456789abc', '3636'), 'FRV9JG18UBEgX0.O'), # confirmed ASA 9.6 + (('0123456789abc', '3653'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6 + (('0123456789abc', 'user'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6 + (('0123456789abc', 'user1234'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6 + + # 14 char password + (('0123456789abcd', ''), '6r8888iMxEoPdLp4'), # confirmed ASA 9.6 + (('0123456789abcd', '3'), 'yxGoujXKPduTVaYB'), + (('0123456789abcd', '36'), 'W0jckhnhjnr/DiT/'), + (('0123456789abcd', '365'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6 + (('0123456789abcd', '3333'), 'yxGoujXKPduTVaYB'), # confirmed ASA 9.6 + (('0123456789abcd', '3636'), 'W0jckhnhjnr/DiT/'), # confirmed ASA 9.6 + (('0123456789abcd', '3653'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6 + (('0123456789abcd', 'adm'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6 + (('0123456789abcd', 'adma'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6 + (('0123456789abcd', 'user'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6 + (('0123456789abcd', 'user1234'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6 + + # 15 char password + (('0123456789abcde', ''), 'al1e0XFIugTYLai3'), # confirmed ASA 9.6 + (('0123456789abcde', '3'), 'nAZrQoHaL.fgrIqt'), + (('0123456789abcde', '36'), '2GxIQ6ICE795587X'), + (('0123456789abcde', '365'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6 + (('0123456789abcde', '3333'), 'nAZrQoHaL.fgrIqt'), # confirmed ASA 9.6 + (('0123456789abcde', '3636'), '2GxIQ6ICE795587X'), # confirmed ASA 9.6 + (('0123456789abcde', '3653'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6 + (('0123456789abcde', 'adm'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6 + (('0123456789abcde', 'adma'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6 + (('0123456789abcde', 'user'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6 + (('0123456789abcde', 'user1234'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6 + + # 16 char password + (('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'), # confirmed ASA 9.6 + (('0123456789abcdef', '36'), 'GhI8.yFSC5lwoafg'), + (('0123456789abcdef', '365'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6 + (('0123456789abcdef', '3333'), 'Ghdi1IlsswgYzzMH'), # confirmed ASA 9.6 + (('0123456789abcdef', '3636'), 'GhI8.yFSC5lwoafg'), # confirmed ASA 9.6 + (('0123456789abcdef', '3653'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6 + (('0123456789abcdef', 'user'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6 + (('0123456789abcdef', 'user1234'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6 + + # 17 char password + # NOTE: past this point, ASA pads to 32 bytes instead of 16 + # for ALL cases, since secret > 16 bytes even for enable pwds; + # and so none of these rest here should match PIX. + (('0123456789abcdefq', ''), 'bKshl.EN.X3CVFRQ'), # confirmed ASA 9.6 + (('0123456789abcdefq', '36'), 'JAeTXHs0n30svlaG'), + (('0123456789abcdefq', '365'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6 + (('0123456789abcdefq', '3333'), 'USEJbxI6.VY4ecBP'), # confirmed ASA 9.6 + (('0123456789abcdefq', '3636'), 'JAeTXHs0n30svlaG'), # confirmed ASA 9.6 + (('0123456789abcdefq', '3653'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6 + (('0123456789abcdefq', 'user'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6 + (('0123456789abcdefq', 'user1234'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6 + + # 27 char password + (('0123456789abcdefqwertyuiopa', ''), '4wp19zS3OCe.2jt5'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', '36'), 'PjUoGqWBKPyV9qOe'), + (('0123456789abcdefqwertyuiopa', '365'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', '3333'), 'rd/ZMuGTJFIb2BNG'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', '3636'), 'PjUoGqWBKPyV9qOe'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', '3653'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', 'user'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopa', 'user1234'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6 + + # 28 char password + # NOTE: past this point, ASA stops appending the username AT ALL, + # even though there's still room for the first few chars. + (('0123456789abcdefqwertyuiopas', ''), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopas', '36'), 'W6nbOddI0SutTK7m'), + (('0123456789abcdefqwertyuiopas', '365'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopas', 'user'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopas', 'user1234'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6 + + # 32 char password + # NOTE: this is max size that ASA allows, and throws error for larger + (('0123456789abcdefqwertyuiopasdfgh', ''), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopasdfgh', '36'), '5hPT/iC6DnoBxo6a'), + (('0123456789abcdefqwertyuiopasdfgh', '365'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopasdfgh', 'user'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6 + (('0123456789abcdefqwertyuiopasdfgh', 'user1234'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6 + ] + + +#============================================================================= +# cisco type 7 +#============================================================================= +class cisco_type7_test(HandlerCase): + handler = hash.cisco_type7 + salt_bits = 4 + salt_type = int + + known_correct_hashes = [ + # + # http://mccltd.net/blog/?p=1034 + # + ("secure ", "04480E051A33490E"), + + # + # http://insecure.org/sploits/cisco.passwords.html + # + ("Its time to go to lunch!", + "153B1F1F443E22292D73212D5300194315591954465A0D0B59"), + + # + # http://blog.ioshints.info/2007/11/type-7-decryption-in-cisco-ios.html + # + ("t35t:pa55w0rd", "08351F1B1D431516475E1B54382F"), + + # + # http://www.m00nie.com/2011/09/cisco-type-7-password-decryption-and-encryption-with-perl/ + # + ("hiImTesting:)", "020E0D7206320A325847071E5F5E"), + + # + # http://packetlife.net/forums/thread/54/ + # + ("cisco123", "060506324F41584B56"), + ("cisco123", "1511021F07257A767B"), + + # + # source ? + # + ('Supe&8ZUbeRp4SS', "06351A3149085123301517391C501918"), + + # + # custom + # + + # ensures utf-8 used for unicode + (UPASS_TABLE, '0958EDC8A9F495F6F8A5FD'), + ] + + known_unidentified_hashes = [ + # salt with hex value + "0A480E051A33490E", + + # salt value > 52. this may in fact be valid, but we reject it for now + # (see docs for more). + '99400E4812', + ] + + def test_90_decode(self): + """test cisco_type7.decode()""" + from passlib.utils import to_unicode, to_bytes + + handler = self.handler + for secret, hash in self.known_correct_hashes: + usecret = to_unicode(secret) + bsecret = to_bytes(secret) + self.assertEqual(handler.decode(hash), usecret) + self.assertEqual(handler.decode(hash, None), bsecret) + + self.assertRaises(UnicodeDecodeError, handler.decode, + '0958EDC8A9F495F6F8A5FD', 'ascii') + + def test_91_salt(self): + """test salt value border cases""" + handler = self.handler + self.assertRaises(TypeError, handler, salt=None) + handler(salt=None, use_defaults=True) + self.assertRaises(TypeError, handler, salt='abc') + self.assertRaises(ValueError, handler, salt=-10) + self.assertRaises(ValueError, handler, salt=100) + + self.assertRaises(TypeError, handler.using, salt='abc') + self.assertRaises(ValueError, handler.using, salt=-10) + self.assertRaises(ValueError, handler.using, salt=100) + with self.assertWarningList("salt/offset must be.*"): + subcls = handler.using(salt=100, relaxed=True) + self.assertEqual(subcls(use_defaults=True).salt, 52) + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_handlers_django.py b/venv/Lib/site-packages/passlib/tests/test_handlers_django.py new file mode 100644 index 0000000..f7c9a0d --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_handlers_django.py @@ -0,0 +1,413 @@ +"""passlib.tests.test_handlers_django - tests for passlib hash algorithms""" +#============================================================================= +# imports +#============================================================================= +from __future__ import with_statement +# core +import logging; log = logging.getLogger(__name__) +import re +import warnings +# site +# pkg +from passlib import hash +from passlib.utils import repeat_string +from passlib.utils.compat import u +from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest +from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE +from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \ + check_django_hasher_has_backend +# module + +#============================================================================= +# django +#============================================================================= + +# standard string django uses +UPASS_LETMEIN = u('l\xe8tmein') + +def vstr(version): + return ".".join(str(e) for e in version) + +class _DjangoHelper(TestCase): + """ + mixin for HandlerCase subclasses that are testing a hasher + which is also present in django. + """ + __unittest_skip = True + + #: minimum django version where hash alg is present / that we support testing against + min_django_version = MIN_DJANGO_VERSION + + #: max django version where hash alg is present + #: TODO: for a bunch of the tests below, this is just max version where + #: settings.PASSWORD_HASHERS includes it by default -- could add helper to patch + #: desired django hasher back in for duration of test. + #: XXX: change this to "disabled_in_django_version" instead? + max_django_version = None + + def _require_django_support(self): + # make sure min django version + if DJANGO_VERSION < self.min_django_version: + raise self.skipTest("Django >= %s not installed" % vstr(self.min_django_version)) + if self.max_django_version and DJANGO_VERSION > self.max_django_version: + raise self.skipTest("Django <= %s not installed" % vstr(self.max_django_version)) + + # make sure django has a backend for specified hasher + name = self.handler.django_name + if not check_django_hasher_has_backend(name): + raise self.skipTest('django hasher %r not available' % name) + + return True + + extra_fuzz_verifiers = HandlerCase.fuzz_verifiers + ( + "fuzz_verifier_django", + ) + + def fuzz_verifier_django(self): + try: + self._require_django_support() + except SkipTest: + return None + from django.contrib.auth.hashers import check_password + + def verify_django(secret, hash): + """django/check_password""" + if self.handler.name == "django_bcrypt" and hash.startswith("bcrypt$$2y$"): + hash = hash.replace("$$2y$", "$$2a$") + if isinstance(secret, bytes): + secret = secret.decode("utf-8") + return check_password(secret, hash) + return verify_django + + def test_90_django_reference(self): + """run known correct hashes through Django's check_password()""" + self._require_django_support() + # XXX: esp. when it's no longer supported by django, + # should verify it's *NOT* recognized + from django.contrib.auth.hashers import check_password + assert self.known_correct_hashes + for secret, hash in self.iter_known_hashes(): + self.assertTrue(check_password(secret, hash), + "secret=%r hash=%r failed to verify" % + (secret, hash)) + self.assertFalse(check_password('x' + secret, hash), + "mangled secret=%r hash=%r incorrect verified" % + (secret, hash)) + + def test_91_django_generation(self): + """test against output of Django's make_password()""" + self._require_django_support() + # XXX: esp. when it's no longer supported by django, + # should verify it's *NOT* recognized + from passlib.utils import tick + from django.contrib.auth.hashers import make_password + name = self.handler.django_name # set for all the django_* handlers + end = tick() + self.max_fuzz_time/2 + generator = self.FuzzHashGenerator(self, self.getRandom()) + while tick() < end: + secret, other = generator.random_password_pair() + if not secret: # django rejects empty passwords. + continue + if isinstance(secret, bytes): + secret = secret.decode("utf-8") + hash = make_password(secret, hasher=name) + self.assertTrue(self.do_identify(hash)) + self.assertTrue(self.do_verify(secret, hash)) + self.assertFalse(self.do_verify(other, hash)) + +class django_disabled_test(HandlerCase): + """test django_disabled""" + handler = hash.django_disabled + disabled_contains_salt = True + + known_correct_hashes = [ + # *everything* should hash to "!", and nothing should verify + ("password", "!"), + ("", "!"), + (UPASS_TABLE, "!"), + ] + + known_alternate_hashes = [ + # django 1.6 appends random alpnum string + ("!9wa845vn7098ythaehasldkfj", "password", "!"), + ] + +class django_des_crypt_test(HandlerCase, _DjangoHelper): + """test django_des_crypt""" + handler = hash.django_des_crypt + max_django_version = (1,9) + + known_correct_hashes = [ + # ensures only first two digits of salt count. + ("password", 'crypt$c2$c2M87q...WWcU'), + ("password", 'crypt$c2e86$c2M87q...WWcU'), + ("passwordignoreme", 'crypt$c2.AZ$c2M87q...WWcU'), + + # ensures utf-8 used for unicode + (UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'), + (UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'), + (u("hell\u00D6"), "crypt$sa$saykDgk3BPZ9E"), + + # prevent regression of issue 22 + ("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'), + ] + + known_alternate_hashes = [ + # ensure django 1.4 empty salt field is accepted; + # but that salt field is re-filled (for django 1.0 compatibility) + ('crypt$$c2M87q...WWcU', "password", 'crypt$c2$c2M87q...WWcU'), + ] + + known_unidentified_hashes = [ + 'sha1$aa$bb', + ] + + known_malformed_hashes = [ + # checksum too short + 'crypt$c2$c2M87q', + + # salt must be >2 + 'crypt$f$c2M87q...WWcU', + + # make sure first 2 chars of salt & chk field agree. + 'crypt$ffe86$c2M87q...WWcU', + ] + +class django_salted_md5_test(HandlerCase, _DjangoHelper): + """test django_salted_md5""" + handler = hash.django_salted_md5 + max_django_version = (1,9) + + known_correct_hashes = [ + # test extra large salt + ("password", 'md5$123abcdef$c8272612932975ee80e8a35995708e80'), + + # test django 1.4 alphanumeric salt + ("test", 'md5$3OpqnFAHW5CT$54b29300675271049a1ebae07b395e20'), + + # ensures utf-8 used for unicode + (UPASS_USD, 'md5$c2e86$92105508419a81a6babfaecf876a2fa0'), + (UPASS_TABLE, 'md5$d9eb8$01495b32852bffb27cf5d4394fe7a54c'), + ] + + known_unidentified_hashes = [ + 'sha1$aa$bb', + ] + + known_malformed_hashes = [ + # checksum too short + 'md5$aa$bb', + ] + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def random_salt_size(self): + # workaround for django14 regression -- + # 1.4 won't accept hashes with empty salt strings, unlike 1.3 and earlier. + # looks to be fixed in a future release -- https://code.djangoproject.com/ticket/18144 + # for now, we avoid salt_size==0 under 1.4 + handler = self.handler + default = handler.default_salt_size + assert handler.min_salt_size == 0 + lower = 1 + upper = handler.max_salt_size or default*4 + return self.randintgauss(lower, upper, default, default*.5) + +class django_salted_sha1_test(HandlerCase, _DjangoHelper): + """test django_salted_sha1""" + handler = hash.django_salted_sha1 + max_django_version = (1,9) + + known_correct_hashes = [ + # test extra large salt + ("password",'sha1$123abcdef$e4a1877b0e35c47329e7ed7e58014276168a37ba'), + + # test django 1.4 alphanumeric salt + ("test", 'sha1$bcwHF9Hy8lxS$6b4cfa0651b43161c6f1471ce9523acf1f751ba3'), + + # ensures utf-8 used for unicode + (UPASS_USD, 'sha1$c2e86$0f75c5d7fbd100d587c127ef0b693cde611b4ada'), + (UPASS_TABLE, 'sha1$6d853$ef13a4d8fb57aed0cb573fe9c82e28dc7fd372d4'), + + # generic password + ("MyPassword", 'sha1$54123$893cf12e134c3c215f3a76bd50d13f92404a54d3'), + ] + + known_unidentified_hashes = [ + 'md5$aa$bb', + ] + + known_malformed_hashes = [ + # checksum too short + 'sha1$c2e86$0f75', + ] + + # reuse custom random_salt_size() helper... + FuzzHashGenerator = django_salted_md5_test.FuzzHashGenerator + +class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper): + """test django_pbkdf2_sha256""" + handler = hash.django_pbkdf2_sha256 + + known_correct_hashes = [ + # + # custom - generated via django 1.4 hasher + # + ('not a password', + 'pbkdf2_sha256$10000$kjVJaVz6qsnJ$5yPHw3rwJGECpUf70daLGhOrQ5+AMxIJdz1c3bqK1Rs='), + (UPASS_TABLE, + 'pbkdf2_sha256$10000$bEwAfNrH1TlQ$OgYUblFNUX1B8GfMqaCYUK/iHyO0pa7STTDdaEJBuY0='), + ] + +class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper): + """test django_pbkdf2_sha1""" + handler = hash.django_pbkdf2_sha1 + + known_correct_hashes = [ + # + # custom - generated via django 1.4 hashers + # + ('not a password', + 'pbkdf2_sha1$10000$wz5B6WkasRoF$atJmJ1o+XfJxKq1+Nu1f1i57Z5I='), + (UPASS_TABLE, + 'pbkdf2_sha1$10000$KZKWwvqb8BfL$rw5pWsxJEU4JrZAQhHTCO+u0f5Y='), + ] + +@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available") +class django_bcrypt_test(HandlerCase, _DjangoHelper): + """test django_bcrypt""" + handler = hash.django_bcrypt + # XXX: not sure when this wasn't in default list anymore. somewhere in [2.0 - 2.2] + max_django_version = (2, 0) + fuzz_salts_need_bcrypt_repair = True + + known_correct_hashes = [ + # + # just copied and adapted a few test vectors from bcrypt (above), + # since django_bcrypt is just a wrapper for the real bcrypt class. + # + ('', 'bcrypt$$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), + ('abcdefghijklmnopqrstuvwxyz', + 'bcrypt$$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'), + (UPASS_TABLE, + 'bcrypt$$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), + ] + + # NOTE: the following have been cloned from _bcrypt_test() + + def populate_settings(self, kwds): + # speed up test w/ lower rounds + kwds.setdefault("rounds", 4) + super(django_bcrypt_test, self).populate_settings(kwds) + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(5, 8, 6, 1) + + def random_ident(self): + # omit multi-ident tests, only $2a$ counts for this class + # XXX: enable this to check 2a / 2b? + return None + +@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available") +class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper): + """test django_bcrypt_sha256""" + handler = hash.django_bcrypt_sha256 + forbidden_characters = None + fuzz_salts_need_bcrypt_repair = True + + known_correct_hashes = [ + # + # custom - generated via django 1.6 hasher + # + ('', + 'bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu'), + (UPASS_LETMEIN, + 'bcrypt_sha256$$2a$08$NDjSAIcas.EcoxCRiArvT.MkNiPYVhrsrnJsRkLueZOoV1bsQqlmC'), + (UPASS_TABLE, + 'bcrypt_sha256$$2a$06$kCXUnRFQptGg491siDKNTu8RxjBGSjALHRuvhPYNFsa4Ea5d9M48u'), + + # test >72 chars is hashed correctly -- under bcrypt these hash the same. + (repeat_string("abc123",72), + 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OySmyXA8FoY4PjGizjE1QSDfuL5MXNni'), + (repeat_string("abc123",72)+"qwr", + 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61Ocy0BEz1RK6xslSNi8PlaLX2pe7x/KQG'), + (repeat_string("abc123",72)+"xyz", + 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OvY2zoRVUa2Pugv2ExVOUT2YmhvxUFUa'), + ] + + known_malformed_hashers = [ + # data in django salt field + 'bcrypt_sha256$xyz$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu', + ] + + # NOTE: the following have been cloned from _bcrypt_test() + + def populate_settings(self, kwds): + # speed up test w/ lower rounds + kwds.setdefault("rounds", 4) + super(django_bcrypt_sha256_test, self).populate_settings(kwds) + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(5, 8, 6, 1) + + def random_ident(self): + # omit multi-ident tests, only $2a$ counts for this class + # XXX: enable this to check 2a / 2b? + return None + +from passlib.tests.test_handlers_argon2 import _base_argon2_test + +@skipUnless(hash.argon2.has_backend(), "no argon2 backends available") +class django_argon2_test(HandlerCase, _DjangoHelper): + """test django_bcrypt""" + handler = hash.django_argon2 + + # NOTE: most of this adapted from _base_argon2_test & argon2pure test + + known_correct_hashes = [ + # sample test + ("password", 'argon2$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'), + + # sample w/ all parameters different + ("password", 'argon2$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'), + + # generated from django 1.10.3 + (UPASS_LETMEIN, 'argon2$argon2i$v=19$m=512,t=2,p=2$V25jN1l4UUJZWkR1$MxpA1BD2Gh7+D79gaAw6sQ'), + ] + + def setUpWarnings(self): + super(django_argon2_test, self).setUpWarnings() + warnings.filterwarnings("ignore", ".*Using argon2pure backend.*") + + def do_stub_encrypt(self, handler=None, **settings): + # overriding default since no way to get stub config from argon2._calc_hash() + # (otherwise test_21b_max_rounds blocks trying to do max rounds) + handler = (handler or self.handler).using(**settings) + self = handler.wrapped(use_defaults=True) + self.checksum = self._stub_checksum + assert self.checksum + return handler._wrap_hash(self.to_string()) + + def test_03_legacy_hash_workflow(self): + # override base method + raise self.skipTest("legacy 1.6 workflow not supported") + + class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator): + + def random_type(self): + # override default since django only uses type I (see note in class) + return "I" + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(1, 3, 2, 1) + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py b/venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py new file mode 100644 index 0000000..4d2f048 --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py @@ -0,0 +1,480 @@ +"""passlib.tests.test_handlers - tests for passlib hash algorithms""" +#============================================================================= +# imports +#============================================================================= +# core +import logging +log = logging.getLogger(__name__) +import warnings +# site +# pkg +from passlib import hash +from passlib.utils.compat import u +from passlib.tests.utils import TestCase, HandlerCase +from passlib.tests.test_handlers import UPASS_WAV +# module + +#============================================================================= +# ldap_pbkdf2_{digest} +#============================================================================= +# NOTE: since these are all wrappers for the pbkdf2_{digest} hasehs, +# they don't extensive separate testing. + +class ldap_pbkdf2_test(TestCase): + + def test_wrappers(self): + """test ldap pbkdf2 wrappers""" + + self.assertTrue( + hash.ldap_pbkdf2_sha1.verify( + "password", + '{PBKDF2}1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI', + ) + ) + + self.assertTrue( + hash.ldap_pbkdf2_sha256.verify( + "password", + '{PBKDF2-SHA256}1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg' + '.fJPeq1h/gXXY7acBp9/6c.tmQ' + ) + ) + + self.assertTrue( + hash.ldap_pbkdf2_sha512.verify( + "password", + '{PBKDF2-SHA512}1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1' + '7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww' + ) + ) + +#============================================================================= +# pbkdf2 hashes +#============================================================================= +class atlassian_pbkdf2_sha1_test(HandlerCase): + handler = hash.atlassian_pbkdf2_sha1 + + known_correct_hashes = [ + # + # generated using Jira + # + ("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'), + (UPASS_WAV, + "{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"), + ] + + known_malformed_hashes = [ + # bad char ---\/ + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy!0IPksHChwoTAVYFrhsgoq8/p' + + # bad size, missing padding + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/' + + # bad size, with correct padding + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/=' + ] + +class pbkdf2_sha1_test(HandlerCase): + handler = hash.pbkdf2_sha1 + known_correct_hashes = [ + ("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'), + (UPASS_WAV, + '$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'), + ] + + known_malformed_hashes = [ + # zero padded rounds field + '$pbkdf2$01212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc', + + # empty rounds field + '$pbkdf2$$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc', + + # too many field + '$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc$', + ] + +class pbkdf2_sha256_test(HandlerCase): + handler = hash.pbkdf2_sha256 + known_correct_hashes = [ + ("password", + '$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ' + ), + (UPASS_WAV, + '$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI' + ), + ] + +class pbkdf2_sha512_test(HandlerCase): + handler = hash.pbkdf2_sha512 + known_correct_hashes = [ + ("password", + '$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1' + '7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww' + ), + (UPASS_WAV, + '$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt' + 'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q' + ), + ] + +class cta_pbkdf2_sha1_test(HandlerCase): + handler = hash.cta_pbkdf2_sha1 + known_correct_hashes = [ + # + # test vectors from original implementation + # + (u("hashy the \N{SNOWMAN}"), '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='), + + # + # custom + # + ("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="), + (UPASS_WAV, + "$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="), + ] + +class dlitz_pbkdf2_sha1_test(HandlerCase): + handler = hash.dlitz_pbkdf2_sha1 + known_correct_hashes = [ + # + # test vectors from original implementation + # + ('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'), + ('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'), + ('dcl', '$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL'), + ('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'), + (UPASS_WAV, + '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'), + ] + +class grub_pbkdf2_sha512_test(HandlerCase): + handler = hash.grub_pbkdf2_sha512 + known_correct_hashes = [ + # + # test vectors generated from cmd line tool + # + + # salt=32 bytes + (UPASS_WAV, + 'grub.pbkdf2.sha512.10000.BCAC1CEC5E4341C8C511C529' + '7FA877BE91C2817B32A35A3ECF5CA6B8B257F751.6968526A' + '2A5B1AEEE0A29A9E057336B48D388FFB3F600233237223C21' + '04DE1752CEC35B0DD1ED49563398A282C0F471099C2803FBA' + '47C7919CABC43192C68F60'), + + # salt=64 bytes + ('toomanysecrets', + 'grub.pbkdf2.sha512.10000.9B436BB6978682363D5C449B' + 'BEAB322676946C632208BC1294D51F47174A9A3B04A7E4785' + '986CD4EA7470FAB8FE9F6BD522D1FC6C51109A8596FB7AD48' + '7C4493.0FE5EF169AFFCB67D86E2581B1E251D88C777B98BA' + '2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4' + '634083F9A20F4B6F339A52FBD6BED618E527B'), + + ] + +#============================================================================= +# scram hash +#============================================================================= +class scram_test(HandlerCase): + handler = hash.scram + + # TODO: need a bunch more reference vectors from some real + # SCRAM transactions. + known_correct_hashes = [ + # + # taken from example in SCRAM specification (rfc 5802) + # + ('pencil', '$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), + + # + # custom + # + + # same as 5802 example hash, but with sha-256 & sha-512 added. + ('pencil', '$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,' + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'), + + # test unicode passwords & saslprep (all the passwords below + # should normalize to the same value: 'IX \xE0') + (u('IX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$' + 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), + (u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$' + 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), + (u('\u00ADIX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$' + 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), + ] + + known_malformed_hashes = [ + # zero-padding in rounds + '$scram$04096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', + + # non-digit in rounds + '$scram$409A$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', + + # bad char in salt ---\/ + '$scram$4096$QSXCR.Q6sek8bf9-$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', + + # bad char in digest ---\/ + '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX3-', + + # missing sections + '$scram$4096$QSXCR.Q6sek8bf92', + '$scram$4096$QSXCR.Q6sek8bf92$', + + # too many sections + '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30$', + + # missing separator + '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY', + + # too many chars in alg name + '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'shaxxx-190=HZbuOlKbWl.eR8AfIposuKbhX30', + + # missing sha-1 alg + '$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30', + + # non-iana name + '$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30', + ] + + def setUp(self): + super(scram_test, self).setUp() + + # some platforms lack stringprep (e.g. Jython, IronPython) + self.require_stringprep() + + # silence norm_hash_name() warning + warnings.filterwarnings("ignore", r"norm_hash_name\(\): unknown hash") + + def test_90_algs(self): + """test parsing of 'algs' setting""" + defaults = dict(salt=b'A'*10, rounds=1000) + def parse(algs, **kwds): + for k in defaults: + kwds.setdefault(k, defaults[k]) + return self.handler(algs=algs, **kwds).algs + + # None -> default list + self.assertEqual(parse(None, use_defaults=True), hash.scram.default_algs) + self.assertRaises(TypeError, parse, None) + + # strings should be parsed + self.assertEqual(parse("sha1"), ["sha-1"]) + self.assertEqual(parse("sha1, sha256, md5"), ["md5","sha-1","sha-256"]) + + # lists should be normalized + self.assertEqual(parse(["sha-1","sha256"]), ["sha-1","sha-256"]) + + # sha-1 required + self.assertRaises(ValueError, parse, ["sha-256"]) + self.assertRaises(ValueError, parse, algs=[], use_defaults=True) + + # alg names must be < 10 chars + self.assertRaises(ValueError, parse, ["sha-1","shaxxx-190"]) + + # alg & checksum mutually exclusive. + self.assertRaises(RuntimeError, parse, ['sha-1'], + checksum={"sha-1": b"\x00"*20}) + + def test_90_checksums(self): + """test internal parsing of 'checksum' keyword""" + # check non-bytes checksum values are rejected + self.assertRaises(TypeError, self.handler, use_defaults=True, + checksum={'sha-1': u('X')*20}) + + # check sha-1 is required + self.assertRaises(ValueError, self.handler, use_defaults=True, + checksum={'sha-256': b'X'*32}) + + # XXX: anything else that's not tested by the other code already? + + def test_91_extract_digest_info(self): + """test scram.extract_digest_info()""" + edi = self.handler.extract_digest_info + + # return appropriate value or throw KeyError + h = "$scram$10$AAAAAA$sha-1=AQ,bbb=Ag,ccc=Aw" + s = b'\x00'*4 + self.assertEqual(edi(h,"SHA1"), (s,10, b'\x01')) + self.assertEqual(edi(h,"bbb"), (s,10, b'\x02')) + self.assertEqual(edi(h,"ccc"), (s,10, b'\x03')) + self.assertRaises(KeyError, edi, h, "ddd") + + # config strings should cause value error. + c = "$scram$10$....$sha-1,bbb,ccc" + self.assertRaises(ValueError, edi, c, "sha-1") + self.assertRaises(ValueError, edi, c, "bbb") + self.assertRaises(ValueError, edi, c, "ddd") + + def test_92_extract_digest_algs(self): + """test scram.extract_digest_algs()""" + eda = self.handler.extract_digest_algs + + self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), ["sha-1"]) + + self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', format="hashlib"), + ["sha1"]) + + self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,' + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'), + ["sha-1","sha-256","sha-512"]) + + def test_93_derive_digest(self): + """test scram.derive_digest()""" + # NOTE: this just does a light test, since derive_digest + # is used by hash / verify, and is tested pretty well via those. + hash = self.handler.derive_digest + + # check various encodings of password work. + s1 = b'\x01\x02\x03' + d1 = b'\xb2\xfb\xab\x82[tNuPnI\x8aZZ\x19\x87\xcen\xe9\xd3' + self.assertEqual(hash(u("\u2168"), s1, 1000, 'sha-1'), d1) + self.assertEqual(hash(b"\xe2\x85\xa8", s1, 1000, 'SHA-1'), d1) + self.assertEqual(hash(u("IX"), s1, 1000, 'sha1'), d1) + self.assertEqual(hash(b"IX", s1, 1000, 'SHA1'), d1) + + # check algs + self.assertEqual(hash("IX", s1, 1000, 'md5'), + b'3\x19\x18\xc0\x1c/\xa8\xbf\xe4\xa3\xc2\x8eM\xe8od') + self.assertRaises(ValueError, hash, "IX", s1, 1000, 'sha-666') + + # check rounds + self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1') + + # unicode salts accepted as of passlib 1.7 (previous caused TypeError) + self.assertEqual(hash(u("IX"), s1.decode("latin-1"), 1000, 'sha1'), d1) + + def test_94_saslprep(self): + """test hash/verify use saslprep""" + # NOTE: this just does a light test that saslprep() is being + # called in various places, relying in saslpreps()'s tests + # to verify full normalization behavior. + + # hash unnormalized + h = self.do_encrypt(u("I\u00ADX")) + self.assertTrue(self.do_verify(u("IX"), h)) + self.assertTrue(self.do_verify(u("\u2168"), h)) + + # hash normalized + h = self.do_encrypt(u("\xF3")) + self.assertTrue(self.do_verify(u("o\u0301"), h)) + self.assertTrue(self.do_verify(u("\u200Do\u0301"), h)) + + # throws error if forbidden char provided + self.assertRaises(ValueError, self.do_encrypt, u("\uFDD0")) + self.assertRaises(ValueError, self.do_verify, u("\uFDD0"), h) + + def test_94_using_w_default_algs(self, param="default_algs"): + """using() -- 'default_algs' parameter""" + # create subclass + handler = self.handler + orig = list(handler.default_algs) # in case it's modified in place + subcls = handler.using(**{param: "sha1,md5"}) + + # shouldn't have changed handler + self.assertEqual(handler.default_algs, orig) + + # should have own set + self.assertEqual(subcls.default_algs, ["md5", "sha-1"]) + + # test hash output + h1 = subcls.hash("dummy") + self.assertEqual(handler.extract_digest_algs(h1), ["md5", "sha-1"]) + + def test_94_using_w_algs(self): + """using() -- 'algs' parameter""" + self.test_94_using_w_default_algs(param="algs") + + def test_94_needs_update_algs(self): + """needs_update() -- algs setting""" + handler1 = self.handler.using(algs="sha1,md5") + + # shouldn't need update, has same algs + h1 = handler1.hash("dummy") + self.assertFalse(handler1.needs_update(h1)) + + # *currently* shouldn't need update, has superset of algs required by handler2 + # (may change this policy) + handler2 = handler1.using(algs="sha1") + self.assertFalse(handler2.needs_update(h1)) + + # should need update, doesn't have all algs required by handler3 + handler3 = handler1.using(algs="sha1,sha256") + self.assertTrue(handler3.needs_update(h1)) + + def test_95_context_algs(self): + """test handling of 'algs' in context object""" + handler = self.handler + from passlib.context import CryptContext + c1 = CryptContext(["scram"], scram__algs="sha1,md5") + + h = c1.hash("dummy") + self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"]) + self.assertFalse(c1.needs_update(h)) + + c2 = c1.copy(scram__algs="sha1") + self.assertFalse(c2.needs_update(h)) + + c2 = c1.copy(scram__algs="sha1,sha256") + self.assertTrue(c2.needs_update(h)) + + def test_96_full_verify(self): + """test verify(full=True) flag""" + def vpart(s, h): + return self.handler.verify(s, h) + def vfull(s, h): + return self.handler.verify(s, h, full=True) + + # reference + h = ('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,' + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') + self.assertTrue(vfull('pencil', h)) + self.assertFalse(vfull('tape', h)) + + # catch truncated digests. + h = ('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhV,' # -1 char + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') + self.assertRaises(ValueError, vfull, 'pencil', h) + + # catch padded digests. + h = ('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' + 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVYa,' # +1 char + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') + self.assertRaises(ValueError, vfull, 'pencil', h) + + # catch hash containing digests belonging to diff passwords. + # proper behavior for quick-verify (the default) is undefined, + # but full-verify should throw error. + h = ('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' # 'pencil' + 'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc,' # 'tape' + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' # 'pencil' + 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') + self.assertTrue(vpart('tape', h)) + self.assertFalse(vpart('pencil', h)) + self.assertRaises(ValueError, vfull, 'pencil', h) + self.assertRaises(ValueError, vfull, 'tape', h) + +#============================================================================= +# eof +#============================================================================= diff --git a/venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py b/venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py new file mode 100644 index 0000000..5ab6d9f --- /dev/null +++ b/venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py @@ -0,0 +1,111 @@ +"""passlib.tests.test_handlers - tests for passlib hash algorithms""" +#============================================================================= +# imports +#============================================================================= +# core +import logging; log = logging.getLogger(__name__) +import warnings +warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*") +# site +# pkg +from passlib import hash +from passlib.tests.utils import HandlerCase, TEST_MODE +from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8 +# module + +#============================================================================= +# scrypt hash +#============================================================================= +class _scrypt_test(HandlerCase): + handler = hash.scrypt + + known_correct_hashes = [ + # + # excepted from test vectors from scrypt whitepaper + # (http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b), + # and encoded using passlib's custom format + # + + # salt=b"" + ("", "$scrypt$ln=4,r=1,p=1$$d9ZXYjhleyA7GcpCwYoEl/FrSETjB0ro39/6P+3iFEI"), + + # salt=b"NaCl" + ("password", "$scrypt$ln=10,r=8,p=16$TmFDbA$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWI"), + + # + # custom + # + + # simple test + ("test", '$scrypt$ln=8,r=8,p=1$wlhLyXmP8b53bm1NKYVQqg$mTpvG8lzuuDk+DWz8HZIB6Vum6erDuUm0As5yU+VxWA'), + + # different block value + ("password", '$scrypt$ln=8,r=2,p=1$dO6d0xoDoLT2PofQGoNQag$g/Wf2A0vhHhaJM+addK61QPBthSmYB6uVTtQzh8CM3o'), + + # different rounds + (UPASS_TABLE, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'), + + # alt encoding + (PASS_TABLE_UTF8, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'), + + # diff block & parallel counts as well + ("nacl", '$scrypt$ln=1,r=4,p=2$yhnD+J+Tci4lZCwFgHCuVQ$fAsEWmxSHuC0cHKMwKVFPzrQukgvK09Sj+NueTSxKds') + ] + + if TEST_MODE("full"): + # add some hashes with larger rounds value. + known_correct_hashes.extend([ + # + # from scrypt whitepaper + # + + # salt=b"SodiumChloride" + ("pleaseletmein", "$scrypt$ln=14,r=8,p=1$U29kaXVtQ2hsb3JpZGU" + "$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+qbVD9lRdofI"), + + # + # openwall format (https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt) + # + ("pleaseletmein", + "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D"), + + ]) + + known_malformed_hashes = [ + # missing 'p' value + '$scrypt$ln=10,r=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ', + + # rounds too low + '$scrypt$ln=0,r=1,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ', + + # invalid block size + '$scrypt$ln=10,r=A,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ', + + # r*p too large + '$scrypt$ln=10,r=134217728,p=8$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ', + ] + + def setUpWarnings(self): + super(_scrypt_test, self).setUpWarnings() + warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*") + + def populate_settings(self, kwds): + # builtin is still just way too slow. + if self.backend == "builtin": + kwds.setdefault("rounds", 6) + super(_scrypt_test, self).populate_settings(kwds) + + class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): + + def random_rounds(self): + # decrease default rounds for fuzz testing to speed up volume. + return self.randintgauss(4, 10, 6, 1) + +# create test cases for specific backends +scrypt_stdlib_test = _scrypt_test.create_backend_case("stdlib") +scrypt_scrypt_test = _scrypt_test.create_backend_case("scrypt") +scrypt_builtin_test = _scrypt_test.create_backend_case("builtin") + +#============================================================================= +# eof +#=============================================================================