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.
		
		
		
		
		
			
		
			
				
					535 lines
				
				21 KiB
			
		
		
			
		
	
	
					535 lines
				
				21 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.handlers.sha2_crypt - SHA256-Crypt / SHA512-Crypt"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								import hashlib
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.utils import safe_crypt, test_crypt, \
							 | 
						||
| 
								 | 
							
								                          repeat_string, to_unicode
							 | 
						||
| 
								 | 
							
								from passlib.utils.binary import h64
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import byte_elem_value, u, \
							 | 
						||
| 
								 | 
							
								                                 uascii_to_str, unicode
							 | 
						||
| 
								 | 
							
								import passlib.utils.handlers as uh
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "sha512_crypt",
							 | 
						||
| 
								 | 
							
								    "sha256_crypt",
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# pure-python backend, used by both sha256_crypt & sha512_crypt
							 | 
						||
| 
								 | 
							
								# when crypt.crypt() backend is not available.
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_BNULL = b'\x00'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# pre-calculated offsets used to speed up C digest stage (see notes below).
							 | 
						||
| 
								 | 
							
								# sequence generated using the following:
							 | 
						||
| 
								 | 
							
								    ##perms_order = "p,pp,ps,psp,sp,spp".split(",")
							 | 
						||
| 
								 | 
							
								    ##def offset(i):
							 | 
						||
| 
								 | 
							
								    ##    key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
							 | 
						||
| 
								 | 
							
								    ##        ("p" if i % 7 else "") + ("" if i % 2 else "p"))
							 | 
						||
| 
								 | 
							
								    ##    return perms_order.index(key)
							 | 
						||
| 
								 | 
							
								    ##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
							 | 
						||
