def test_norm_hash_name(self): "test norm_hash_name()" from itertools import chain from 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))
def test_20_norm_salt(self): "test GenericHandler+HasSalt: .norm_salt(), .generate_salt()" class d1(uh.HasSalt, uh.GenericHandler): name = 'd1' setting_kwds = ('salt',) min_salt_size = 1 max_salt_size = 3 default_salt_size = 2 salt_chars = 'a' #check salt=None self.assertEqual(d1.norm_salt(None), 'aa') self.assertRaises(ValueError, d1.norm_salt, None, strict=True) #check small & large salts with catch_warnings(): warnings.filterwarnings("ignore", ".* salt string must be at (least|most) .*", UserWarning) self.assertEqual(d1.norm_salt('aaaa'), 'aaa') self.assertRaises(ValueError, d1.norm_salt, '') self.assertRaises(ValueError, d1.norm_salt, 'aaaa', strict=True) #check generate salt (indirectly) self.assertEqual(len(d1.norm_salt(None)), 2) self.assertEqual(len(d1.norm_salt(None,salt_size=1)), 1) self.assertEqual(len(d1.norm_salt(None,salt_size=3)), 3) self.assertEqual(len(d1.norm_salt(None,salt_size=5)), 3) self.assertRaises(ValueError, d1.norm_salt, None, salt_size=5, strict=True)
def test_get_crypt_handler(self): "test get_crypt_handler()" class dummy_1(uh.StaticHandler): name = "dummy_1" # without available handler self.assertRaises(KeyError, get_crypt_handler, "dummy_1") self.assertIs(get_crypt_handler("dummy_1", None), None) # already loaded handler register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) with catch_warnings(): warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning) # already loaded handler, using incorrect name self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1) # lazy load of unloaded handler, using incorrect name register_crypt_handler_path('dummy_0', __name__) self.assertIs(get_crypt_handler("DUMMY-0"), dummy_0) # check system & private names aren't returned import passlib.hash # ensure module imported, so py3.3 sets __package__ passlib.hash.__dict__["_fake"] = "dummy" # so behavior seen under py2x also for name in ["_fake", "__package__"]: self.assertRaises(KeyError, get_crypt_handler, name) self.assertIs(get_crypt_handler(name, None), None)
def test_30_norm_rounds(self): "test GenericHandler+HasRounds: .norm_rounds()" class d1(uh.HasRounds, uh.GenericHandler): name = "d1" setting_kwds = ("rounds",) min_rounds = 1 max_rounds = 3 default_rounds = 2 # check rounds=None self.assertEqual(d1.norm_rounds(None), 2) self.assertRaises(ValueError, d1.norm_rounds, None, strict=True) # check small & large rounds with catch_warnings(): warnings.filterwarnings("ignore", ".* does not allow (less|more) than \d rounds: .*", UserWarning) self.assertEqual(d1.norm_rounds(0), 1) self.assertEqual(d1.norm_rounds(4), 3) self.assertRaises(ValueError, d1.norm_rounds, 0, strict=True) self.assertRaises(ValueError, d1.norm_rounds, 4, strict=True) # check no default rounds d1.default_rounds = None self.assertRaises(ValueError, d1.norm_rounds, None)
def test_20_norm_salt(self): "test GenericHandler+HasSalt: .norm_salt(), .generate_salt()" class d1(uh.HasSalt, uh.GenericHandler): name = "d1" setting_kwds = ("salt",) min_salt_size = 1 max_salt_size = 3 default_salt_size = 2 salt_chars = "a" # check salt=None self.assertEqual(d1.norm_salt(None), "aa") self.assertRaises(ValueError, d1.norm_salt, None, strict=True) # check small & large salts with catch_warnings(): warnings.filterwarnings("ignore", ".* salt string must be at (least|most) .*", UserWarning) self.assertEqual(d1.norm_salt("aaaa"), "aaa") self.assertRaises(ValueError, d1.norm_salt, "") self.assertRaises(ValueError, d1.norm_salt, "aaaa", strict=True) # check generate salt (indirectly) self.assertEqual(len(d1.norm_salt(None)), 2) self.assertEqual(len(d1.norm_salt(None, salt_size=1)), 1) self.assertEqual(len(d1.norm_salt(None, salt_size=3)), 3) self.assertEqual(len(d1.norm_salt(None, salt_size=5)), 3) self.assertRaises(ValueError, d1.norm_salt, None, salt_size=5, strict=True)
def test_get_crypt_handler(self): "test get_crypt_handler()" class dummy_1(uh.StaticHandler): name = "dummy_1" # without available handler self.assertRaises(KeyError, get_crypt_handler, "dummy_1") self.assertIs(get_crypt_handler("dummy_1", None), None) # already loaded handler register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) with catch_warnings(): warnings.filterwarnings( "ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning) # already loaded handler, using incorrect name self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1) # lazy load of unloaded handler, using incorrect name register_crypt_handler_path('dummy_0', __name__) self.assertIs(get_crypt_handler("DUMMY-0"), dummy_0) # check system & private names aren't returned import passlib.hash # ensure module imported, so py3.3 sets __package__ passlib.hash.__dict__[ "_fake"] = "dummy" # so behavior seen under py2x also for name in ["_fake", "__package__"]: self.assertRaises(KeyError, get_crypt_handler, name) self.assertIs(get_crypt_handler(name, None), None)
def test_norm_hash_name(self): "test norm_hash_name()" from itertools import chain from 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))
def test_get_crypt_handler(self): "test get_crypt_handler()" class dummy_1(uh.StaticHandler): name = "dummy_1" self.assertRaises(KeyError, get_crypt_handler, "dummy_1") register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) with catch_warnings(): warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning) self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1)
def test_24_min_verify_time(self): "test verify() honors min_verify_time" #NOTE: this whole test assumes time.sleep() and time.time() # have at least 1ms accuracy delta = .1 min_delay = delta min_verify_time = min_delay + 2*delta max_delay = min_verify_time + 2*delta class TimedHash(uh.StaticHandler): "psuedo hash that takes specified amount of time" name = "timed_hash" delay = 0 @classmethod def identify(cls, hash): return True @classmethod def genhash(cls, secret, hash): time.sleep(cls.delay) return hash or 'x' cc = CryptContext([TimedHash], min_verify_time=min_verify_time) def timecall(func, *args, **kwds): start = time.time() result = func(*args, **kwds) end = time.time() return end-start, result #verify hashing works TimedHash.delay = min_delay elapsed, _ = timecall(TimedHash.genhash, 'stub', 'stub') self.assertAlmostEqual(elapsed, min_delay, delta=delta) #ensure min verify time is honored elapsed, _ = timecall(cc.verify, "stub", "stub") self.assertAlmostEqual(elapsed, min_verify_time, delta=delta) #ensure taking longer emits a warning. TimedHash.delay = max_delay with catch_warnings(record=True) as wlog: warnings.simplefilter("always") elapsed, _ = timecall(cc.verify, "stub", "stub") self.assertAlmostEqual(elapsed, max_delay, delta=delta) self.assertEqual(len(wlog), 1) self.assertWarningMatches(wlog[0], message_re="CryptContext: verify exceeded min_verify_time")
def test_register_crypt_handler_path(self): """test register_crypt_handler_path()""" # NOTE: this messes w/ internals of registry, shouldn't be used publically. paths = registry._locations # check namespace is clear self.assertTrue('dummy_0' not in paths) self.assertFalse(hasattr(hash, 'dummy_0')) # check invalid names are rejected self.assertRaises(ValueError, register_crypt_handler_path, "dummy_0", ".test_registry") self.assertRaises(ValueError, register_crypt_handler_path, "dummy_0", __name__ + ":dummy_0:xxx") self.assertRaises(ValueError, register_crypt_handler_path, "dummy_0", __name__ + ":dummy_0.xxx") # try lazy load register_crypt_handler_path('dummy_0', __name__) self.assertTrue('dummy_0' in list_crypt_handlers()) self.assertTrue('dummy_0' not in list_crypt_handlers(loaded_only=True)) self.assertIs(hash.dummy_0, dummy_0) self.assertTrue('dummy_0' in list_crypt_handlers(loaded_only=True)) unload_handler_name('dummy_0') # try lazy load w/ alt register_crypt_handler_path('dummy_0', __name__ + ':alt_dummy_0') self.assertIs(hash.dummy_0, alt_dummy_0) unload_handler_name('dummy_0') # check lazy load w/ wrong type fails register_crypt_handler_path('dummy_x', __name__) self.assertRaises(TypeError, get_crypt_handler, 'dummy_x') # check lazy load w/ wrong name fails register_crypt_handler_path('alt_dummy_0', __name__) self.assertRaises(ValueError, get_crypt_handler, "alt_dummy_0") unload_handler_name("alt_dummy_0") # TODO: check lazy load which calls register_crypt_handler (warning should be issued) sys.modules.pop("passlib.tests._test_bad_register", None) register_crypt_handler_path("dummy_bad", "passlib.tests._test_bad_register") with catch_warnings(): warnings.filterwarnings("ignore", "xxxxxxxxxx", DeprecationWarning) h = get_crypt_handler("dummy_bad") from passlib.tests import _test_bad_register as tbr self.assertIs(h, tbr.alt_dummy_bad)
def test_30_norm_rounds(self): "test GenericHandler + HasRounds mixin" # setup helpers class d1(uh.HasRounds, uh.GenericHandler): name = 'd1' setting_kwds = ('rounds', ) min_rounds = 1 max_rounds = 3 default_rounds = 2 def norm_rounds(**k): return d1(**k).rounds # check rounds=None self.assertRaises(TypeError, norm_rounds) self.assertRaises(TypeError, norm_rounds, rounds=None) self.assertEqual(norm_rounds(use_defaults=True), 2) # check rounds=non int self.assertRaises(TypeError, norm_rounds, rounds=1.5) # check explicit rounds with catch_warnings(record=True) as wlog: # too small self.assertRaises(ValueError, norm_rounds, rounds=0) self.consumeWarningList(wlog) self.assertEqual(norm_rounds(rounds=0, relaxed=True), 1) self.consumeWarningList(wlog, PasslibHashWarning) # just right self.assertEqual(norm_rounds(rounds=1), 1) self.assertEqual(norm_rounds(rounds=2), 2) self.assertEqual(norm_rounds(rounds=3), 3) self.consumeWarningList(wlog) # too large self.assertRaises(ValueError, norm_rounds, rounds=4) self.consumeWarningList(wlog) self.assertEqual(norm_rounds(rounds=4, relaxed=True), 3) self.consumeWarningList(wlog, PasslibHashWarning) # check no default rounds d1.default_rounds = None self.assertRaises(TypeError, norm_rounds, use_defaults=True)
def test_30_norm_rounds(self): "test GenericHandler + HasRounds mixin" # setup helpers class d1(uh.HasRounds, uh.GenericHandler): name = 'd1' setting_kwds = ('rounds',) min_rounds = 1 max_rounds = 3 default_rounds = 2 def norm_rounds(**k): return d1(**k).rounds # check rounds=None self.assertRaises(TypeError, norm_rounds) self.assertRaises(TypeError, norm_rounds, rounds=None) self.assertEqual(norm_rounds(use_defaults=True), 2) # check rounds=non int self.assertRaises(TypeError, norm_rounds, rounds=1.5) # check explicit rounds with catch_warnings(record=True) as wlog: # too small self.assertRaises(ValueError, norm_rounds, rounds=0) self.consumeWarningList(wlog) self.assertEqual(norm_rounds(rounds=0, relaxed=True), 1) self.consumeWarningList(wlog, PasslibHashWarning) # just right self.assertEqual(norm_rounds(rounds=1), 1) self.assertEqual(norm_rounds(rounds=2), 2) self.assertEqual(norm_rounds(rounds=3), 3) self.consumeWarningList(wlog) # too large self.assertRaises(ValueError, norm_rounds, rounds=4) self.consumeWarningList(wlog) self.assertEqual(norm_rounds(rounds=4, relaxed=True), 3) self.consumeWarningList(wlog, PasslibHashWarning) # check no default rounds d1.default_rounds = None self.assertRaises(TypeError, norm_rounds, use_defaults=True)
def test_01_patch_control_detection(self): "test set_django_password_context detection of foreign monkeypatches" def dummy(): pass with catch_warnings(record=True) as wlog: warnings.simplefilter("always") #patch to use stock django context utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 0) #mess with User.set_password, make sure it's detected dam.User.set_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches( wlog.pop(), message_re="^another library has patched.*User\.set_password$") #mess with user.check_password, make sure it's detected dam.User.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches( wlog.pop(), message_re="^another library has patched.*User\.check_password$" ) #mess with user.check_password, make sure it's detected dam.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches( wlog.pop(), message_re= "^another library has patched.*models:check_password$")
def test_11_encrypt_settings(self): "test encrypt() honors policy settings" cc = CryptContext(**self.sample_policy_1) # hash specific settings self.assertEqual( cc.encrypt("password", scheme="nthash"), '$NT$8846f7eaee8fb117ad06bdd830b7586c', ) self.assertEqual( cc.encrypt("password", scheme="nthash", ident="3"), '$3$$8846f7eaee8fb117ad06bdd830b7586c', ) # min rounds self.assertEqual( cc.encrypt("password", rounds=1999, salt="nacl"), '$5$rounds=2000$nacl$9/lTZ5nrfPuz8vphznnmHuDGFuvjSNvOEDsGmGfsS97', ) self.assertEqual( cc.encrypt("password", rounds=2001, salt="nacl"), '$5$rounds=2001$nacl$8PdeoPL4aXQnJ0woHhqgIw/efyfCKC2WHneOpnvF.31' ) #TODO: # max rounds # default rounds # falls back to max, then min. # specified # outside of min/max range # default+vary rounds # default+vary % rounds #make sure default > max doesn't cause error when vary is set cc2 = cc.replace(sha256_crypt__default_rounds=4000) with catch_warnings(): warnings.filterwarnings("ignore", "vary default rounds: lower bound > upper bound.*", UserWarning) self.assertEqual( cc2.encrypt("password", salt="nacl"), '$5$rounds=3000$nacl$oH831OVMbkl.Lbw1SXflly4dW8L3mSxpxDz1u1CK/B0', )
def test_90_bcrypt_normhash(self): "teset verify_and_update / hash_needs_update corrects bcrypt padding" # see issue 25. bcrypt = hash.bcrypt PASS1 = "loppux" BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" ctx = CryptContext(["bcrypt"]) with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertTrue(ctx.hash_needs_update(BAD1)) self.assertFalse(ctx.hash_needs_update(GOOD1)) if bcrypt.has_backend(): self.assertEquals(ctx.verify_and_update(PASS1,GOOD1), (True,None)) self.assertEquals(ctx.verify_and_update("x",BAD1), (False,None)) res = ctx.verify_and_update(PASS1, BAD1) self.assertTrue(res[0] and res[1] and res[1] != BAD1)
def test_get_crypt_handler(self): "test get_crypt_handler()" class dummy_1(uh.StaticHandler): name = "dummy_1" # without available handler self.assertRaises(KeyError, get_crypt_handler, "dummy_1") self.assertIs(get_crypt_handler("dummy_1", None), None) # already loaded handler register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) with catch_warnings(): warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning) # already loaded handler, using incorrect name self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1) # lazy load of unloaded handler, using incorrect name register_crypt_handler_path('dummy_0', __name__) self.assertIs(get_crypt_handler("DUMMY-0"), dummy_0)
def test_30_norm_rounds(self): "test GenericHandler+HasRounds: .norm_rounds()" class d1(uh.HasRounds, uh.GenericHandler): name = 'd1' setting_kwds = ('rounds',) min_rounds = 1 max_rounds = 3 default_rounds = 2 #check rounds=None self.assertEqual(d1.norm_rounds(None), 2) self.assertRaises(ValueError, d1.norm_rounds, None, strict=True) #check small & large rounds with catch_warnings(): warnings.filterwarnings("ignore", ".* does not allow (less|more) than \d rounds: .*", UserWarning) self.assertEqual(d1.norm_rounds(0), 1) self.assertEqual(d1.norm_rounds(4), 3) self.assertRaises(ValueError, d1.norm_rounds, 0, strict=True) self.assertRaises(ValueError, d1.norm_rounds, 4, strict=True) #check no default rounds d1.default_rounds = None self.assertRaises(ValueError, d1.norm_rounds, None)
def test_01_patch_control_detection(self): "test set_django_password_context detection of foreign monkeypatches" def dummy(): pass with catch_warnings(record=True) as wlog: warnings.simplefilter("always") #patch to use stock django context utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 0) #mess with User.set_password, make sure it's detected dam.User.set_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*User\.set_password$") #mess with user.check_password, make sure it's detected dam.User.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*User\.check_password$") #mess with user.check_password, make sure it's detected dam.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*models:check_password$")
def test_20_norm_salt(self): "test GenericHandler + HasSalt mixin" # setup helpers class d1(uh.HasSalt, uh.GenericHandler): name = 'd1' setting_kwds = ('salt',) min_salt_size = 2 max_salt_size = 4 default_salt_size = 3 salt_chars = 'ab' def norm_salt(**k): return d1(**k).salt def gen_salt(sz, **k): return d1(use_defaults=True, salt_size=sz, **k).salt salts2 = _makelang('ab', 2) salts3 = _makelang('ab', 3) salts4 = _makelang('ab', 4) # check salt=None self.assertRaises(TypeError, norm_salt) self.assertRaises(TypeError, norm_salt, salt=None) self.assertIn(norm_salt(use_defaults=True), salts3) # check explicit salts with catch_warnings(record=True) as wlog: # check too-small salts self.assertRaises(ValueError, norm_salt, salt='') self.assertRaises(ValueError, norm_salt, salt='a') self.consumeWarningList(wlog) # check correct salts self.assertEqual(norm_salt(salt='ab'), 'ab') self.assertEqual(norm_salt(salt='aba'), 'aba') self.assertEqual(norm_salt(salt='abba'), 'abba') self.consumeWarningList(wlog) # check too-large salts self.assertRaises(ValueError, norm_salt, salt='aaaabb') self.consumeWarningList(wlog) self.assertEqual(norm_salt(salt='aaaabb', relaxed=True), 'aaaa') self.consumeWarningList(wlog, PasslibHashWarning) # check generated salts with catch_warnings(record=True) as wlog: # check too-small salt size self.assertRaises(ValueError, gen_salt, 0) self.assertRaises(ValueError, gen_salt, 1) self.consumeWarningList(wlog) # check correct salt size self.assertIn(gen_salt(2), salts2) self.assertIn(gen_salt(3), salts3) self.assertIn(gen_salt(4), salts4) self.consumeWarningList(wlog) # check too-large salt size self.assertRaises(ValueError, gen_salt, 5) self.consumeWarningList(wlog) self.assertIn(gen_salt(5, relaxed=True), salts4) self.consumeWarningList(wlog, ["salt too large"]) # test with max_salt_size=None del d1.max_salt_size with self.assertWarningList([]): self.assertEqual(len(gen_salt(None)), 3) self.assertEqual(len(gen_salt(5)), 5)
def test_91_bcrypt_padding(self): "test passlib correctly handles bcrypt padding bits" bcrypt = self.handler def check_warning(wlog): self.assertWarningMatches( wlog.pop(0), message_re= "^encountered a bcrypt hash with incorrectly set padding bits.*", ) self.assertFalse(wlog) def check_padding(hash): "check bcrypt hash doesn't have salt padding bits set" assert hash.startswith("$2a$") and len(hash) >= 28 self.assertTrue(hash[28] in BSLAST, "padding bits set in hash: %r" % (hash, )) #=============================================================== # test generated salts #=============================================================== from passlib.handlers.bcrypt import BCHARS, BSLAST # make sure genconfig & encrypt don't return bad hashes. # bug had 15/16 chance of occurring every time salt generated. # so we call it a few different way a number of times. for i in xrange(6): check_padding(bcrypt.genconfig()) for i in xrange(3): check_padding(bcrypt.encrypt("bob", rounds=bcrypt.min_rounds)) # check passing salt to genconfig causes it to be normalized. with catch_warnings(record=True) as wlog: warnings.simplefilter("always") hash = bcrypt.genconfig(salt="." * 21 + "A.") check_warning(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) hash = bcrypt.genconfig(salt="." * 23) self.assertFalse(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) #=============================================================== # test handling existing hashes #=============================================================== # 2 bits of salt padding set PASS1 = "loppux" BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" # all 4 bits of salt padding set PASS2 = "Passlib11" BAD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" GOOD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" # bad checksum padding PASS3 = "test" BAD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV" GOOD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" # make sure genhash() corrects input with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.genhash(PASS1, BAD1), GOOD1) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, BAD2), GOOD2) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, GOOD2), GOOD2) self.assertFalse(wlog) self.assertEqual(bcrypt.genhash(PASS3, BAD3), GOOD3) check_warning(wlog) self.assertFalse(wlog) # make sure verify works on both bad and good hashes with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertTrue(bcrypt.verify(PASS1, BAD1)) check_warning(wlog) self.assertTrue(bcrypt.verify(PASS1, GOOD1)) self.assertFalse(wlog) #=============================================================== # test normhash cleans things up correctly #=============================================================== with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.normhash(BAD1), GOOD1) self.assertEqual(bcrypt.normhash(BAD2), GOOD2) self.assertEqual(bcrypt.normhash(GOOD1), GOOD1) self.assertEqual(bcrypt.normhash(GOOD2), GOOD2) self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc")
def test_91_bcrypt_padding(self): "test passlib correctly handles bcrypt padding bits" bcrypt = self.handler def check_warning(wlog): self.assertWarningMatches(wlog.pop(0), message_re="^encountered a bcrypt hash with incorrectly set padding bits.*", ) self.assertFalse(wlog) def check_padding(hash): "check bcrypt hash doesn't have salt padding bits set" assert hash.startswith("$2a$") and len(hash) >= 28 self.assertTrue(hash[28] in BSLAST, "padding bits set in hash: %r" % (hash,)) #=============================================================== # test generated salts #=============================================================== from passlib.handlers.bcrypt import BCHARS, BSLAST # make sure genconfig & encrypt don't return bad hashes. # bug had 15/16 chance of occurring every time salt generated. # so we call it a few different way a number of times. for i in xrange(6): check_padding(bcrypt.genconfig()) for i in xrange(3): check_padding(bcrypt.encrypt("bob", rounds=bcrypt.min_rounds)) # check passing salt to genconfig causes it to be normalized. with catch_warnings(record=True) as wlog: warnings.simplefilter("always") hash = bcrypt.genconfig(salt="."*21 + "A.") check_warning(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) hash = bcrypt.genconfig(salt="."*23) self.assertFalse(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) #=============================================================== # test handling existing hashes #=============================================================== # 2 bits of salt padding set PASS1 = "loppux" BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" # all 4 bits of salt padding set PASS2 = "Passlib11" BAD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" GOOD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" # bad checksum padding PASS3 = "test" BAD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV" GOOD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" # make sure genhash() corrects input with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.genhash(PASS1, BAD1), GOOD1) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, BAD2), GOOD2) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, GOOD2), GOOD2) self.assertFalse(wlog) self.assertEqual(bcrypt.genhash(PASS3, BAD3), GOOD3) check_warning(wlog) self.assertFalse(wlog) # make sure verify works on both bad and good hashes with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertTrue(bcrypt.verify(PASS1, BAD1)) check_warning(wlog) self.assertTrue(bcrypt.verify(PASS1, GOOD1)) self.assertFalse(wlog) #=============================================================== # test normhash cleans things up correctly #=============================================================== with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.normhash(BAD1), GOOD1) self.assertEqual(bcrypt.normhash(BAD2), GOOD2) self.assertEqual(bcrypt.normhash(GOOD1), GOOD1) self.assertEqual(bcrypt.normhash(GOOD2), GOOD2) self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc")