Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
argon2.py1010 linesDownload Raw Back to handlers
1"""passlib.handlers.argon2 -- argon2 password hash wrapper2 3References4==========5* argon26    - home: https://github.com/P-H-C/phc-winner-argon27    - whitepaper: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf8* argon2 cffi wrapper9    - pypi: https://pypi.python.org/pypi/argon2_cffi10    - home: https://github.com/hynek/argon2_cffi11* argon2 pure python12    - pypi: https://pypi.python.org/pypi/argon2pure13    - home: https://github.com/bwesterb/argon2pure14"""15#=============================================================================16# imports17#=============================================================================18from __future__ import with_statement, absolute_import19# core20import logging21log = logging.getLogger(__name__)22import re23import types24from warnings import warn25# site26_argon2_cffi = None  # loaded below27_argon2pure = None  # dynamically imported by _load_backend_argon2pure()28# pkg29from passlib import exc30from passlib.crypto.digest import MAX_UINT3231from passlib.utils import classproperty, to_bytes, render_bytes32from passlib.utils.binary import b64s_encode, b64s_decode33from passlib.utils.compat import u, unicode, bascii_to_str, uascii_to_str, PY234import passlib.utils.handlers as uh35# local36__all__ = [37    "argon2",38]39 40#=============================================================================41# helpers42#=============================================================================43 44# NOTE: when adding a new argon2 hash type, need to do the following:45# * add TYPE_XXX constant, and add to ALL_TYPES46# * make sure "_backend_type_map" constructors handle it correctly for all backends47# * make sure _hash_regex & _ident_regex (below) support type string.48# * add reference vectors for testing.49 50#: argon2 type constants -- subclasses handle mapping these to backend-specific type constants.51#: (should be lowercase, to match representation in hash string)52TYPE_I = u("i")53TYPE_D = u("d")54TYPE_ID = u("id")  # new 2016-10-29; passlib 1.7.2 requires backends new enough for support55 56#: list of all known types; first (supported) type will be used as default.57ALL_TYPES = (TYPE_ID, TYPE_I, TYPE_D)58ALL_TYPES_SET = set(ALL_TYPES)59 60#=============================================================================61# import argon2 package (https://pypi.python.org/pypi/argon2_cffi)62#=============================================================================63 64# import cffi package65# NOTE: we try to do this even if caller is going to use argon2pure,66#       so that we can always use the libargon2 default settings when possible.67_argon2_cffi_error = None68try:69    import argon2 as _argon2_cffi70except ImportError:71    _argon2_cffi = None72else:73    if not hasattr(_argon2_cffi, "Type"):74        # they have incompatible "argon2" package installed, instead of "argon2_cffi" package.75        _argon2_cffi_error = (76            "'argon2' module points to unsupported 'argon2' pypi package; "77            "please install 'argon2-cffi' instead."78        )79        _argon2_cffi = None80    elif not hasattr(_argon2_cffi, "low_level"):81        # they have pre-v16 argon2_cffi package82        _argon2_cffi_error = "'argon2-cffi' is too old, please update to argon2_cffi >= 18.2.0"83        _argon2_cffi = None84 85# init default settings for our hasher class --86# if we have argon2_cffi >= 16.0, use their default hasher settings, otherwise use static default87if hasattr(_argon2_cffi, "PasswordHasher"):88    # use cffi's default settings89    _default_settings = _argon2_cffi.PasswordHasher()90    _default_version = _argon2_cffi.low_level.ARGON2_VERSION91else:92    # use fallback settings (for no backend, or argon2pure)93    class _DummyCffiHasher:94        """95        dummy object to use as source of defaults when argon2_cffi isn't present.96        this tries to mimic the attributes of ``argon2.PasswordHasher()`` which the rest of97        this module reads.98 99        .. note:: values last synced w/ argon2 19.2 as of 2019-11-09100        """101        time_cost = 2102        memory_cost = 512103        parallelism = 2104        salt_len = 16105        hash_len = 16106        # NOTE: "type" attribute added in argon2_cffi v18.2; but currently not reading it107        # type = _argon2_cffi.Type.ID108 109    _default_settings = _DummyCffiHasher()110    _default_version = 0x13  # v1.9111 112#=============================================================================113# handler114#=============================================================================115class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin,116                    uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum,117                    uh.GenericHandler):118    """119    Base class which implements brunt of Argon2 code.120    This is then subclassed by the various backends,121    to override w/ backend-specific methods.122 123    When a backend is loaded, the bases of the 'argon2' class proper124    are modified to prepend the correct backend-specific subclass.125    """126    #===================================================================127    # class attrs128    #===================================================================129 130    #------------------------131    # PasswordHash132    #------------------------133 134    name = "argon2"135    setting_kwds = ("salt",136                    "salt_size",137                    "salt_len",  # 'salt_size' alias for compat w/ argon2 package138                    "rounds",139                    "time_cost",  # 'rounds' alias for compat w/ argon2 package140                    "memory_cost",141                    "parallelism",142                    "digest_size",143                    "hash_len",  # 'digest_size' alias for compat w/ argon2 package144                    "type",  # the type of argon2 hash used145                    )146 147    # TODO: could support the optional 'data' parameter,148    #       but need to research the uses, what a more descriptive name would be,149    #       and deal w/ fact that argon2_cffi 16.1 doesn't currently support it.150    #       (argon2_pure does though)151 152    #------------------------153    # GenericHandler154    #------------------------155 156    # NOTE: ident -- all argon2 hashes start with "$argon2<type>$"157    # XXX: could programmaticaly generate "ident_values" string from ALL_TYPES above158 159    checksum_size = _default_settings.hash_len160 161    #: force parsing these kwds162    _always_parse_settings = uh.GenericHandler._always_parse_settings + \163                             ("type",)164 165    #: exclude these kwds from parsehash() result (most are aliases for other keys)166    _unparsed_settings = uh.GenericHandler._unparsed_settings + \167                         ("salt_len", "time_cost", "hash_len", "digest_size")168 169    #------------------------170    # HasSalt171    #------------------------172    default_salt_size = _default_settings.salt_len173    min_salt_size = 8174    max_salt_size = MAX_UINT32175 176    #------------------------177    # HasRounds178    # TODO: once rounds limit logic is factored out,179    #       make 'rounds' and 'cost' an alias for 'time_cost'180    #------------------------181    default_rounds = _default_settings.time_cost182    min_rounds = 1183    max_rounds = MAX_UINT32184    rounds_cost = "linear"185 186    #------------------------187    # ParalleismMixin188    #------------------------189    max_parallelism = (1 << 24) - 1  # from argon2.h / ARGON2_MAX_LANES190 191    #------------------------192    # custom193    #------------------------194 195    #: max version support196    #: NOTE: this is dependant on the backend, and initialized/modified by set_backend()197    max_version = _default_version198 199    #: minimum version before needs_update() marks the hash; if None, defaults to max_version200    min_desired_version = None201 202    #: minimum valid memory_cost203    min_memory_cost = 8  # from argon2.h / ARGON2_MIN_MEMORY204 205    #: maximum number of threads (-1=unlimited);206    #: number of threads used by .hash() will be min(parallelism, max_threads)207    max_threads = -1208 209    #: global flag signalling argon2pure backend to use threads210    #: rather than subprocesses.211    pure_use_threads = False212 213    #: internal helper used to store mapping of TYPE_XXX constants -> backend-specific type constants;214    #: this is populated by _load_backend_mixin(); and used to detect which types are supported.215    #: XXX: could expose keys as class-level .supported_types property?216    _backend_type_map = {}217 218    @classproperty219    def type_values(cls):220        """221        return tuple of types supported by this backend222        223        .. versionadded:: 1.7.2224        """225        cls.get_backend()  # make sure backend is loaded226        return tuple(cls._backend_type_map)227 228    #===================================================================229    # instance attrs230    #===================================================================231 232    #: argon2 hash type, one of ALL_TYPES -- class value controls the default233    #: .. versionadded:: 1.7.2234    type = TYPE_ID235 236    #: parallelism setting -- class value controls the default237    parallelism = _default_settings.parallelism238 239    #: hash version (int)240    #: NOTE: this is modified by set_backend()241    version = _default_version242 243    #: memory cost -- class value controls the default244    memory_cost = _default_settings.memory_cost245 246    @property247    def type_d(self):248        """249        flag indicating a Type D hash250 251        .. deprecated:: 1.7.2; will be removed in passlib 2.0252        """253        return self.type == TYPE_D254 255    #: optional secret data256    data = None257 258    #===================================================================259    # variant constructor260    #===================================================================261 262    @classmethod263    def using(cls, type=None, memory_cost=None, salt_len=None, time_cost=None, digest_size=None,264              checksum_size=None, hash_len=None, max_threads=None, **kwds):265        # support aliases which match argon2 naming convention266        if time_cost is not None:267            if "rounds" in kwds:268                raise TypeError("'time_cost' and 'rounds' are mutually exclusive")269            kwds['rounds'] = time_cost270 271        if salt_len is not None:272            if "salt_size" in kwds:273                raise TypeError("'salt_len' and 'salt_size' are mutually exclusive")274            kwds['salt_size'] = salt_len275 276        if hash_len is not None:277            if digest_size is not None:278                raise TypeError("'hash_len' and 'digest_size' are mutually exclusive")279            digest_size = hash_len280 281        if checksum_size is not None:282            if digest_size is not None:283                raise TypeError("'checksum_size' and 'digest_size' are mutually exclusive")284            digest_size = checksum_size285 286        # create variant287        subcls = super(_Argon2Common, cls).using(**kwds)288 289        # set type290        if type is not None:291            subcls.type = subcls._norm_type(type)292 293        # set checksum size294        relaxed = kwds.get("relaxed")295        if digest_size is not None:296            if isinstance(digest_size, uh.native_string_types):297                digest_size = int(digest_size)298            # NOTE: this isn't *really* digest size minimum, but want to enforce secure minimum.299            subcls.checksum_size = uh.norm_integer(subcls, digest_size, min=16, max=MAX_UINT32,300                                                   param="digest_size", relaxed=relaxed)301 302        # set memory cost303        if memory_cost is not None:304            if isinstance(memory_cost, uh.native_string_types):305                memory_cost = int(memory_cost)306            subcls.memory_cost = subcls._norm_memory_cost(memory_cost, relaxed=relaxed)307 308        # validate constraints309        subcls._validate_constraints(subcls.memory_cost, subcls.parallelism)310 311        # set max threads312        if max_threads is not None:313            if isinstance(max_threads, uh.native_string_types):314                max_threads = int(max_threads)315            if max_threads < 1 and max_threads != -1:316                raise ValueError("max_threads (%d) must be -1 (unlimited), or at least 1." %317                                 (max_threads,))318            subcls.max_threads = max_threads319 320        return subcls321 322    @classmethod323    def _validate_constraints(cls, memory_cost, parallelism):324        # NOTE: this is used by class & instance, hence passing in via arguments.325        #       could switch and make this a hybrid method.326        min_memory_cost = 8 * parallelism327        if memory_cost < min_memory_cost:328            raise ValueError("%s: memory_cost (%d) is too low, must be at least "329                             "8 * parallelism (8 * %d = %d)" %330                             (cls.name, memory_cost,331                              parallelism, min_memory_cost))332 333    #===================================================================334    # public api335    #===================================================================336 337    #: shorter version of _hash_regex, used to quickly identify hashes338    _ident_regex = re.compile(r"^\$argon2[a-z]+\$")339 340    @classmethod341    def identify(cls, hash):342        hash = uh.to_unicode_for_identify(hash)343        return cls._ident_regex.match(hash) is not None344 345    # hash(), verify(), genhash() -- implemented by backend subclass346 347    #===================================================================348    # hash parsing / rendering349    #===================================================================350 351    # info taken from source of decode_string() function in352    # <https://github.com/P-H-C/phc-winner-argon2/blob/master/src/encoding.c>353    #354    # hash format:355    #   $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>[,keyid=<bin>][,data=<bin>][$<bin>[$<bin>]]356    #357    # NOTE: as of 2016-6-17, the official source (above) lists the "keyid" param in the comments,358    #       but the actual source of decode_string & encode_string don't mention it at all.359    #       we're supporting parsing it, but throw NotImplementedError if encountered.360    #361    # sample hashes:362    #    v1.0: '$argon2i$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'363    #    v1.3: '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'364 365    #: regex to parse argon hash366    _hash_regex = re.compile(br"""367        ^368        \$argon2(?P<type>[a-z]+)\$369        (?:370            v=(?P<version>\d+)371            \$372        )?373        m=(?P<memory_cost>\d+)374        ,375        t=(?P<time_cost>\d+)376        ,377        p=(?P<parallelism>\d+)378        (?:379            ,keyid=(?P<keyid>[^,$]+)380        )?381        (?:382            ,data=(?P<data>[^,$]+)383        )?384        (?:385            \$386            (?P<salt>[^$]+)387            (?:388                \$389                (?P<digest>.+)390            )?391        )?392        $393    """, re.X)394 395    @classmethod396    def from_string(cls, hash):397        # NOTE: assuming hash will be unicode, or use ascii-compatible encoding.398        # TODO: switch to working w/ str or unicode399        if isinstance(hash, unicode):400            hash = hash.encode("utf-8")401        if not isinstance(hash, bytes):402            raise exc.ExpectedStringError(hash, "hash")403        m = cls._hash_regex.match(hash)404        if not m:405            raise exc.MalformedHashError(cls)406        type, version, memory_cost, time_cost, parallelism, keyid, data, salt, digest = \407            m.group("type", "version", "memory_cost", "time_cost", "parallelism",408                    "keyid", "data", "salt", "digest")409        if keyid:410            raise NotImplementedError("argon2 'keyid' parameter not supported")411        return cls(412            type=type.decode("ascii"),413            version=int(version) if version else 0x10,414            memory_cost=int(memory_cost),415            rounds=int(time_cost),416            parallelism=int(parallelism),417            salt=b64s_decode(salt) if salt else None,418            data=b64s_decode(data) if data else None,419            checksum=b64s_decode(digest) if digest else None,420        )421 422    def to_string(self):423        version = self.version424        if version == 0x10:425            vstr = ""426        else:427            vstr = "v=%d$" % version428 429        data = self.data430        if data:431            kdstr = ",data=" + bascii_to_str(b64s_encode(self.data))432        else:433            kdstr = ""434 435        # NOTE: 'keyid' param currently not supported436        return "$argon2%s$%sm=%d,t=%d,p=%d%s$%s$%s" % (437            uascii_to_str(self.type),438            vstr, 439            self.memory_cost,440            self.rounds, 441            self.parallelism,442            kdstr,443            bascii_to_str(b64s_encode(self.salt)),444            bascii_to_str(b64s_encode(self.checksum)),445        )446 447    #===================================================================448    # init449    #===================================================================450    def __init__(self, type=None, type_d=False, version=None, memory_cost=None, data=None, **kwds):451 452        # handle deprecated kwds453        if type_d:454            warn('argon2 `type_d=True` keyword is deprecated, and will be removed in passlib 2.0; '455                 'please use ``type="d"`` instead')456            assert type is None457            type = TYPE_D458 459        # TODO: factor out variable checksum size support into a mixin.460        # set checksum size to specific value before _norm_checksum() is called461        checksum = kwds.get("checksum")462        if checksum is not None:463            self.checksum_size = len(checksum)464 465        # call parent466        super(_Argon2Common, self).__init__(**kwds)467 468        # init type469        if type is None:470            assert uh.validate_default_value(self, self.type, self._norm_type, param="type")471        else:472            self.type = self._norm_type(type)473 474        # init version475        if version is None:476            assert uh.validate_default_value(self, self.version, self._norm_version,477                                             param="version")478        else:479            self.version = self._norm_version(version)480 481        # init memory cost482        if memory_cost is None:483            assert uh.validate_default_value(self, self.memory_cost, self._norm_memory_cost,484                                             param="memory_cost")485        else:486            self.memory_cost = self._norm_memory_cost(memory_cost)487 488        # init data489        if data is None:490            assert self.data is None491        else:492            if not isinstance(data, bytes):493                raise uh.exc.ExpectedTypeError(data, "bytes", "data")494            self.data = data495 496    #-------------------------------------------------------------------497    # parameter guards498    #-------------------------------------------------------------------499 500    @classmethod501    def _norm_type(cls, value):502        # type check503        if not isinstance(value, unicode):504            if PY2 and isinstance(value, bytes):505                value = value.decode('ascii')506            else:507                raise uh.exc.ExpectedTypeError(value, "str", "type")508 509        # check if type is valid510        if value in ALL_TYPES_SET:511            return value512 513        # translate from uppercase514        temp = value.lower()515        if temp in ALL_TYPES_SET:516            return temp517 518        # failure!519        raise ValueError("unknown argon2 hash type: %r" % (value,))520 521    @classmethod522    def _norm_version(cls, version):523        if not isinstance(version, uh.int_types):524            raise uh.exc.ExpectedTypeError(version, "integer", "version")525 526        # minimum valid version527        if version < 0x13 and version != 0x10:528            raise ValueError("invalid argon2 hash version: %d" % (version,))529 530        # check this isn't past backend's max version531        backend = cls.get_backend()532        if version > cls.max_version:533            raise ValueError("%s: hash version 0x%X not supported by %r backend "534                             "(max version is 0x%X); try updating or switching backends" %535                             (cls.name, version, backend, cls.max_version))536        return version537 538    @classmethod539    def _norm_memory_cost(cls, memory_cost, relaxed=False):540        return uh.norm_integer(cls, memory_cost, min=cls.min_memory_cost,541                               param="memory_cost", relaxed=relaxed)542 543    #===================================================================544    # digest calculation545    #===================================================================546 547    # NOTE: _calc_checksum implemented by backend subclass548 549    @classmethod550    def _get_backend_type(cls, value):551        """552        helper to resolve backend constant from type553        """554        try:555            return cls._backend_type_map[value]556        except KeyError:557            pass558        # XXX: pick better error class?559        msg = "unsupported argon2 hash (type %r not supported by %s backend)" % \560              (value, cls.get_backend())561        raise ValueError(msg)562 563    #===================================================================564    # hash migration565    #===================================================================566 567    def _calc_needs_update(self, **kwds):568        cls = type(self)569        if self.type != cls.type:570            return True571        minver = cls.min_desired_version572        if minver is None or minver > cls.max_version:573            minver = cls.max_version574        if self.version < minver:575            # version is too old.576            return True577        if self.memory_cost != cls.memory_cost:578            return True579        if self.checksum_size != cls.checksum_size:580            return True581        return super(_Argon2Common, self)._calc_needs_update(**kwds)582    583    #===================================================================584    # backend loading585    #===================================================================586 587    _no_backend_suggestion = " -- recommend you install one (e.g. 'pip install argon2_cffi')"588 589    @classmethod590    def _finalize_backend_mixin(mixin_cls, name, dryrun):591        """592        helper called by from backend mixin classes' _load_backend_mixin() --593        invoked after backend imports have been loaded, and performs594        feature detection & testing common to all backends.595        """596        # check argon2 version597        max_version = mixin_cls.max_version598        assert isinstance(max_version, int) and max_version >= 0x10599        if max_version < 0x13:600            warn("%r doesn't support argon2 v1.3, and should be upgraded" % name,601                 uh.exc.PasslibSecurityWarning)602 603        # prefer best available type604        for type in ALL_TYPES:605            if type in mixin_cls._backend_type_map:606                mixin_cls.type = type607                break608        else:609            warn("%r lacks support for all known hash types" % name, uh.exc.PasslibRuntimeWarning)610            # NOTE: class will just throw "unsupported argon2 hash" error if they try to use it...611            mixin_cls.type = TYPE_ID612 613        return True614 615    @classmethod616    def _adapt_backend_error(cls, err, hash=None, self=None):617        """618        internal helper invoked when backend has hash/verification error;619        used to adapt to passlib message.620        """621        backend = cls.get_backend()622 623        # parse hash to throw error if format was invalid, parameter out of range, etc.624        if self is None and hash is not None:625            self = cls.from_string(hash)626 627        # check constraints on parsed object628        # XXX: could move this to __init__, but not needed by needs_update calls629        if self is not None:630            self._validate_constraints(self.memory_cost, self.parallelism)631 632            # as of cffi 16.1, lacks support in hash_secret(), so genhash() will get here.633            # as of cffi 16.2, support removed from verify_secret() as well.634            if backend == "argon2_cffi" and self.data is not None:635                raise NotImplementedError("argon2_cffi backend doesn't support the 'data' parameter")636 637        # fallback to reporting a malformed hash638        text = str(err)639        if text not in [640            "Decoding failed"  # argon2_cffi's default message641            ]:642            reason = "%s reported: %s: hash=%r" % (backend, text, hash)643        else:644            reason = repr(hash)645        raise exc.MalformedHashError(cls, reason=reason)646 647    #===================================================================648    # eoc649    #===================================================================650 651#-----------------------------------------------------------------------652# stub backend653#-----------------------------------------------------------------------654class _NoBackend(_Argon2Common):655    """656    mixin used before any backend has been loaded.657    contains stubs that force loading of one of the available backends.658    """659    #===================================================================660    # primary methods661    #===================================================================662    @classmethod663    def hash(cls, secret):664        cls._stub_requires_backend()665        return cls.hash(secret)666 667    @classmethod668    def verify(cls, secret, hash):669        cls._stub_requires_backend()670        return cls.verify(secret, hash)671 672    @uh.deprecated_method(deprecated="1.7", removed="2.0")673    @classmethod674    def genhash(cls, secret, config):675        cls._stub_requires_backend()676        return cls.genhash(secret, config)677 678    #===================================================================679    # digest calculation680    #===================================================================681    def _calc_checksum(self, secret):682        # NOTE: since argon2_cffi takes care of rendering hash,683        #       _calc_checksum() is only used by the argon2pure backend.684        self._stub_requires_backend()685        # NOTE: have to use super() here so that we don't recursively686        #       call subclass's wrapped _calc_checksum687        return super(argon2, self)._calc_checksum(secret)688 689    #===================================================================690    # eoc691    #===================================================================692 693#-----------------------------------------------------------------------694# argon2_cffi backend695#-----------------------------------------------------------------------696class _CffiBackend(_Argon2Common):697    """698    argon2_cffi backend699    """700    #===================================================================701    # backend loading702    #===================================================================703 704    @classmethod705    def _load_backend_mixin(mixin_cls, name, dryrun):706        # make sure we write info to base class's __dict__, not that of a subclass707        assert mixin_cls is _CffiBackend708 709        # we automatically import this at top, so just grab info710        if _argon2_cffi is None:711            if _argon2_cffi_error:712                raise exc.PasslibSecurityError(_argon2_cffi_error)713            return False714        max_version = _argon2_cffi.low_level.ARGON2_VERSION715        log.debug("detected 'argon2_cffi' backend, version %r, with support for 0x%x argon2 hashes",716                  _argon2_cffi.__version__, max_version)717 718        # build type map719        TypeEnum = _argon2_cffi.Type720        type_map = {}721        for type in ALL_TYPES:722            try:723                type_map[type] = getattr(TypeEnum, type.upper())724            except AttributeError:725                # TYPE_ID support not added until v18.2726                assert type not in (TYPE_I, TYPE_D), "unexpected missing type: %r" % type727        mixin_cls._backend_type_map = type_map728 729        # set version info, and run common setup730        mixin_cls.version = mixin_cls.max_version = max_version731        return mixin_cls._finalize_backend_mixin(name, dryrun)732 733    #===================================================================734    # primary methods735    #===================================================================736    @classmethod737    def hash(cls, secret):738        # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.739        uh.validate_secret(secret)740        secret = to_bytes(secret, "utf-8")741        # XXX: doesn't seem to be a way to make this honor max_threads742        try:743            return bascii_to_str(_argon2_cffi.low_level.hash_secret(744                type=cls._get_backend_type(cls.type),745                memory_cost=cls.memory_cost,746                time_cost=cls.default_rounds,747                parallelism=cls.parallelism,748                salt=to_bytes(cls._generate_salt()),749                hash_len=cls.checksum_size,750                secret=secret,751            ))752        except _argon2_cffi.exceptions.HashingError as err:753            raise cls._adapt_backend_error(err)754 755    #: helper for verify() method below -- maps prefixes to type constants756    _byte_ident_map = dict((render_bytes(b"$argon2%s$", type.encode("ascii")), type)757                           for type in ALL_TYPES)758 759    @classmethod760    def verify(cls, secret, hash):761        # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.762        uh.validate_secret(secret)763        secret = to_bytes(secret, "utf-8")764        hash = to_bytes(hash, "ascii")765 766        # read type from start of hash767        # NOTE: don't care about malformed strings, lowlevel will throw error for us768        type = cls._byte_ident_map.get(hash[:1+hash.find(b"$", 1)], TYPE_I)769        type_code = cls._get_backend_type(type)770 771        # XXX: doesn't seem to be a way to make this honor max_threads772        try:773            result = _argon2_cffi.low_level.verify_secret(hash, secret, type_code)774            assert result is True775            return True776        except _argon2_cffi.exceptions.VerifyMismatchError:777            return False778        except _argon2_cffi.exceptions.VerificationError as err:779            raise cls._adapt_backend_error(err, hash=hash)780 781    # NOTE: deprecated, will be removed in 2.0782    @classmethod783    def genhash(cls, secret, config):784        # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.785        uh.validate_secret(secret)786        secret = to_bytes(secret, "utf-8")787        self = cls.from_string(config)788        # XXX: doesn't seem to be a way to make this honor max_threads789        try:790            result = bascii_to_str(_argon2_cffi.low_level.hash_secret(791                type=cls._get_backend_type(self.type),792                memory_cost=self.memory_cost,793                time_cost=self.rounds,794                parallelism=self.parallelism,795                salt=to_bytes(self.salt),796                hash_len=self.checksum_size,797                secret=secret,798                version=self.version,799            ))800        except _argon2_cffi.exceptions.HashingError as err:801            raise cls._adapt_backend_error(err, hash=config)802        if self.version == 0x10:803            # workaround: argon2 0x13 always returns "v=" segment, even for 0x10 hashes804            result = result.replace("$v=16$", "$")805        return result806 807    #===================================================================808    # digest calculation809    #===================================================================810    def _calc_checksum(self, secret):811        raise AssertionError("shouldn't be called under argon2_cffi backend")812 813    #===================================================================814    # eoc815    #===================================================================816 817#-----------------------------------------------------------------------818# argon2pure backend819#-----------------------------------------------------------------------820class _PureBackend(_Argon2Common):821    """822    argon2pure backend823    """824    #===================================================================825    # backend loading826    #===================================================================827 828    @classmethod829    def _load_backend_mixin(mixin_cls, name, dryrun):830        # make sure we write info to base class's __dict__, not that of a subclass831        assert mixin_cls is _PureBackend832 833        # import argon2pure834        global _argon2pure835        try:836            import argon2pure as _argon2pure837        except ImportError:838            return False839 840        # get default / max supported version -- added in v1.2.2841        try:842            from argon2pure import ARGON2_DEFAULT_VERSION as max_version843        except ImportError:844            log.warning("detected 'argon2pure' backend, but package is too old "845                        "(passlib requires argon2pure >= 1.2.3)")846            return False847 848        log.debug("detected 'argon2pure' backend, with support for 0x%x argon2 hashes",849                  max_version)850 851        if not dryrun:852            warn("Using argon2pure backend, which is 100x+ slower than is required "853                 "for adequate security. Installing argon2_cffi (via 'pip install argon2_cffi') "854                 "is strongly recommended", exc.PasslibSecurityWarning)855 856        # build type map857        type_map = {}858        for type in ALL_TYPES:859            try:860                type_map[type] = getattr(_argon2pure, "ARGON2" + type.upper())861            except AttributeError:862                # TYPE_ID support not added until v1.3863                assert type not in (TYPE_I, TYPE_D), "unexpected missing type: %r" % type864        mixin_cls._backend_type_map = type_map865 866        mixin_cls.version = mixin_cls.max_version = max_version867        return mixin_cls._finalize_backend_mixin(name, dryrun)868 869    #===================================================================870    # primary methods871    #===================================================================872 873    # NOTE: this backend uses default .hash() & .verify() implementations.874 875    #===================================================================876    # digest calculation877    #===================================================================878    def _calc_checksum(self, secret):879        # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.880        uh.validate_secret(secret)881        secret = to_bytes(secret, "utf-8")882        kwds = dict(883            password=secret,884            salt=self.salt,885            time_cost=self.rounds,886            memory_cost=self.memory_cost,887            parallelism=self.parallelism,888            tag_length=self.checksum_size,889            type_code=self._get_backend_type(self.type),890            version=self.version,891        )892        if self.max_threads > 0:893            kwds['threads'] = self.max_threads894        if self.pure_use_threads:895            kwds['use_threads'] = True896        if self.data:897            kwds['associated_data'] = self.data898        # NOTE: should return raw bytes899        # NOTE: this may raise _argon2pure.Argon2ParameterError,900        #       but it if does that, there's a bug in our own parameter checking code.901        try:902            return _argon2pure.argon2(**kwds)903        except _argon2pure.Argon2Error as err:904            raise self._adapt_backend_error(err, self=self)905 906    #===================================================================907    # eoc908    #===================================================================909 910class argon2(_NoBackend, _Argon2Common):911    """912    This class implements the Argon2 password hash [#argon2-home]_, and follows the :ref:`password-hash-api`.913 914    Argon2 supports a variable-length salt, and variable time & memory cost,915    and a number of other configurable parameters.916 917    The :meth:`~passlib.ifc.PasswordHash.replace` method accepts the following optional keywords:918 919    :type type: str920    :param type:921        Specify the type of argon2 hash to generate.922        Can be one of "ID", "I", "D".923 924        This defaults to "ID" if supported by the backend, otherwise "I".925 926    :type salt: str927    :param salt:928        Optional salt string.929        If specified, the length must be between 0-1024 bytes.930        If not specified, one will be auto-generated (this is recommended).931 932    :type salt_size: int933    :param salt_size:934        Optional number of bytes to use when autogenerating new salts.935 936    :type rounds: int937    :param rounds:938        Optional number of rounds to use.939        This corresponds linearly to the amount of time hashing will take.940 941    :type time_cost: int942    :param time_cost:943        An alias for **rounds**, for compatibility with underlying argon2 library.944 945    :param int memory_cost:946        Defines the memory usage in kibibytes.947        This corresponds linearly to the amount of memory hashing will take.948 949    :param int parallelism:950        Defines the parallelization factor.951        *NOTE: this will affect the resulting hash value.*952 953    :param int digest_size:954        Length of the digest in bytes.955 956    :param int max_threads:957        Maximum number of threads that will be used.958        -1 means unlimited; otherwise hashing will use ``min(parallelism, max_threads)`` threads.959 960        .. note::961 962            This option is currently only honored by the argon2pure backend.963 964    :type relaxed: bool965    :param relaxed:966        By default, providing an invalid value for one of the other967        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,968        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`969        will be issued instead. Correctable errors include ``rounds``970        that are too small or too large, and ``salt`` strings that are too long.971 972    .. versionchanged:: 1.7.2973 974        Added the "type" keyword, and support for type "D" and "ID" hashes.975        (Prior versions could verify type "D" hashes, but not generate them).976 977    .. todo::978 979        * Support configurable threading limits.980    """981    #=============================================================================982    # backend983    #=============================================================================984 985    # NOTE: the brunt of the argon2 class is implemented in _Argon2Common.986    #       there are then subclass for each backend (e.g. _PureBackend),987    #       these are dynamically prepended to this class's bases988    #       in order to load the appropriate backend.989 990    #: list of potential backends991    backends = ("argon2_cffi", "argon2pure")992 993    #: flag that this class's bases should be modified by SubclassBackendMixin994    _backend_mixin_target = True995 996    #: map of backend -> mixin class, used by _get_backend_loader()997    _backend_mixin_map = {998        None: _NoBackend,999        "argon2_cffi": _CffiBackend,1000        "argon2pure": _PureBackend,1001    }1002 1003    #=============================================================================1004    #1005    #=============================================================================1006 1007#=============================================================================1008# eof1009#=============================================================================1010 
codekingpro/portable-devtools · Team Ai