hyb
2025-10-24 43c4449e6c9231446895ad26d169825ca7a65c9a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# SPDX-License-Identifier: MIT
 
from __future__ import annotations
 
import os
 
from typing import ClassVar, Literal
 
from ._utils import (
    Parameters,
    _check_types,
    extract_parameters,
    validate_params_for_platform,
)
from .exceptions import InvalidHashError
from .low_level import Type, hash_secret, verify_secret
from .profiles import get_default_parameters
 
 
default_params = get_default_parameters()
 
DEFAULT_RANDOM_SALT_LENGTH = default_params.salt_len
DEFAULT_HASH_LENGTH = default_params.hash_len
DEFAULT_TIME_COST = default_params.time_cost
DEFAULT_MEMORY_COST = default_params.memory_cost
DEFAULT_PARALLELISM = default_params.parallelism
 
 
def _ensure_bytes(s: bytes | str, encoding: str) -> bytes:
    """
    Ensure *s* is a bytes string.  Encode using *encoding* if it isn't.
    """
    if isinstance(s, bytes):
        return s
    return s.encode(encoding)
 
 
class PasswordHasher:
    r"""
    High level class to hash passwords with sensible defaults.
 
    Uses Argon2\ **id** by default and uses a random salt_ for hashing. But it
    can verify any type of Argon2 as long as the hash is correctly encoded.
 
    The reason for this being a class is both for convenience to carry
    parameters and to verify the parameters only *once*.  Any unnecessary
    slowdown when hashing is a tangible advantage for a brute-force attacker.
 
    Args:
        time_cost:
            Defines the amount of computation realized and therefore the
            execution time, given in number of iterations.
 
        memory_cost: Defines the memory usage, given in kibibytes_.
 
        parallelism:
            Defines the number of parallel threads (*changes* the resulting
            hash value).
 
        hash_len: Length of the hash in bytes.
 
        salt_len: Length of random salt to be generated for each password.
 
        encoding:
            The Argon2 C library expects bytes.  So if :meth:`hash` or
            :meth:`verify` are passed a ``str``, it will be encoded using this
            encoding.
 
        type:
            Argon2 type to use.  Only change for interoperability with legacy
            systems.
 
    .. versionadded:: 16.0.0
    .. versionchanged:: 18.2.0
       Switch from Argon2i to Argon2id based on the recommendation by the
       current RFC draft. See also :doc:`parameters`.
    .. versionchanged:: 18.2.0
       Changed default *memory_cost* to 100 MiB and default *parallelism* to 8.
    .. versionchanged:: 18.2.0 ``verify`` now will determine the type of hash.
    .. versionchanged:: 18.3.0 The Argon2 type is configurable now.
    .. versionadded:: 21.2.0 :meth:`from_parameters`
    .. versionchanged:: 21.2.0
       Changed defaults to :data:`argon2.profiles.RFC_9106_LOW_MEMORY`.
 
    .. _salt: https://en.wikipedia.org/wiki/Salt_(cryptography)
    .. _kibibytes: https://en.wikipedia.org/wiki/Binary_prefix#kibi
    """
 
    __slots__ = ["_parameters", "encoding"]
 
    _parameters: Parameters
    encoding: str
 
    def __init__(
        self,
        time_cost: int = DEFAULT_TIME_COST,
        memory_cost: int = DEFAULT_MEMORY_COST,
        parallelism: int = DEFAULT_PARALLELISM,
        hash_len: int = DEFAULT_HASH_LENGTH,
        salt_len: int = DEFAULT_RANDOM_SALT_LENGTH,
        encoding: str = "utf-8",
        type: Type = Type.ID,
    ):
        e = _check_types(
            time_cost=(time_cost, int),
            memory_cost=(memory_cost, int),
            parallelism=(parallelism, int),
            hash_len=(hash_len, int),
            salt_len=(salt_len, int),
            encoding=(encoding, str),
            type=(type, Type),
        )
        if e:
            raise TypeError(e)
 
        params = Parameters(
            type=type,
            version=19,
            salt_len=salt_len,
            hash_len=hash_len,
            time_cost=time_cost,
            memory_cost=memory_cost,
            parallelism=parallelism,
        )
 
        validate_params_for_platform(params)
 
        # Cache a Parameters object for check_needs_rehash.
        self._parameters = params
        self.encoding = encoding
 
    @classmethod
    def from_parameters(cls, params: Parameters) -> PasswordHasher:
        """
        Construct a `PasswordHasher` from *params*.
 
        Returns:
            A `PasswordHasher` instance with the parameters from *params*.
 
        .. versionadded:: 21.2.0
        """
 
        return cls(
            time_cost=params.time_cost,
            memory_cost=params.memory_cost,
            parallelism=params.parallelism,
            hash_len=params.hash_len,
            salt_len=params.salt_len,
            type=params.type,
        )
 
    @property
    def time_cost(self) -> int:
        return self._parameters.time_cost
 
    @property
    def memory_cost(self) -> int:
        return self._parameters.memory_cost
 
    @property
    def parallelism(self) -> int:
        return self._parameters.parallelism
 
    @property
    def hash_len(self) -> int:
        return self._parameters.hash_len
 
    @property
    def salt_len(self) -> int:
        return self._parameters.salt_len
 
    @property
    def type(self) -> Type:
        return self._parameters.type
 
    def hash(self, password: str | bytes, *, salt: bytes | None = None) -> str:
        """
        Hash *password* and return an encoded hash.
 
        Args:
            password: Password to hash.
 
            salt:
                If None, a random salt is securely created.
 
                .. danger::
 
                    You should **not** pass a salt unless you really know what
                    you are doing.
 
        Raises:
            argon2.exceptions.HashingError: If hashing fails.
 
        Returns:
            Hashed *password*.
 
        .. versionadded:: 23.1.0 *salt* parameter
        """
        return hash_secret(
            secret=_ensure_bytes(password, self.encoding),
            salt=salt or os.urandom(self.salt_len),
            time_cost=self.time_cost,
            memory_cost=self.memory_cost,
            parallelism=self.parallelism,
            hash_len=self.hash_len,
            type=self.type,
        ).decode("ascii")
 
    _header_to_type: ClassVar[dict[bytes, Type]] = {
        b"$argon2i$": Type.I,
        b"$argon2d$": Type.D,
        b"$argon2id": Type.ID,
    }
 
    def verify(
        self, hash: str | bytes, password: str | bytes
    ) -> Literal[True]:
        """
        Verify that *password* matches *hash*.
 
        .. warning::
 
            It is assumed that the caller is in full control of the hash.  No
            other parsing than the determination of the hash type is done by
            *argon2-cffi*.
 
        Args:
            hash: An encoded hash as returned from :meth:`PasswordHasher.hash`.
 
            password: The password to verify.
 
        Raises:
            argon2.exceptions.VerifyMismatchError:
                If verification fails because *hash* is not valid for
                *password*.
 
            argon2.exceptions.VerificationError:
                If verification fails for other reasons.
 
            argon2.exceptions.InvalidHashError:
                If *hash* is so clearly invalid, that it couldn't be passed to
                Argon2.
 
        Returns:
            ``True`` on success, otherwise an exception is raised.
 
        .. versionchanged:: 16.1.0
            Raise :exc:`~argon2.exceptions.VerifyMismatchError` on mismatches
            instead of its more generic superclass.
        .. versionadded:: 18.2.0 Hash type agility.
        """
        hash = _ensure_bytes(hash, "ascii")
        try:
            hash_type = self._header_to_type[hash[:9]]
        except LookupError:
            raise InvalidHashError from None
 
        return verify_secret(
            hash, _ensure_bytes(password, self.encoding), hash_type
        )
 
    def check_needs_rehash(self, hash: str | bytes) -> bool:
        """
        Check whether *hash* was created using the instance's parameters.
 
        Whenever your Argon2 parameters -- or *argon2-cffi*'s defaults! --
        change, you should rehash your passwords at the next opportunity.  The
        common approach is to do that whenever a user logs in, since that
        should be the only time when you have access to the cleartext
        password.
 
        Therefore it's best practice to check -- and if necessary rehash --
        passwords after each successful authentication.
 
        Args:
            hash: An encoded Argon2 password hash.
 
        Returns:
            Whether *hash* was created using the instance's parameters.
 
        .. versionadded:: 18.2.0
        .. versionchanged:: 24.1.0 Accepts bytes for *hash*.
        """
        if isinstance(hash, bytes):
            hash = hash.decode("ascii")
 
        return self._parameters != extract_parameters(hash)