_password_hasher.py 8.6 KB

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