| 
								 | 
							
								_c_digest_offsets = (
							 | 
						||
| 
								 | 
							
								    (0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
							 | 
						||
| 
								 | 
							
								    (4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
							 | 
						||
| 
								 | 
							
								    (4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# map used to transpose bytes when encoding final sha256_crypt digest
							 | 
						||
| 
								 | 
							
								_256_transpose_map = (
							 | 
						||
| 
								 | 
							
								    20, 10,  0, 11,  1, 21,  2, 22, 12, 23, 13,  3, 14,  4, 24,  5,
							 | 
						||
| 
								 | 
							
								    25, 15, 26, 16,  6, 17,  7, 27,  8, 28, 18, 29, 19,  9, 30, 31,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# map used to transpose bytes when encoding final sha512_crypt digest
							 | 
						||
| 
								 | 
							
								_512_transpose_map = (
							 | 
						||
| 
								 | 
							
								    42, 21,  0,  1, 43, 22, 23,  2, 44, 45, 24,  3,  4, 46, 25, 26,
							 | 
						||
| 
								 | 
							
								     5, 47, 48, 27,  6,  7, 49, 28, 29,  8, 50, 51, 30,  9, 10, 52,
							 | 
						||
| 
								 | 
							
								    31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15,
							 | 
						||
| 
								 | 
							
								    16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _raw_sha2_crypt(pwd, salt, rounds, use_512=False):
							 | 
						||
| 
								 | 
							
								    """perform raw sha256-crypt / sha512-crypt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this function provides a pure-python implementation of the internals
							 | 
						||
| 
								 | 
							
								    for the SHA256-Crypt and SHA512-Crypt algorithms; it doesn't
							 | 
						||
| 
								 | 
							
								    handle any of the parsing/validation of the hash strings themselves.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg pwd: password chars/bytes to hash
							 | 
						||
| 
								 | 
							
								    :arg salt: salt chars to use
							 | 
						||
| 
								 | 
							
								    :arg rounds: linear rounds cost
							 | 
						||
| 
								 | 
							
								    :arg use_512: use sha512-crypt instead of sha256-crypt mode
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        encoded checksum chars
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # init & validate inputs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: the setup portion of this algorithm scales ~linearly in time
							 | 
						||
| 
								 | 
							
								    #       with the size of the password, making it vulnerable to a DOS from
							 | 
						||
| 
								 | 
							
								    #       unreasonably large inputs. the following code has some optimizations
							 | 
						||
| 
								 | 
							
								    #       which would make things even worse, using O(pwd_len**2) memory
							 | 
						||
| 
								 | 
							
								    #       when calculating digest P. 
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    #       to mitigate these two issues: 1) this code switches to a 
							 | 
						||
| 
								 | 
							
								    #       O(pwd_len)-memory algorithm for passwords that are much larger 
							 | 
						||
| 
								 | 
							
								    #       than average, and 2) Passlib enforces a library-wide max limit on
							 | 
						||
| 
								 | 
							
								    #       the size of passwords it will allow, to prevent this algorithm and 
							 | 
						||
| 
								 | 
							
								    #       others from being DOSed in this way (see passlib.exc.PasswordSizeError
							 | 
						||
| 
								 | 
							
								    #       for details).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate secret
							 | 
						||
| 
								 | 
							
								    if isinstance(pwd, unicode):
							 | 
						||
| 
								 | 
							
								        # XXX: not sure what official unicode policy is, using this as default
							 | 
						||
| 
								 | 
							
								        pwd = pwd.encode("utf-8")
							 | 
						||
| 
								 | 
							
								    assert isinstance(pwd, bytes)
							 | 
						||
| 
								 | 
							
								    if _BNULL in pwd:
							 | 
						||
| 
								 | 
							
								        raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt)
							 | 
						||
| 
								 | 
							
								    pwd_len = len(pwd)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate rounds
							 | 
						||
| 
								 | 
							
								    assert 1000 <= rounds <= 999999999, "invalid rounds"
							 | 
						||
| 
								 | 
							
								        # NOTE: spec says out-of-range rounds should be clipped, instead of
							 | 
						||
| 
								 | 
							
								        # causing an error. this function assumes that's been taken care of
							 | 
						||
| 
								 | 
							
								        # by the handler class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate salt
							 | 
						||
| 
								 | 
							
								    assert isinstance(salt, unicode), "salt not unicode"
							 | 
						||
| 
								 | 
							
								    salt = salt.encode("ascii")
							 | 
						||
| 
								 | 
							
								    salt_len = len(salt)
							 | 
						||
| 
								 | 
							
								    assert salt_len < 17, "salt too large"
							 | 
						||
| 
								 | 
							
								        # NOTE: spec says salts larger than 16 bytes should be truncated,
							 | 
						||
| 
								 | 
							
								        # instead of causing an error. this function assumes that's been
							 | 
						||
| 
								 | 
							
								        # taken care of by the handler class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # load sha256/512 specific constants
							 | 
						||
| 
								 | 
							
								    if use_512:
							 | 
						||
| 
								 | 
							
								        hash_const = hashlib.sha512
							 | 
						||
| 
								 | 
							
								        transpose_map = _512_transpose_map
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        hash_const = hashlib.sha256
							 | 
						||
| 
								 | 
							
								        transpose_map = _256_transpose_map
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # digest B - used as subinput to digest A
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    db = hash_const(pwd + salt + pwd).digest()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # digest A - used to initialize first round of digest C
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # start out with pwd + salt
							 | 
						||
| 
								 | 
							
								    a_ctx = hash_const(pwd + salt)
							 | 
						||
| 
								 | 
							
								    a_ctx_update = a_ctx.update
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # add pwd_len bytes of b, repeating b as many times as needed.
							 | 
						||
| 
								 | 
							
								    a_ctx_update(repeat_string(db, pwd_len))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # for each bit in pwd_len: add b if it's 1, or pwd if it's 0
							 | 
						||
| 
								 | 
							
								    i = pwd_len
							 | 
						||
| 
								 | 
							
								    while i:
							 | 
						||
| 
								 | 
							
								        a_ctx_update(db if i & 1 else pwd)
							 | 
						||
| 
								 | 
							
								        i >>= 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # finish A
							 | 
						||
| 
								 | 
							
								    da = a_ctx.digest()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # digest P from password - used instead of password itself
							 | 
						||
| 
								 | 
							
								    #                          when calculating digest C.
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    if pwd_len < 96:
							 | 
						||
| 
								 | 
							
								        # this method is faster under python, but uses O(pwd_len**2) memory;
							 | 
						||
| 
								 | 
							
								        # so we don't use it for larger passwords to avoid a potential DOS.
							 | 
						||
| 
								 | 
							
								        dp = repeat_string(hash_const(pwd * pwd_len).digest(), pwd_len)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        # this method is slower under python, but uses a fixed amount of memory.
							 | 
						||
| 
								 | 
							
								        tmp_ctx = hash_const(pwd)
							 | 
						||
| 
								 | 
							
								        tmp_ctx_update = tmp_ctx.update
							 | 
						||
| 
								 | 
							
								        i = pwd_len-1
							 | 
						||
| 
								 | 
							
								        while i:
							 | 
						||
| 
								 | 
							
								            tmp_ctx_update(pwd)
							 | 
						||
| 
								 | 
							
								            i -= 1
							 | 
						||
| 
								 | 
							
								        dp = repeat_string(tmp_ctx.digest(), pwd_len)
							 | 
						||
| 
								 | 
							
								    assert len(dp) == pwd_len
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # digest S  - used instead of salt itself when calculating digest C
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    ds = hash_const(salt * (16 + byte_elem_value(da[0]))).digest()[:salt_len]
							 | 
						||
| 
								 | 
							
								    assert len(ds) == salt_len, "salt_len somehow > hash_len!"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # digest C - for a variable number of rounds, combine A, S, and P
							 | 
						||
| 
								 | 
							
								    #            digests in various ways; in order to burn CPU time.
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: the original SHA256/512-Crypt specification performs the C digest
							 | 
						||
| 
								 | 
							
								    # calculation using the following loop:
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    ##dc = da
							 | 
						||
| 
								 | 
							
								    ##i = 0
							 | 
						||
| 
								 | 
							
								    ##while i < rounds:
							 | 
						||
| 
								 | 
							
								    ##    tmp_ctx = hash_const(dp if i & 1 else dc)
							 | 
						||
| 
								 | 
							
								    ##    if i % 3:
							 | 
						||
| 
								 | 
							
								    ##        tmp_ctx.update(ds)
							 | 
						||
| 
								 | 
							
								    ##    if i % 7:
							 | 
						||
| 
								 | 
							
								    ##        tmp_ctx.update(dp)
							 | 
						||
| 
								 | 
							
								    ##    tmp_ctx.update(dc if i & 1 else dp)
							 | 
						||
| 
								 | 
							
								    ##    dc = tmp_ctx.digest()
							 | 
						||
| 
								 | 
							
								    ##    i += 1
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # The code Passlib uses (below) implements an equivalent algorithm,
							 | 
						||
| 
								 | 
							
								    # it's just been heavily optimized to pre-calculate a large number
							 | 
						||
| 
								 | 
							
								    # of things beforehand. It works off of a couple of observations
							 | 
						||
| 
								 | 
							
								    # about the original algorithm:
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # 1. each round is a combination of 'dc', 'ds', and 'dp'; determined
							 | 
						||
| 
								 | 
							
								    #    by the whether 'i' a multiple of 2,3, and/or 7.
							 | 
						||
| 
								 | 
							
								    # 2. since lcm(2,3,7)==42, the series of combinations will repeat
							 | 
						||
| 
								 | 
							
								    #    every 42 rounds.
							 | 
						||
| 
								 | 
							
								    # 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
							 | 
						||
| 
								 | 
							
								    #    while odd rounds 1-41 consist of hash(round-specific-constant + dc)
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Using these observations, the following code...
							 | 
						||
| 
								 | 
							
								    # * calculates the round-specific combination of ds & dp for each round 0-41
							 | 
						||
| 
								 | 
							
								    # * runs through as many 42-round blocks as possible
							 | 
						||
| 
								 | 
							
								    # * runs through as many pairs of rounds as possible for remaining rounds
							 | 
						||
| 
								 | 
							
								    # * performs once last round if the total rounds should be odd.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # this cuts out a lot of the control overhead incurred when running the
							 | 
						||
| 
								 | 
							
								    # original loop 40,000+ times in python, resulting in ~20% increase in
							 | 
						||
| 
								 | 
							
								    # speed under CPython (though still 2x slower than glibc crypt)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # prepare the 6 combinations of ds & dp which are needed
							 | 
						||
| 
								 | 
							
								    # (order of 'perms' must match how _c_digest_offsets was generated)
							 | 
						||
| 
								 | 
							
								    dp_dp = dp+dp
							 | 
						||
| 
								 | 
							
								    dp_ds = dp+ds
							 | 
						||
| 
								 | 
							
								    perms = [dp, dp_dp, dp_ds, dp_ds+dp, ds+dp, ds+dp_dp]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # build up list of even-round & odd-round constants,
							 | 
						||
| 
								 | 
							
								    # and store in 21-element list as (even,odd) pairs.
							 | 
						||
| 
								 | 
							
								    data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # perform as many full 42-round blocks as possible
							 | 
						||
| 
								 | 
							
								    dc = da
							 | 
						||
| 
								 | 
							
								    blocks, tail = divmod(rounds, 42)
							 | 
						||
| 
								 | 
							
								    while blocks:
							 | 
						||
| 
								 | 
							
								        for even, odd in data:
							 | 
						||
| 
								 | 
							
								            dc = hash_const(odd + hash_const(dc + even).digest()).digest()
							 | 
						||
| 
								 | 
							
								        blocks -= 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # perform any leftover rounds
							 | 
						||
| 
								 | 
							
								    if tail:
							 | 
						||
| 
								 | 
							
								        # perform any pairs of rounds
							 | 
						||
| 
								 | 
							
								        pairs = tail>>1
							 | 
						||
| 
								 | 
							
								        for even, odd in data[:pairs]:
							 | 
						||
| 
								 | 
							
								            dc = hash_const(odd + hash_const(dc + even).digest()).digest()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # if rounds was odd, do one last round (since we started at 0,
							 | 
						||
| 
								 | 
							
								        # last round will be an even-numbered round)
							 | 
						||
| 
								 | 
							
								        if tail & 1:
							 | 
						||
| 
								 | 
							
								            dc = hash_const(dc + data[pairs][0]).digest()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # encode digest using appropriate transpose map
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    return h64.encode_transposed_bytes(dc, transpose_map).decode("ascii")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# handlers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_UROUNDS = u("rounds=")
							 | 
						||
| 
								 | 
							
								_UDOLLAR = u("$")
							 | 
						||
| 
								 | 
							
								_UZERO = u("0")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
							 | 
						||
| 
								 | 
							
								                   uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """class containing common code shared by sha256_crypt & sha512_crypt"""
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # name - set by subclass
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size")
							 | 
						||
| 
								 | 
							
								    # ident - set by subclass
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								    # checksum_size - set by subclass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    max_salt_size = 16
							 | 
						||
| 
								 | 
							
								    salt_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    min_rounds = 1000 # bounds set by spec
							 | 
						||
| 
								 | 
							
								    max_rounds = 999999999 # bounds set by spec
							 | 
						||
| 
								 | 
							
								    rounds_cost = "linear"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _cdb_use_512 = False # flag for _calc_digest_builtin()
							 | 
						||
| 
								 | 
							
								    _rounds_prefix = None # ident + _UROUNDS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # methods
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    implicit_rounds = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, implicit_rounds=None, **kwds):
							 | 
						||
| 
								 | 
							
								        super(_SHA2_Common, self).__init__(**kwds)
							 | 
						||
| 
								 | 
							
								        # if user calls hash() w/ 5000 rounds, default to compact form.
							 | 
						||
| 
								 | 
							
								        if implicit_rounds is None:
							 | 
						||
| 
								 | 
							
								            implicit_rounds = (self.use_defaults and self.rounds == 5000)
							 | 
						||
| 
								 | 
							
								        self.implicit_rounds = implicit_rounds
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _parse_salt(self, salt):
							 | 
						||
| 
								 | 
							
								        # required per SHA2-crypt spec -- truncate config salts rather than throwing error
							 | 
						||
| 
								 | 
							
								        return self._norm_salt(salt, relaxed=self.checksum is None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _parse_rounds(self, rounds):
							 | 
						||
| 
								 | 
							
								        # required per SHA2-crypt spec -- clip config rounds rather than throwing error
							 | 
						||
| 
								 | 
							
								        return self._norm_rounds(rounds, relaxed=self.checksum is None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        # basic format this parses -
							 | 
						||
| 
								 | 
							
								        # $5$[rounds=<rounds>$]<salt>[$<checksum>]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # TODO: this *could* use uh.parse_mc3(), except that the rounds
							 | 
						||
| 
								 | 
							
								        # portion has a slightly different grammar.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # convert to unicode, check for ident prefix, split on dollar signs.
							 | 
						||
| 
								 | 
							
								        hash = to_unicode(hash, "ascii", "hash")
							 | 
						||
| 
								 | 
							
								        ident = cls.ident
							 | 
						||
| 
								 | 
							
								        if not hash.startswith(ident):
							 | 
						||
| 
								 | 
							
								            raise uh.exc.InvalidHashError(cls)
							 | 
						||
| 
								 | 
							
								        assert len(ident) == 3
							 | 
						||
| 
								 | 
							
								        parts = hash[3:].split(_UDOLLAR)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # extract rounds value
							 | 
						||
| 
								 | 
							
								        if parts[0].startswith(_UROUNDS):
							 | 
						||
| 
								 | 
							
								            assert len(_UROUNDS) == 7
							 | 
						||
| 
								 | 
							
								            rounds = parts.pop(0)[7:]
							 | 
						||
| 
								 | 
							
								            if rounds.startswith(_UZERO) and rounds != _UZERO:
							 | 
						||
| 
								 | 
							
								                raise uh.exc.ZeroPaddedRoundsError(cls)
							 | 
						||
| 
								 | 
							
								            rounds = int(rounds)
							 | 
						||
| 
								 | 
							
								            implicit_rounds = False
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            rounds = 5000
							 | 
						||
| 
								 | 
							
								            implicit_rounds = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # rest should be salt and checksum
							 | 
						||
| 
								 | 
							
								        if len(parts) == 2:
							 | 
						||
| 
								 | 
							
								            salt, chk = parts
							 | 
						||
| 
								 | 
							
								        elif len(parts) == 1:
							 | 
						||
| 
								 | 
							
								            salt = parts[0]
							 | 
						||
| 
								 | 
							
								            chk = None
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.MalformedHashError(cls)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # return new object
							 | 
						||
| 
								 | 
							
								        return cls(
							 | 
						||
| 
								 | 
							
								            rounds=rounds,
							 | 
						||
| 
								 | 
							
								            salt=salt,
							 | 
						||
| 
								 | 
							
								            checksum=chk or None,
							 | 
						||
| 
								 | 
							
								            implicit_rounds=implicit_rounds,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        if self.rounds == 5000 and self.implicit_rounds:
							 | 
						||
| 
								 | 
							
								            hash = u("%s%s$%s") % (self.ident, self.salt,
							 | 
						||
| 
								 | 
							
								                                   self.checksum or u(''))
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds,
							 | 
						||
| 
								 | 
							
								                                             self.salt, self.checksum or u(''))
							 | 
						||
| 
								 | 
							
								        return uascii_to_str(hash)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # backends
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    backends = ("os_crypt", "builtin")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # os_crypt backend
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: test hash for OS detection -- provided by subclass
							 | 
						||
| 
								 | 
							
								    _test_hash = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _load_backend_os_crypt(cls):
							 | 
						||
| 
								 | 
							
								        if test_crypt(*cls._test_hash):
							 | 
						||
| 
								 | 
							
								            cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum_os_crypt(self, secret):
							 | 
						||
| 
								 | 
							
								        config = self.to_string()
							 | 
						||
| 
								 | 
							
								        hash = safe_crypt(secret, config)
							 | 
						||
| 
								 | 
							
								        if hash is None:
							 | 
						||
| 
								 | 
							
								            # py3's crypt.crypt() can't handle non-utf8 bytes.
							 | 
						||
| 
								 | 
							
								            # fallback to builtin alg, which is always available.
							 | 
						||
| 
								 | 
							
								            return self._calc_checksum_builtin(secret)
							 | 
						||
| 
								 | 
							
								        # NOTE: avoiding full parsing routine via from_string().checksum,
							 | 
						||
| 
								 | 
							
								        # and just extracting the bit we need.
							 | 
						||
| 
								 | 
							
								        cs = self.checksum_size
							 | 
						||
| 
								 | 
							
								        if not hash.startswith(self.ident) or hash[-cs-1] != _UDOLLAR:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.CryptBackendError(self, config, hash)
							 | 
						||
| 
								 | 
							
								        return hash[-cs:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # builtin backend
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _load_backend_builtin(cls):
							 | 
						||
| 
								 | 
							
								        cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum_builtin(self, secret):
							 | 
						||
| 
								 | 
							
								        return _raw_sha2_crypt(secret, self.salt, self.rounds,
							 | 
						||
| 
								 | 
							
								                               self._cdb_use_512)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class sha256_crypt(_SHA2_Common):
							 | 
						||
| 
								 | 
							
								    """This class implements the SHA256-Crypt password hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and a variable number of rounds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type rounds: int
							 | 
						||
| 
								 | 
							
								    :param rounds:
							 | 
						||
| 
								 | 
							
								        Optional number of rounds to use.
							 | 
						||
| 
								 | 
							
								        Defaults to 535000, must be between 1000 and 999999999, inclusive.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. note::
							 | 
						||
| 
								 | 
							
								            per the official specification, when the rounds parameter is set to 5000,
							 | 
						||
| 
								 | 
							
								            it may be omitted from the hash string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type relaxed: bool
							 | 
						||
| 
								 | 
							
								    :param relaxed:
							 | 
						||
| 
								 | 
							
								        By default, providing an invalid value for one of the other
							 | 
						||
| 
								 | 
							
								        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
							 | 
						||
| 
								 | 
							
								        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
							 | 
						||
| 
								 | 
							
								        will be issued instead. Correctable errors include ``rounds``
							 | 
						||
| 
								 | 
							
								        that are too small or too large, and ``salt`` strings that are too long.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ..
							 | 
						||
| 
								 | 
							
								        commented out, currently only supported by :meth:`hash`, and not via :meth:`using`:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :type implicit_rounds: bool
							 | 
						||
| 
								 | 
							
								        :param implicit_rounds:
							 | 
						||
| 
								 | 
							
								            this is an internal option which generally doesn't need to be touched.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            this flag determines whether the hash should omit the rounds parameter
							 | 
						||
| 
								 | 
							
								            when encoding it to a string; this is only permitted by the spec for rounds=5000,
							 | 
						||
| 
								 | 
							
								            and the flag is ignored otherwise. the spec requires the two different
							 | 
						||
| 
								 | 
							
								            encodings be preserved as they are, instead of normalizing them.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    name = "sha256_crypt"
							 | 
						||
| 
								 | 
							
								    ident = u("$5$")
							 | 
						||
| 
								 | 
							
								    checksum_size = 43
							 | 
						||
| 
								 | 
							
								    # NOTE: using 25/75 weighting of builtin & os_crypt backends
							 | 
						||
| 
								 | 
							
								    default_rounds = 535000
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # backends
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    _test_hash = ("test", "$5$rounds=1000$test$QmQADEXMG8POI5W"
							 | 
						||
| 
								 | 
							
								                          "Dsaeho0P36yK3Tcrgboabng6bkb/")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# sha 512 crypt
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class sha512_crypt(_SHA2_Common):
							 | 
						||
| 
								 | 
							
								    """This class implements the SHA512-Crypt password hash, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It supports a variable-length salt, and a variable number of rounds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type rounds: int
							 | 
						||
| 
								 | 
							
								    :param rounds:
							 | 
						||
| 
								 | 
							
								        Optional number of rounds to use.
							 | 
						||
| 
								 | 
							
								        Defaults to 656000, must be between 1000 and 999999999, inclusive.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. note::
							 | 
						||
| 
								 | 
							
								            per the official specification, when the rounds parameter is set to 5000,
							 | 
						||
| 
								 | 
							
								            it may be omitted from the hash string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type relaxed: bool
							 | 
						||
| 
								 | 
							
								    :param relaxed:
							 | 
						||
| 
								 | 
							
								        By default, providing an invalid value for one of the other
							 | 
						||
| 
								 | 
							
								        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
							 | 
						||
| 
								 | 
							
								        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
							 | 
						||
| 
								 | 
							
								        will be issued instead. Correctable errors include ``rounds``
							 | 
						||
| 
								 | 
							
								        that are too small or too large, and ``salt`` strings that are too long.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ..
							 | 
						||
| 
								 | 
							
								        commented out, currently only supported by :meth:`hash`, and not via :meth:`using`:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :type implicit_rounds: bool
							 | 
						||
| 
								 | 
							
								        :param implicit_rounds:
							 | 
						||
| 
								 | 
							
								            this is an internal option which generally doesn't need to be touched.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            this flag determines whether the hash should omit the rounds parameter
							 | 
						||
| 
								 | 
							
								            when encoding it to a string; this is only permitted by the spec for rounds=5000,
							 | 
						||
| 
								 | 
							
								            and the flag is ignored otherwise. the spec requires the two different
							 | 
						||
| 
								 | 
							
								            encodings be preserved as they are, instead of normalizing them.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    name = "sha512_crypt"
							 | 
						||
| 
								 | 
							
								    ident = u("$6$")
							 | 
						||
| 
								 | 
							
								    checksum_size = 86
							 | 
						||
| 
								 | 
							
								    _cdb_use_512 = True
							 | 
						||
| 
								 | 
							
								    # NOTE: using 25/75 weighting of builtin & os_crypt backends
							 | 
						||
| 
								 | 
							
								    default_rounds = 656000
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # backend
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    _test_hash = ("test", "$6$rounds=1000$test$2M/Lx6Mtobqj"
							 | 
						||
| 
								 | 
							
								                          "Ljobw0Wmo4Q5OFx5nVLJvmgseatA6oMn"
							 | 
						||
| 
								 | 
							
								                          "yWeBdRDx4DU.1H3eGmse6pgsOgDisWBG"
							 | 
						||
| 
								 | 
							
								                          "I5c7TZauS0")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |