You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					689 lines
				
				29 KiB
			
		
		
			
		
	
	
					689 lines
				
				29 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""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
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |