1"""passlib.tests.test_handlers - tests for passlib hash algorithms""" 2#============================================================================= 3# imports 4#============================================================================= 5from __future__ import with_statement 6# core 7import logging; log = logging.getLogger(__name__) 8import os 9import warnings 10# site 11# pkg 12from passlib import hash 13from passlib.handlers.bcrypt import IDENT_2, IDENT_2X 14from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input 15from passlib.utils.compat import irange, PY3 16from passlib.tests.utils import HandlerCase, TEST_MODE 17from passlib.tests.test_handlers import UPASS_TABLE 18# module 19 20#============================================================================= 21# bcrypt 22#============================================================================= 23class _bcrypt_test(HandlerCase): 24 """base for BCrypt test cases""" 25 handler = hash.bcrypt 26 reduce_default_rounds = True 27 fuzz_salts_need_bcrypt_repair = True 28 29 known_correct_hashes = [ 30 # 31 # from JTR 1.7.9 32 # 33 ('U*U*U*U*', '$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O'), 34 ('U*U***U', '$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq'), 35 ('U*U***U*', '$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi'), 36 ('*U*U*U*U', '$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2'), 37 ('', '$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO'), 38 39 # 40 # test vectors from http://www.openwall.com/crypt v1.2 41 # note that this omits any hashes that depend on crypt_blowfish's 42 # various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password, 43 # and any 2x hashes); and only contain hashes which are correct 44 # under both crypt_blowfish 1.2 AND OpenBSD. 45 # 46 ('U*U', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW'), 47 ('U*U*', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK'), 48 ('U*U*U', '$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a'), 49 ('', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'), 50 ('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 51 '0123456789chars after 72 are ignored', 52 '$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui'), 53 (b'\xa3', 54 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), 55 (b'\xff\xa3345', 56 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e'), 57 (b'\xa3ab', 58 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS'), 59 (b'\xaa'*72 + b'chars after 72 are ignored as usual', 60 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6'), 61 (b'\xaa\x55'*36, 62 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy'), 63 (b'\x55\xaa\xff'*24, 64 '$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe'), 65 66 # keeping one of their 2y tests, because we are supporting that. 67 (b'\xa3', 68 '$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), 69 70 # 71 # 8bit bug (fixed in 2y/2b) 72 # 73 74 # NOTE: see assert_lacks_8bit_bug() for origins of this test vector. 75 (b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"), 76 77 # 78 # bsd wraparound bug (fixed in 2b) 79 # 80 81 # NOTE: if backend is vulnerable, password will hash the same as '0'*72 82 # ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"), 83 # rather than same as ("0123456789"*8)[:72] 84 # 255 should be sufficient, but checking 85 (("0123456789"*26)[:254], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), 86 (("0123456789"*26)[:255], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), 87 (("0123456789"*26)[:256], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), 88 (("0123456789"*26)[:257], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'), 89 90 91 # 92 # from py-bcrypt tests 93 # 94 ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), 95 ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'), 96 ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'), 97 ('abcdefghijklmnopqrstuvwxyz', 98 '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'), 99 ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', 100 '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'), 101 102 # 103 # custom test vectors 104 # 105 106 # ensures utf-8 used for unicode 107 (UPASS_TABLE, 108 '$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), 109 110 # ensure 2b support 111 (UPASS_TABLE, 112 '$2b$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), 113 114 ] 115 116 if TEST_MODE("full"): 117 # 118 # add some extra tests related to 2/2a 119 # 120 CONFIG_2 = '$2$05$' + '.'*22 121 CONFIG_A = '$2a$05$' + '.'*22 122 known_correct_hashes.extend([ 123 ("", CONFIG_2 + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), 124 ("", CONFIG_A + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), 125 ("abc", CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 126 ("abc", CONFIG_A + 'ev6gDwpVye3oMCUpLY85aTpfBNHD0Ga'), 127 ("abc"*23, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 128 ("abc"*23, CONFIG_A + '2kIdfSj/4/R/Q6n847VTvc68BXiRYZC'), 129 ("abc"*24, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 130 ("abc"*24, CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 131 ("abc"*24+'x', CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 132 ("abc"*24+'x', CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), 133 ]) 134 135 known_correct_configs = [ 136 ('$2a$04$uM6csdM8R9SXTex/gbTaye', UPASS_TABLE, 137 '$2a$04$uM6csdM8R9SXTex/gbTayezuvzFEufYGd2uB6of7qScLjQ4GwcD4G'), 138 ] 139 140 known_unidentified_hashes = [ 141 # invalid minor version 142 "$2f$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", 143 "$2`$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", 144 ] 145 146 known_malformed_hashes = [ 147 # bad char in otherwise correct hash 148 # \/ 149 "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", 150 151 # unsupported (but recognized) minor version 152 "$2x$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", 153 154 # rounds not zero-padded (py-bcrypt rejects this, therefore so do we) 155 '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.' 156 157 # NOTE: salts with padding bits set are technically malformed, 158 # but we can reliably correct & issue a warning for that. 159 ] 160 161 platform_crypt_support = [ 162 ("freedbsd|openbsd|netbsd", True), 163 ("darwin", False), 164 ("linux", None), # may be present via addon, e.g. debian's libpam-unix2 165 ("solaris", None), # depends on system policy 166 ] 167 168 #=================================================================== 169 # override some methods 170 #=================================================================== 171 def setUp(self): 172 # ensure builtin is enabled for duration of test. 173 if TEST_MODE("full") and self.backend == "builtin": 174 key = "PASSLIB_BUILTIN_BCRYPT" 175 orig = os.environ.get(key) 176 if orig: 177 self.addCleanup(os.environ.__setitem__, key, orig) 178 else: 179 self.addCleanup(os.environ.__delitem__, key) 180 os.environ[key] = "true" 181 182 super(_bcrypt_test, self).setUp() 183 184 # silence this warning, will come up a bunch during testing of old 2a hashes. 185 warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") 186 187 def populate_settings(self, kwds): 188 # builtin is still just way too slow. 189 if self.backend == "builtin": 190 kwds.setdefault("rounds", 4) 191 super(_bcrypt_test, self).populate_settings(kwds) 192 193 #=================================================================== 194 # fuzz testing 195 #=================================================================== 196 def crypt_supports_variant(self, hash): 197 """check if OS crypt is expected to support given ident""" 198 from passlib.handlers.bcrypt import bcrypt, IDENT_2X, IDENT_2Y 199 from passlib.utils import safe_crypt 200 ident = bcrypt.from_string(hash) 201 return (safe_crypt("test", ident + "04$5BJqKfqMQvV7nS.yUguNcu") or "").startswith(ident) 202 203 fuzz_verifiers = HandlerCase.fuzz_verifiers + ( 204 "fuzz_verifier_bcrypt", 205 "fuzz_verifier_pybcrypt", 206 "fuzz_verifier_bcryptor", 207 ) 208 209 def fuzz_verifier_bcrypt(self): 210 # test against bcrypt, if available 211 from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, _detect_pybcrypt 212 from passlib.utils import to_native_str, to_bytes 213 try: 214 import bcrypt 215 except ImportError: 216 return 217 if _detect_pybcrypt(): 218 return 219 def check_bcrypt(secret, hash): 220 """bcrypt""" 221 secret = to_bytes(secret, self.FuzzHashGenerator.password_encoding) 222 if hash.startswith(IDENT_2B): 223 # bcrypt <1.1 lacks 2B support 224 hash = IDENT_2A + hash[4:] 225 elif hash.startswith(IDENT_2): 226 # bcrypt doesn't support $2$ hashes; but we can fake it 227 # using the $2a$ algorithm, by repeating the password until 228 # it's 72 chars in length. 229 hash = IDENT_2A + hash[3:] 230 if secret: 231 secret = repeat_string(secret, 72) 232 elif hash.startswith(IDENT_2Y) and bcrypt.__version__ == "3.0.0": 233 hash = IDENT_2B + hash[4:] 234 hash = to_bytes(hash) 235 try: 236 return bcrypt.hashpw(secret, hash) == hash 237 except ValueError: 238 raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret)) 239 return check_bcrypt 240 241 def fuzz_verifier_pybcrypt(self): 242 # test against py-bcrypt, if available 243 from passlib.handlers.bcrypt import ( 244 IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, 245 _PyBcryptBackend, 246 ) 247 from passlib.utils import to_native_str 248 249 loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False) 250 if not loaded: 251 return 252 253 from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod 254 255 lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2 256 257 def check_pybcrypt(secret, hash): 258 """pybcrypt""" 259 secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) 260 if len(secret) > 200: # vulnerable to wraparound bug 261 secret = secret[:200] 262 if hash.startswith((IDENT_2B, IDENT_2Y)): 263 hash = IDENT_2A + hash[4:] 264 try: 265 if lock: 266 with lock: 267 return bcrypt_mod.hashpw(secret, hash) == hash 268 else: 269 return bcrypt_mod.hashpw(secret, hash) == hash 270 except ValueError: 271 raise ValueError("py-bcrypt rejected hash: %r" % (hash,)) 272 return check_pybcrypt 273 274 def fuzz_verifier_bcryptor(self): 275 # test against bcryptor if available 276 from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y, IDENT_2B 277 from passlib.utils import to_native_str 278 try: 279 from bcryptor.engine import Engine 280 except ImportError: 281 return 282 def check_bcryptor(secret, hash): 283 """bcryptor""" 284 secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) 285 if hash.startswith((IDENT_2B, IDENT_2Y)): 286 hash = IDENT_2A + hash[4:] 287 elif hash.startswith(IDENT_2): 288 # bcryptor doesn't support $2$ hashes; but we can fake it 289 # using the $2a$ algorithm, by repeating the password until 290 # it's 72 chars in length. 291 hash = IDENT_2A + hash[3:] 292 if secret: 293 secret = repeat_string(secret, 72) 294 return Engine(False).hash_key(secret, hash) == hash 295 return check_bcryptor 296 297 class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): 298 299 def generate(self): 300 opts = super(_bcrypt_test.FuzzHashGenerator, self).generate() 301 302 secret = opts['secret'] 303 other = opts['other'] 304 settings = opts['settings'] 305 ident = settings.get('ident') 306 307 if ident == IDENT_2X: 308 # 2x is just recognized, not supported. don't test with it. 309 del settings['ident'] 310 311 elif ident == IDENT_2 and other and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret): 312 # avoid false failure due to flaw in 0-revision bcrypt: 313 # repeated strings like 'abc' and 'abcabc' hash identically. 314 opts['secret'], opts['other'] = self.random_password_pair() 315 316 return opts 317 318 def random_rounds(self): 319 # decrease default rounds for fuzz testing to speed up volume. 320 return self.randintgauss(5, 8, 6, 1) 321 322 #=================================================================== 323 # custom tests 324 #=================================================================== 325 known_incorrect_padding = [ 326 # password, bad hash, good hash 327 328 # 2 bits of salt padding set 329# ("loppux", # \/ 330# "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C", 331# "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C"), 332 ("test", # \/ 333 '$2a$04$oaQbBqq8JnSM1NHRPQGXORY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO', 334 '$2a$04$oaQbBqq8JnSM1NHRPQGXOOY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO'), 335 336 # all 4 bits of salt padding set 337# ("Passlib11", # \/ 338# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK", 339# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK"), 340 ("test", # \/ 341 "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS", 342 "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), 343 344 # bad checksum padding 345 ("test", # \/ 346 "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV", 347 "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), 348 ] 349 350 def test_90_bcrypt_padding(self): 351 """test passlib correctly handles bcrypt padding bits""" 352 self.require_TEST_MODE("full") 353 # 354 # prevents reccurrence of issue 25 (https://code.google.com/p/passlib/issues/detail?id=25) 355 # were some unused bits were incorrectly set in bcrypt salt strings. 356 # (fixed since 1.5.3) 357 # 358 bcrypt = self.handler 359 corr_desc = ".*incorrectly set padding bits" 360 361 # 362 # test hash() / genconfig() don't generate invalid salts anymore 363 # 364 def check_padding(hash): 365 assert hash.startswith(("$2a$", "$2b$")) and len(hash) >= 28, \ 366 "unexpectedly malformed hash: %r" % (hash,) 367 self.assertTrue(hash[28] in '.Oeu', 368 "unused bits incorrectly set in hash: %r" % (hash,)) 369 for i in irange(6): 370 check_padding(bcrypt.genconfig()) 371 for i in irange(3): 372 check_padding(bcrypt.using(rounds=bcrypt.min_rounds).hash("bob")) 373 374 # 375 # test genconfig() corrects invalid salts & issues warning. 376 # 377 with self.assertWarningList(["salt too large", corr_desc]): 378 hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True) 379 self.assertEqual(hash, "$2b$05$" + "." * (22 + 31)) 380 381 # 382 # test public methods against good & bad hashes 383 # 384 samples = self.known_incorrect_padding 385 for pwd, bad, good in samples: 386 387 # make sure genhash() corrects bad configs, leaves good unchanged 388 with self.assertWarningList([corr_desc]): 389 self.assertEqual(bcrypt.genhash(pwd, bad), good) 390 with self.assertWarningList([]): 391 self.assertEqual(bcrypt.genhash(pwd, good), good) 392 393 # make sure verify() works correctly with good & bad hashes 394 with self.assertWarningList([corr_desc]): 395 self.assertTrue(bcrypt.verify(pwd, bad)) 396 with self.assertWarningList([]): 397 self.assertTrue(bcrypt.verify(pwd, good)) 398 399 # make sure normhash() corrects bad hashes, leaves good unchanged 400 with self.assertWarningList([corr_desc]): 401 self.assertEqual(bcrypt.normhash(bad), good) 402 with self.assertWarningList([]): 403 self.assertEqual(bcrypt.normhash(good), good) 404 405 # make sure normhash() leaves non-bcrypt hashes alone 406 self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc") 407 408 def test_needs_update_w_padding(self): 409 """needs_update corrects bcrypt padding""" 410 # NOTE: see padding test above for details about issue this detects 411 bcrypt = self.handler.using(rounds=4) 412 413 # PASS1 = "test" 414 # bad contains invalid 'c' char at end of salt: 415 # \/ 416 BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" 417 GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" 418 419 self.assertTrue(bcrypt.needs_update(BAD1)) 420 self.assertFalse(bcrypt.needs_update(GOOD1)) 421 422 #=================================================================== 423 # eoc 424 #=================================================================== 425 426# create test cases for specific backends 427bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt") 428bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt") 429bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor") 430 431class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")): 432 433 # os crypt doesn't support non-utf8 secret bytes 434 known_correct_hashes = [row for row in _bcrypt_test.known_correct_hashes 435 if is_safe_crypt_input(row[0])] 436 437 # os crypt backend doesn't currently implement a per-call fallback if it fails 438 has_os_crypt_fallback = False 439 440bcrypt_builtin_test = _bcrypt_test.create_backend_case("builtin") 441 442#============================================================================= 443# bcrypt 444#============================================================================= 445class _bcrypt_sha256_test(HandlerCase): 446 "base for BCrypt-SHA256 test cases" 447 handler = hash.bcrypt_sha256 448 reduce_default_rounds = True 449 forbidden_characters = None 450 fuzz_salts_need_bcrypt_repair = True 451 452 known_correct_hashes = [ 453 #------------------------------------------------------------------- 454 # custom test vectors for old v1 format 455 #------------------------------------------------------------------- 456 457 # empty 458 ("", 459 '$bcrypt-sha256$2a,5$E/e/2AOhqM5W/KJTFQzLce$F6dYSxOdAEoJZO2eoHUZWZljW/e0TXO'), 460 461 # ascii 462 ("password", 463 '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), 464 465 # unicode / utf8 466 (UPASS_TABLE, 467 '$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), 468 (UPASS_TABLE.encode("utf-8"), 469 '$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), 470 471 # ensure 2b support 472 ("password", 473 '$bcrypt-sha256$2b,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), 474 (UPASS_TABLE, 475 '$bcrypt-sha256$2b,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'), 476 477 # test >72 chars is hashed correctly -- under bcrypt these hash the same. 478 # NOTE: test_60_truncate_size() handles this already, this is just for overkill :) 479 (repeat_string("abc123", 72), 480 '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$r/hyEtqJ0teqPEmfTLoZ83ciAI1Q74.'), 481 (repeat_string("abc123", 72) + "qwr", 482 '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$021KLEif6epjot5yoxk0m8I0929ohEa'), 483 (repeat_string("abc123", 72) + "xyz", 484 '$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$7.1kgpHduMGEjvM3fX6e/QCvfn6OKja'), 485 486 #------------------------------------------------------------------- 487 # custom test vectors for v2 format 488 # TODO: convert to v2 format 489 #------------------------------------------------------------------- 490 491 # empty 492 ("", 493 '$bcrypt-sha256$v=2,t=2b,r=5$E/e/2AOhqM5W/KJTFQzLce$WFPIZKtDDTriqWwlmRFfHiOTeheAZWe'), 494 495 # ascii 496 ("password", 497 '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'), 498 499 # unicode / utf8 500 (UPASS_TABLE, 501 '$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'), 502 (UPASS_TABLE.encode("utf-8"), 503 '$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'), 504 505 # test >72 chars is hashed correctly -- under bcrypt these hash the same. 506 # NOTE: test_60_truncate_size() handles this already, this is just for overkill :) 507 (repeat_string("abc123", 72), 508 '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zu1cloESVFIOsUIo7fCEgkdHaI9SSue'), 509 (repeat_string("abc123", 72) + "qwr", 510 '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$CBF9csfEdW68xv3DwE6xSULXMtqEFP.'), 511 (repeat_string("abc123", 72) + "xyz", 512 '$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zC/1UDUG2ofEXB6Onr2vvyFzfhEOS3S'), 513 ] 514 515 known_correct_configs =[ 516 # v1 517 ('$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe', 518 "password", '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'), 519 # v2 520 ('$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe', 521 "password", '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'), 522 ] 523 524 known_malformed_hashes = [ 525 #------------------------------------------------------------------- 526 # v1 format 527 #------------------------------------------------------------------- 528 529 # bad char in otherwise correct hash 530 # \/ 531 '$bcrypt-sha256$2a,5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 532 533 # unrecognized bcrypt variant 534 '$bcrypt-sha256$2c,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 535 536 # unsupported bcrypt variant 537 '$bcrypt-sha256$2x,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 538 539 # rounds zero-padded 540 '$bcrypt-sha256$2a,05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 541 542 # config string w/ $ added 543 '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$', 544 545 #------------------------------------------------------------------- 546 # v2 format 547 #------------------------------------------------------------------- 548 549 # bad char in otherwise correct hash 550 # \/ 551 '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 552 553 # unsupported version (for this format) 554 '$bcrypt-sha256$v=1,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 555 556 # unrecognized version 557 '$bcrypt-sha256$v=3,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 558 559 # unrecognized bcrypt variant 560 '$bcrypt-sha256$v=2,t=2c,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 561 562 # unsupported bcrypt variant 563 '$bcrypt-sha256$v=2,t=2a,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 564 '$bcrypt-sha256$v=2,t=2x,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 565 566 # rounds zero-padded 567 '$bcrypt-sha256$v=2,t=2b,r=05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu', 568 569 # config string w/ $ added 570 '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$', 571 ] 572 573 #=================================================================== 574 # override some methods -- cloned from bcrypt 575 #=================================================================== 576 def setUp(self): 577 # ensure builtin is enabled for duration of test. 578 if TEST_MODE("full") and self.backend == "builtin": 579 key = "PASSLIB_BUILTIN_BCRYPT" 580 orig = os.environ.get(key) 581 if orig: 582 self.addCleanup(os.environ.__setitem__, key, orig) 583 else: 584 self.addCleanup(os.environ.__delitem__, key) 585 os.environ[key] = "enabled" 586 super(_bcrypt_sha256_test, self).setUp() 587 warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") 588 589 def populate_settings(self, kwds): 590 # builtin is still just way too slow. 591 if self.backend == "builtin": 592 kwds.setdefault("rounds", 4) 593 super(_bcrypt_sha256_test, self).populate_settings(kwds) 594 595 #=================================================================== 596 # override ident tests for now 597 #=================================================================== 598 599 def require_many_idents(self): 600 raise self.skipTest("multiple idents not supported") 601 602 def test_30_HasOneIdent(self): 603 # forbidding ident keyword, we only support "2b" for now 604 handler = self.handler 605 handler(use_defaults=True) 606 self.assertRaises(ValueError, handler, ident="$2y$", use_defaults=True) 607 608 #=================================================================== 609 # fuzz testing -- cloned from bcrypt 610 #=================================================================== 611 612 class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): 613 614 def random_rounds(self): 615 # decrease default rounds for fuzz testing to speed up volume. 616 return self.randintgauss(5, 8, 6, 1) 617 618 def random_ident(self): 619 return "2b" 620 621 #=================================================================== 622 # custom tests 623 #=================================================================== 624 625 def test_using_version(self): 626 # default to v2 627 handler = self.handler 628 self.assertEqual(handler.version, 2) 629 630 # allow v1 explicitly 631 subcls = handler.using(version=1) 632 self.assertEqual(subcls.version, 1) 633 634 # forbid unknown ver 635 self.assertRaises(ValueError, handler.using, version=999) 636 637 # allow '2a' only for v1 638 subcls = handler.using(version=1, ident="2a") 639 self.assertRaises(ValueError, handler.using, ident="2a") 640 641 def test_calc_digest_v2(self): 642 """ 643 test digest calc v2 matches bcrypt() 644 """ 645 from passlib.hash import bcrypt 646 from passlib.crypto.digest import compile_hmac 647 from passlib.utils.binary import b64encode 648 649 # manually calc intermediary digest 650 salt = "nyKYxTAvjmy6lMDYMl11Uu" 651 secret = "test" 652 temp_digest = compile_hmac("sha256", salt.encode("ascii"))(secret.encode("ascii")) 653 temp_digest = b64encode(temp_digest).decode("ascii") 654 self.assertEqual(temp_digest, "J5TlyIDm+IcSWmKiDJm+MeICndBkFVPn4kKdJW8f+xY=") 655 656 # manually final hash from intermediary 657 # XXX: genhash() could be useful here 658 bcrypt_digest = bcrypt(ident="2b", salt=salt, rounds=12)._calc_checksum(temp_digest) 659 self.assertEqual(bcrypt_digest, "M0wE0Ov/9LXoQFCe.jRHu3MSHPF54Ta") 660 self.assertTrue(bcrypt.verify(temp_digest, "$2b$12$" + salt + bcrypt_digest)) 661 662 # confirm handler outputs same thing. 663 # XXX: genhash() could be useful here 664 result = self.handler(ident="2b", salt=salt, rounds=12)._calc_checksum(secret) 665 self.assertEqual(result, bcrypt_digest) 666 667 #=================================================================== 668 # eoc 669 #=================================================================== 670 671# create test cases for specific backends 672bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt") 673bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt") 674bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor") 675 676class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")): 677 678 @classmethod 679 def _get_safe_crypt_handler_backend(cls): 680 return bcrypt_os_crypt_test._get_safe_crypt_handler_backend() 681 682 has_os_crypt_fallback = False 683 684bcrypt_sha256_builtin_test = _bcrypt_sha256_test.create_backend_case("builtin") 685 686#============================================================================= 687# eof 688#============================================================================= 689