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.
		
		
		
		
		
			
		
			
				
					159 lines
				
				5.7 KiB
			
		
		
			
		
	
	
					159 lines
				
				5.7 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.handlers.sha1_crypt
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.utils import safe_crypt, test_crypt
							 | 
						||
| 
								 | 
							
								from passlib.utils.binary import h64
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import u, unicode, irange
							 | 
						||
| 
								 | 
							
								from passlib.crypto.digest import compile_hmac
							 | 
						||
| 
								 | 
							
								import passlib.utils.handlers as uh
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# sha1-crypt
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_BNULL = b'\x00'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """This class implements the SHA1-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, an 8 character one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt_size: int
							 | 
						||
| 
								 | 
							
								    :param salt_size:
							 | 
						||
| 
								 | 
							
								        Optional number of bytes to use when autogenerating new salts.
							 | 
						||
| 
								 | 
							
								        Defaults to 8 bytes, but can be any value between 0 and 64.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type rounds: int
							 | 
						||
| 
								 | 
							
								    :param rounds:
							 | 
						||
| 
								 | 
							
								        Optional number of rounds to use.
							 | 
						||
| 
								 | 
							
								        Defaults to 480000, must be between 1 and 4294967295, inclusive.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :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
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    #--GenericHandler--
							 | 
						||
| 
								 | 
							
								    name = "sha1_crypt"
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt", "salt_size", "rounds")
							 | 
						||
| 
								 | 
							
								    ident = u("$sha1$")
							 | 
						||
| 
								 | 
							
								    checksum_size = 28
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--HasSalt--
							 | 
						||
| 
								 | 
							
								    default_salt_size = 8
							 | 
						||
| 
								 | 
							
								    max_salt_size = 64
							 | 
						||
| 
								 | 
							
								    salt_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--HasRounds--
							 | 
						||
| 
								 | 
							
								    default_rounds = 480000 # current passlib default
							 | 
						||
| 
								 | 
							
								    min_rounds = 1 # really, this should be higher.
							 | 
						||
| 
								 | 
							
								    max_rounds = 4294967295 # 32-bit integer limit
							 | 
						||
| 
								 | 
							
								    rounds_cost = "linear"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # formatting
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
							 | 
						||
| 
								 | 
							
								        return cls(rounds=rounds, salt=salt, checksum=chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self, config=False):
							 | 
						||
| 
								 | 
							
								        chk = None if config else self.checksum
							 | 
						||
| 
								 | 
							
								        return uh.render_mc3(self.ident, self.rounds, self.salt, chk)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # backend
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    backends = ("os_crypt", "builtin")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # os_crypt backend
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _load_backend_os_crypt(cls):
							 | 
						||
| 
								 | 
							
								        if test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHim'
							 | 
						||
| 
								 | 
							
								                              'ExLaiSFlGkAe'):
							 | 
						||
| 
								 | 
							
								            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(config=True)
							 | 
						||
| 
								 | 
							
								        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)
							 | 
						||
| 
								 | 
							
								        if not hash.startswith(config) or len(hash) != len(config) + 29:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.CryptBackendError(self, config, hash)
							 | 
						||
| 
								 | 
							
								        return hash[-28:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # 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):
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        if _BNULL in secret:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.NullPasswordError(self)
							 | 
						||
| 
								 | 
							
								        rounds = self.rounds
							 | 
						||
| 
								 | 
							
								        # NOTE: this seed value is NOT the same as the config string
							 | 
						||
| 
								 | 
							
								        result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii")
							 | 
						||
| 
								 | 
							
								        # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
							 | 
						||
| 
								 | 
							
								        keyed_hmac = compile_hmac("sha1", secret)
							 | 
						||
| 
								 | 
							
								        for _ in irange(rounds):
							 | 
						||
| 
								 | 
							
								            result = keyed_hmac(result)
							 | 
						||
| 
								 | 
							
								        return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _chk_offsets = [
							 | 
						||
| 
								 | 
							
								        2,1,0,
							 | 
						||
| 
								 | 
							
								        5,4,3,
							 | 
						||
| 
								 | 
							
								        8,7,6,
							 | 
						||
| 
								 | 
							
								        11,10,9,
							 | 
						||
| 
								 | 
							
								        14,13,12,
							 | 
						||
| 
								 | 
							
								        17,16,15,
							 | 
						||
| 
								 | 
							
								        0,19,18,
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |