security.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import hashlib
  2. import hmac
  3. import os
  4. import posixpath
  5. import secrets
  6. import typing as t
  7. import warnings
  8. if t.TYPE_CHECKING:
  9. pass
  10. SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  11. DEFAULT_PBKDF2_ITERATIONS = 260000
  12. _os_alt_seps: t.List[str] = list(
  13. sep for sep in [os.path.sep, os.path.altsep] if sep is not None and sep != "/"
  14. )
  15. def pbkdf2_hex(
  16. data: t.Union[str, bytes],
  17. salt: t.Union[str, bytes],
  18. iterations: int = DEFAULT_PBKDF2_ITERATIONS,
  19. keylen: t.Optional[int] = None,
  20. hashfunc: t.Optional[t.Union[str, t.Callable]] = None,
  21. ) -> str:
  22. """Like :func:`pbkdf2_bin`, but returns a hex-encoded string.
  23. :param data: the data to derive.
  24. :param salt: the salt for the derivation.
  25. :param iterations: the number of iterations.
  26. :param keylen: the length of the resulting key. If not provided,
  27. the digest size will be used.
  28. :param hashfunc: the hash function to use. This can either be the
  29. string name of a known hash function, or a function
  30. from the hashlib module. Defaults to sha256.
  31. .. deprecated:: 2.0
  32. Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac`
  33. instead.
  34. .. versionadded:: 0.9
  35. """
  36. warnings.warn(
  37. "'pbkdf2_hex' is deprecated and will be removed in Werkzeug"
  38. " 2.1. Use 'hashlib.pbkdf2_hmac().hex()' instead.",
  39. DeprecationWarning,
  40. stacklevel=2,
  41. )
  42. return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).hex()
  43. def pbkdf2_bin(
  44. data: t.Union[str, bytes],
  45. salt: t.Union[str, bytes],
  46. iterations: int = DEFAULT_PBKDF2_ITERATIONS,
  47. keylen: t.Optional[int] = None,
  48. hashfunc: t.Optional[t.Union[str, t.Callable]] = None,
  49. ) -> bytes:
  50. """Returns a binary digest for the PBKDF2 hash algorithm of `data`
  51. with the given `salt`. It iterates `iterations` times and produces a
  52. key of `keylen` bytes. By default, SHA-256 is used as hash function;
  53. a different hashlib `hashfunc` can be provided.
  54. :param data: the data to derive.
  55. :param salt: the salt for the derivation.
  56. :param iterations: the number of iterations.
  57. :param keylen: the length of the resulting key. If not provided
  58. the digest size will be used.
  59. :param hashfunc: the hash function to use. This can either be the
  60. string name of a known hash function or a function
  61. from the hashlib module. Defaults to sha256.
  62. .. deprecated:: 2.0
  63. Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac`
  64. instead.
  65. .. versionadded:: 0.9
  66. """
  67. warnings.warn(
  68. "'pbkdf2_bin' is deprecated and will be removed in Werkzeug"
  69. " 2.1. Use 'hashlib.pbkdf2_hmac()' instead.",
  70. DeprecationWarning,
  71. stacklevel=2,
  72. )
  73. if isinstance(data, str):
  74. data = data.encode("utf8")
  75. if isinstance(salt, str):
  76. salt = salt.encode("utf8")
  77. if not hashfunc:
  78. hash_name = "sha256"
  79. elif callable(hashfunc):
  80. hash_name = hashfunc().name
  81. else:
  82. hash_name = hashfunc
  83. return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen)
  84. def safe_str_cmp(a: str, b: str) -> bool:
  85. """This function compares strings in somewhat constant time. This
  86. requires that the length of at least one string is known in advance.
  87. Returns `True` if the two strings are equal, or `False` if they are not.
  88. .. deprecated:: 2.0
  89. Will be removed in Werkzeug 2.1. Use
  90. :func:`hmac.compare_digest` instead.
  91. .. versionadded:: 0.7
  92. """
  93. warnings.warn(
  94. "'safe_str_cmp' is deprecated and will be removed in Werkzeug"
  95. " 2.1. Use 'hmac.compare_digest' instead.",
  96. DeprecationWarning,
  97. stacklevel=2,
  98. )
  99. if isinstance(a, str):
  100. a = a.encode("utf-8") # type: ignore
  101. if isinstance(b, str):
  102. b = b.encode("utf-8") # type: ignore
  103. return hmac.compare_digest(a, b)
  104. def gen_salt(length: int) -> str:
  105. """Generate a random string of SALT_CHARS with specified ``length``."""
  106. if length <= 0:
  107. raise ValueError("Salt length must be positive")
  108. return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
  109. def _hash_internal(method: str, salt: str, password: str) -> t.Tuple[str, str]:
  110. """Internal password hash helper. Supports plaintext without salt,
  111. unsalted and salted passwords. In case salted passwords are used
  112. hmac is used.
  113. """
  114. if method == "plain":
  115. return password, method
  116. salt = salt.encode("utf-8")
  117. password = password.encode("utf-8")
  118. if method.startswith("pbkdf2:"):
  119. if not salt:
  120. raise ValueError("Salt is required for PBKDF2")
  121. args = method[7:].split(":")
  122. if len(args) not in (1, 2):
  123. raise ValueError("Invalid number of arguments for PBKDF2")
  124. method = args.pop(0)
  125. iterations = int(args[0] or 0) if args else DEFAULT_PBKDF2_ITERATIONS
  126. return (
  127. hashlib.pbkdf2_hmac(method, password, salt, iterations).hex(),
  128. f"pbkdf2:{method}:{iterations}",
  129. )
  130. if salt:
  131. return hmac.new(salt, password, method).hexdigest(), method
  132. return hashlib.new(method, password).hexdigest(), method
  133. def generate_password_hash(
  134. password: str, method: str = "pbkdf2:sha256", salt_length: int = 16
  135. ) -> str:
  136. """Hash a password with the given method and salt with a string of
  137. the given length. The format of the string returned includes the method
  138. that was used so that :func:`check_password_hash` can check the hash.
  139. The format for the hashed string looks like this::
  140. method$salt$hash
  141. This method can **not** generate unsalted passwords but it is possible
  142. to set param method='plain' in order to enforce plaintext passwords.
  143. If a salt is used, hmac is used internally to salt the password.
  144. If PBKDF2 is wanted it can be enabled by setting the method to
  145. ``pbkdf2:method:iterations`` where iterations is optional::
  146. pbkdf2:sha256:80000$salt$hash
  147. pbkdf2:sha256$salt$hash
  148. :param password: the password to hash.
  149. :param method: the hash method to use (one that hashlib supports). Can
  150. optionally be in the format ``pbkdf2:method:iterations``
  151. to enable PBKDF2.
  152. :param salt_length: the length of the salt in letters.
  153. """
  154. salt = gen_salt(salt_length) if method != "plain" else ""
  155. h, actual_method = _hash_internal(method, salt, password)
  156. return f"{actual_method}${salt}${h}"
  157. def check_password_hash(pwhash: str, password: str) -> bool:
  158. """Check a password against a given salted and hashed password value.
  159. In order to support unsalted legacy passwords this method supports
  160. plain text passwords, md5 and sha1 hashes (both salted and unsalted).
  161. Returns `True` if the password matched, `False` otherwise.
  162. :param pwhash: a hashed string like returned by
  163. :func:`generate_password_hash`.
  164. :param password: the plaintext password to compare against the hash.
  165. """
  166. if pwhash.count("$") < 2:
  167. return False
  168. method, salt, hashval = pwhash.split("$", 2)
  169. return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)
  170. def safe_join(directory: str, *pathnames: str) -> t.Optional[str]:
  171. """Safely join zero or more untrusted path components to a base
  172. directory to avoid escaping the base directory.
  173. :param directory: The trusted base directory.
  174. :param pathnames: The untrusted path components relative to the
  175. base directory.
  176. :return: A safe path, otherwise ``None``.
  177. """
  178. parts = [directory]
  179. for filename in pathnames:
  180. if filename != "":
  181. filename = posixpath.normpath(filename)
  182. if (
  183. any(sep in filename for sep in _os_alt_seps)
  184. or os.path.isabs(filename)
  185. or filename == ".."
  186. or filename.startswith("../")
  187. ):
  188. return None
  189. parts.append(filename)
  190. return posixpath.join(*parts)