Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
des_crypt.py608 linesDownload Raw Back to handlers
1"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""2#=============================================================================3# imports4#=============================================================================5# core6import re7import logging; log = logging.getLogger(__name__)8from warnings import warn9# site10# pkg11from passlib.utils import safe_crypt, test_crypt, to_unicode12from passlib.utils.binary import h64, h64big13from passlib.utils.compat import byte_elem_value, u, uascii_to_str, unicode, suppress_cause14from passlib.crypto.des import des_encrypt_int_block15import passlib.utils.handlers as uh16# local17__all__ = [18    "des_crypt",19    "bsdi_crypt",20    "bigcrypt",21    "crypt16",22]23 24#=============================================================================25# pure-python backend for des_crypt family26#=============================================================================27_BNULL = b'\x00'28 29def _crypt_secret_to_key(secret):30    """convert secret to 64-bit DES key.31 32    this only uses the first 8 bytes of the secret,33    and discards the high 8th bit of each byte at that.34    a null parity bit is inserted after every 7th bit of the output.35    """36    # NOTE: this would set the parity bits correctly,37    #       but des_encrypt_int_block() would just ignore them...38    ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)39    ##           for i, c in enumerate(secret[:8]))40    return sum((byte_elem_value(c) & 0x7f) << (57-i*8)41               for i, c in enumerate(secret[:8]))42 43def _raw_des_crypt(secret, salt):44    """pure-python backed for des_crypt"""45    assert len(salt) == 246 47    # NOTE: some OSes will accept non-HASH64 characters in the salt,48    #       but what value they assign these characters varies wildy,49    #       so just rejecting them outright.50    #       the same goes for single-character salts...51    #       some OSes duplicate the char, some insert a '.' char,52    #       and openbsd does (something) which creates an invalid hash.53    salt_value = h64.decode_int12(salt)54 55    # gotta do something - no official policy since this predates unicode56    if isinstance(secret, unicode):57        secret = secret.encode("utf-8")58    assert isinstance(secret, bytes)59 60    # forbidding NULL char because underlying crypt() rejects them too.61    if _BNULL in secret:62        raise uh.exc.NullPasswordError(des_crypt)63 64    # convert first 8 bytes of secret string into an integer65    key_value = _crypt_secret_to_key(secret)66 67    # run data through des using input of 068    result = des_encrypt_int_block(key_value, 0, salt_value, 25)69 70    # run h64 encode on result71    return h64big.encode_int64(result)72 73def _bsdi_secret_to_key(secret):74    """convert secret to DES key used by bsdi_crypt"""75    key_value = _crypt_secret_to_key(secret)76    idx = 877    end = len(secret)78    while idx < end:79        next = idx + 880        tmp_value = _crypt_secret_to_key(secret[idx:next])81        key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value82        idx = next83    return key_value84 85def _raw_bsdi_crypt(secret, rounds, salt):86    """pure-python backend for bsdi_crypt"""87 88    # decode salt89    salt_value = h64.decode_int24(salt)90 91    # gotta do something - no official policy since this predates unicode92    if isinstance(secret, unicode):93        secret = secret.encode("utf-8")94    assert isinstance(secret, bytes)95 96    # forbidding NULL char because underlying crypt() rejects them too.97    if _BNULL in secret:98        raise uh.exc.NullPasswordError(bsdi_crypt)99 100    # convert secret string into an integer101    key_value = _bsdi_secret_to_key(secret)102 103    # run data through des using input of 0104    result = des_encrypt_int_block(key_value, 0, salt_value, rounds)105 106    # run h64 encode on result107    return h64big.encode_int64(result)108 109#=============================================================================110# handlers111#=============================================================================112class des_crypt(uh.TruncateMixin, uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):113    """This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.114 115    It supports a fixed-length salt.116 117    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:118 119    :type salt: str120    :param salt:121        Optional salt string.122        If not specified, one will be autogenerated (this is recommended).123        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.124 125    :param bool truncate_error:126        By default, des_crypt will silently truncate passwords larger than 8 bytes.127        Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`128        to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.129 130        .. versionadded:: 1.7131 132    :type relaxed: bool133    :param relaxed:134        By default, providing an invalid value for one of the other135        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,136        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`137        will be issued instead. Correctable errors include138        ``salt`` strings that are too long.139 140        .. versionadded:: 1.6141    """142    #===================================================================143    # class attrs144    #===================================================================145 146    #--------------------147    # PasswordHash148    #--------------------149    name = "des_crypt"150    setting_kwds = ("salt", "truncate_error")151 152    #--------------------153    # GenericHandler154    #--------------------155    checksum_chars = uh.HASH64_CHARS156    checksum_size = 11157 158    #--------------------159    # HasSalt160    #--------------------161    min_salt_size = max_salt_size = 2162    salt_chars = uh.HASH64_CHARS163 164    #--------------------165    # TruncateMixin166    #--------------------167    truncate_size = 8168 169    #===================================================================170    # formatting171    #===================================================================172    # FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum173 174    _hash_regex = re.compile(u(r"""175        ^176        (?P<salt>[./a-z0-9]{2})177        (?P<chk>[./a-z0-9]{11})?178        $"""), re.X|re.I)179 180    @classmethod181    def from_string(cls, hash):182        hash = to_unicode(hash, "ascii", "hash")183        salt, chk = hash[:2], hash[2:]184        return cls(salt=salt, checksum=chk or None)185 186    def to_string(self):187        hash = u("%s%s") % (self.salt, self.checksum)188        return uascii_to_str(hash)189 190    #===================================================================191    # digest calculation192    #===================================================================193    def _calc_checksum(self, secret):194        # check for truncation (during .hash() calls only)195        if self.use_defaults:196            self._check_truncate_policy(secret)197 198        return self._calc_checksum_backend(secret)199 200    #===================================================================201    # backend202    #===================================================================203    backends = ("os_crypt", "builtin")204 205    #---------------------------------------------------------------206    # os_crypt backend207    #---------------------------------------------------------------208    @classmethod209    def _load_backend_os_crypt(cls):210        if test_crypt("test", 'abgOeLfPimXQo'):211            cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)212            return True213        else:214            return False215 216    def _calc_checksum_os_crypt(self, secret):217        # NOTE: we let safe_crypt() encode unicode secret -> utf8;218        #       no official policy since des-crypt predates unicode219        hash = safe_crypt(secret, self.salt)220        if hash is None:221            # py3's crypt.crypt() can't handle non-utf8 bytes.222            # fallback to builtin alg, which is always available.223            return self._calc_checksum_builtin(secret)224        if not hash.startswith(self.salt) or len(hash) != 13:225            raise uh.exc.CryptBackendError(self, self.salt, hash)226        return hash[2:]227 228    #---------------------------------------------------------------229    # builtin backend230    #---------------------------------------------------------------231    @classmethod232    def _load_backend_builtin(cls):233        cls._set_calc_checksum_backend(cls._calc_checksum_builtin)234        return True235 236    def _calc_checksum_builtin(self, secret):237        return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")238 239    #===================================================================240    # eoc241    #===================================================================242 243class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):244    """This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.245 246    It supports a fixed-length salt, and a variable number of rounds.247 248    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:249 250    :type salt: str251    :param salt:252        Optional salt string.253        If not specified, one will be autogenerated (this is recommended).254        If specified, it must be 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``.255 256    :type rounds: int257    :param rounds:258        Optional number of rounds to use.259        Defaults to 5001, must be between 1 and 16777215, inclusive.260 261    :type relaxed: bool262    :param relaxed:263        By default, providing an invalid value for one of the other264        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,265        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`266        will be issued instead. Correctable errors include ``rounds``267        that are too small or too large, and ``salt`` strings that are too long.268 269        .. versionadded:: 1.6270 271    .. versionchanged:: 1.6272        :meth:`hash` will now issue a warning if an even number of rounds is used273        (see :ref:`bsdi-crypt-security-issues` regarding weak DES keys).274    """275    #===================================================================276    # class attrs277    #===================================================================278    #--GenericHandler--279    name = "bsdi_crypt"280    setting_kwds = ("salt", "rounds")281    checksum_size = 11282    checksum_chars = uh.HASH64_CHARS283 284    #--HasSalt--285    min_salt_size = max_salt_size = 4286    salt_chars = uh.HASH64_CHARS287 288    #--HasRounds--289    default_rounds = 5001290    min_rounds = 1291    max_rounds = 16777215 # (1<<24)-1292    rounds_cost = "linear"293 294    # NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds,295    # but that seems to be an OS policy, not a algorithm limitation.296 297    #===================================================================298    # parsing299    #===================================================================300    _hash_regex = re.compile(u(r"""301        ^302        _303        (?P<rounds>[./a-z0-9]{4})304        (?P<salt>[./a-z0-9]{4})305        (?P<chk>[./a-z0-9]{11})?306        $"""), re.X|re.I)307 308    @classmethod309    def from_string(cls, hash):310        hash = to_unicode(hash, "ascii", "hash")311        m = cls._hash_regex.match(hash)312        if not m:313            raise uh.exc.InvalidHashError(cls)314        rounds, salt, chk = m.group("rounds", "salt", "chk")315        return cls(316            rounds=h64.decode_int24(rounds.encode("ascii")),317            salt=salt,318            checksum=chk,319        )320 321    def to_string(self):322        hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"),323                               self.salt, self.checksum)324        return uascii_to_str(hash)325 326    #===================================================================327    # validation328    #===================================================================329 330    # NOTE: keeping this flag for admin/choose_rounds.py script.331    #       want to eventually expose rounds logic to that script in better way.332    _avoid_even_rounds = True333 334    @classmethod335    def using(cls, **kwds):336        subcls = super(bsdi_crypt, cls).using(**kwds)337        if not subcls.default_rounds & 1:338            # issue warning if caller set an even 'rounds' value.339            warn("bsdi_crypt rounds should be odd, as even rounds may reveal weak DES keys",340                 uh.exc.PasslibSecurityWarning)341        return subcls342 343    @classmethod344    def _generate_rounds(cls):345        rounds = super(bsdi_crypt, cls)._generate_rounds()346        # ensure autogenerated rounds are always odd347        # NOTE: doing this even for default_rounds so needs_update() doesn't get348        #       caught in a loop.349        # FIXME: this technically might generate a rounds value 1 larger350        # than the requested upper bound - but better to err on side of safety.351        return rounds|1352 353    #===================================================================354    # migration355    #===================================================================356 357    def _calc_needs_update(self, **kwds):358        # mark bsdi_crypt hashes as deprecated if they have even rounds.359        if not self.rounds & 1:360            return True361        # hand off to base implementation362        return super(bsdi_crypt, self)._calc_needs_update(**kwds)363 364    #===================================================================365    # backends366    #===================================================================367    backends = ("os_crypt", "builtin")368 369    #---------------------------------------------------------------370    # os_crypt backend371    #---------------------------------------------------------------372    @classmethod373    def _load_backend_os_crypt(cls):374        if test_crypt("test", '_/...lLDAxARksGCHin.'):375            cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)376            return True377        else:378            return False379 380    def _calc_checksum_os_crypt(self, secret):381        config = self.to_string()382        hash = safe_crypt(secret, config)383        if hash is None:384            # py3's crypt.crypt() can't handle non-utf8 bytes.385            # fallback to builtin alg, which is always available.386            return self._calc_checksum_builtin(secret)387        if not hash.startswith(config[:9]) or len(hash) != 20:388            raise uh.exc.CryptBackendError(self, config, hash)389        return hash[-11:]390 391    #---------------------------------------------------------------392    # builtin backend393    #---------------------------------------------------------------394    @classmethod395    def _load_backend_builtin(cls):396        cls._set_calc_checksum_backend(cls._calc_checksum_builtin)397        return True398 399    def _calc_checksum_builtin(self, secret):400        return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")401 402    #===================================================================403    # eoc404    #===================================================================405 406class bigcrypt(uh.HasSalt, uh.GenericHandler):407    """This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.408 409    It supports a fixed-length salt.410 411    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:412 413    :type salt: str414    :param salt:415        Optional salt string.416        If not specified, one will be autogenerated (this is recommended).417        If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.418 419    :type relaxed: bool420    :param relaxed:421        By default, providing an invalid value for one of the other422        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,423        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`424        will be issued instead. Correctable errors include425        ``salt`` strings that are too long.426 427        .. versionadded:: 1.6428    """429    #===================================================================430    # class attrs431    #===================================================================432    #--GenericHandler--433    name = "bigcrypt"434    setting_kwds = ("salt",)435    checksum_chars = uh.HASH64_CHARS436    # NOTE: checksum chars must be multiple of 11437 438    #--HasSalt--439    min_salt_size = max_salt_size = 2440    salt_chars = uh.HASH64_CHARS441 442    #===================================================================443    # internal helpers444    #===================================================================445    _hash_regex = re.compile(u(r"""446        ^447        (?P<salt>[./a-z0-9]{2})448        (?P<chk>([./a-z0-9]{11})+)?449        $"""), re.X|re.I)450 451    @classmethod452    def from_string(cls, hash):453        hash = to_unicode(hash, "ascii", "hash")454        m = cls._hash_regex.match(hash)455        if not m:456            raise uh.exc.InvalidHashError(cls)457        salt, chk = m.group("salt", "chk")458        return cls(salt=salt, checksum=chk)459 460    def to_string(self):461        hash = u("%s%s") % (self.salt, self.checksum)462        return uascii_to_str(hash)463 464    def _norm_checksum(self, checksum, relaxed=False):465        checksum = super(bigcrypt, self)._norm_checksum(checksum, relaxed=relaxed)466        if len(checksum) % 11:467            raise uh.exc.InvalidHashError(self)468        return checksum469 470    #===================================================================471    # backend472    #===================================================================473    def _calc_checksum(self, secret):474        if isinstance(secret, unicode):475            secret = secret.encode("utf-8")476        chk = _raw_des_crypt(secret, self.salt.encode("ascii"))477        idx = 8478        end = len(secret)479        while idx < end:480            next = idx + 8481            chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])482            idx = next483        return chk.decode("ascii")484 485    #===================================================================486    # eoc487    #===================================================================488 489class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):490    """This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.491 492    It supports a fixed-length salt.493 494    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:495 496    :type salt: str497    :param salt:498        Optional salt string.499        If not specified, one will be autogenerated (this is recommended).500        If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.501 502    :param bool truncate_error:503        By default, crypt16 will silently truncate passwords larger than 16 bytes.504        Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`505        to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.506 507        .. versionadded:: 1.7508 509    :type relaxed: bool510    :param relaxed:511        By default, providing an invalid value for one of the other512        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,513        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`514        will be issued instead. Correctable errors include515        ``salt`` strings that are too long.516 517        .. versionadded:: 1.6518    """519    #===================================================================520    # class attrs521    #===================================================================522 523    #--------------------524    # PasswordHash525    #--------------------526    name = "crypt16"527    setting_kwds = ("salt", "truncate_error")528 529    #--------------------530    # GenericHandler531    #--------------------532    checksum_size = 22533    checksum_chars = uh.HASH64_CHARS534 535    #--------------------536    # HasSalt537    #--------------------538    min_salt_size = max_salt_size = 2539    salt_chars = uh.HASH64_CHARS540 541    #--------------------542    # TruncateMixin543    #--------------------544    truncate_size = 16545 546    #===================================================================547    # internal helpers548    #===================================================================549    _hash_regex = re.compile(u(r"""550        ^551        (?P<salt>[./a-z0-9]{2})552        (?P<chk>[./a-z0-9]{22})?553        $"""), re.X|re.I)554 555    @classmethod556    def from_string(cls, hash):557        hash = to_unicode(hash, "ascii", "hash")558        m = cls._hash_regex.match(hash)559        if not m:560            raise uh.exc.InvalidHashError(cls)561        salt, chk = m.group("salt", "chk")562        return cls(salt=salt, checksum=chk)563 564    def to_string(self):565        hash = u("%s%s") % (self.salt, self.checksum)566        return uascii_to_str(hash)567 568    #===================================================================569    # backend570    #===================================================================571    def _calc_checksum(self, secret):572        if isinstance(secret, unicode):573            secret = secret.encode("utf-8")574 575        # check for truncation (during .hash() calls only)576        if self.use_defaults:577            self._check_truncate_policy(secret)578 579        # parse salt value580        try:581            salt_value = h64.decode_int12(self.salt.encode("ascii"))582        except ValueError: # pragma: no cover - caught by class583            raise suppress_cause(ValueError("invalid chars in salt"))584 585        # convert first 8 byts of secret string into an integer,586        key1 = _crypt_secret_to_key(secret)587 588        # run data through des using input of 0589        result1 = des_encrypt_int_block(key1, 0, salt_value, 20)590 591        # convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)592        key2 = _crypt_secret_to_key(secret[8:16])593 594        # run data through des using input of 0595        result2 = des_encrypt_int_block(key2, 0, salt_value, 5)596 597        # done598        chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)599        return chk.decode("ascii")600 601    #===================================================================602    # eoc603    #===================================================================604 605#=============================================================================606# eof607#=============================================================================608 
codekingpro/portable-devtools · Team Ai