aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/werkzeug/security.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.8/site-packages/werkzeug/security.py')
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/security.py163
1 files changed, 163 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/werkzeug/security.py b/venv/lib/python3.8/site-packages/werkzeug/security.py
new file mode 100644
index 0000000..9975979
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/security.py
@@ -0,0 +1,163 @@
+from __future__ import annotations
+
+import hashlib
+import hmac
+import os
+import posixpath
+import secrets
+
+SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+DEFAULT_PBKDF2_ITERATIONS = 600000
+
+_os_alt_seps: list[str] = list(
+ sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/"
+)
+
+
+def gen_salt(length: int) -> str:
+ """Generate a random string of SALT_CHARS with specified ``length``."""
+ if length <= 0:
+ raise ValueError("Salt length must be at least 1.")
+
+ return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
+
+
+def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
+ method, *args = method.split(":")
+ salt_bytes = salt.encode()
+ password_bytes = password.encode()
+
+ if method == "scrypt":
+ if not args:
+ n = 2**15
+ r = 8
+ p = 1
+ else:
+ try:
+ n, r, p = map(int, args)
+ except ValueError:
+ raise ValueError("'scrypt' takes 3 arguments.") from None
+
+ maxmem = 132 * n * r * p # ideally 128, but some extra seems needed
+ return (
+ hashlib.scrypt(
+ password_bytes, salt=salt_bytes, n=n, r=r, p=p, maxmem=maxmem
+ ).hex(),
+ f"scrypt:{n}:{r}:{p}",
+ )
+ elif method == "pbkdf2":
+ len_args = len(args)
+
+ if len_args == 0:
+ hash_name = "sha256"
+ iterations = DEFAULT_PBKDF2_ITERATIONS
+ elif len_args == 1:
+ hash_name = args[0]
+ iterations = DEFAULT_PBKDF2_ITERATIONS
+ elif len_args == 2:
+ hash_name = args[0]
+ iterations = int(args[1])
+ else:
+ raise ValueError("'pbkdf2' takes 2 arguments.")
+
+ return (
+ hashlib.pbkdf2_hmac(
+ hash_name, password_bytes, salt_bytes, iterations
+ ).hex(),
+ f"pbkdf2:{hash_name}:{iterations}",
+ )
+ else:
+ raise ValueError(f"Invalid hash method '{method}'.")
+
+
+def generate_password_hash(
+ password: str, method: str = "scrypt", salt_length: int = 16
+) -> str:
+ """Securely hash a password for storage. A password can be compared to a stored hash
+ using :func:`check_password_hash`.
+
+ The following methods are supported:
+
+ - ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default
+ is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`.
+ - ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``,
+ the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`.
+
+ Default parameters may be updated to reflect current guidelines, and methods may be
+ deprecated and removed if they are no longer considered secure. To migrate old
+ hashes, you may generate a new hash when checking an old hash, or you may contact
+ users with a link to reset their password.
+
+ :param password: The plaintext password.
+ :param method: The key derivation function and parameters.
+ :param salt_length: The number of characters to generate for the salt.
+
+ .. versionchanged:: 2.3
+ Scrypt support was added.
+
+ .. versionchanged:: 2.3
+ The default iterations for pbkdf2 was increased to 600,000.
+
+ .. versionchanged:: 2.3
+ All plain hashes are deprecated and will not be supported in Werkzeug 3.0.
+ """
+ salt = gen_salt(salt_length)
+ h, actual_method = _hash_internal(method, salt, password)
+ return f"{actual_method}${salt}${h}"
+
+
+def check_password_hash(pwhash: str, password: str) -> bool:
+ """Securely check that the given stored password hash, previously generated using
+ :func:`generate_password_hash`, matches the given password.
+
+ Methods may be deprecated and removed if they are no longer considered secure. To
+ migrate old hashes, you may generate a new hash when checking an old hash, or you
+ may contact users with a link to reset their password.
+
+ :param pwhash: The hashed password.
+ :param password: The plaintext password.
+
+ .. versionchanged:: 2.3
+ All plain hashes are deprecated and will not be supported in Werkzeug 3.0.
+ """
+ try:
+ method, salt, hashval = pwhash.split("$", 2)
+ except ValueError:
+ return False
+
+ return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)
+
+
+def safe_join(directory: str, *pathnames: str) -> str | None:
+ """Safely join zero or more untrusted path components to a base
+ directory to avoid escaping the base directory.
+
+ :param directory: The trusted base directory.
+ :param pathnames: The untrusted path components relative to the
+ base directory.
+ :return: A safe path, otherwise ``None``.
+ """
+ if not directory:
+ # Ensure we end up with ./path if directory="" is given,
+ # otherwise the first untrusted part could become trusted.
+ directory = "."
+
+ parts = [directory]
+
+ for filename in pathnames:
+ if filename != "":
+ filename = posixpath.normpath(filename)
+
+ if (
+ any(sep in filename for sep in _os_alt_seps)
+ or os.path.isabs(filename)
+ # ntpath.isabs doesn't catch this on Python < 3.11
+ or filename.startswith("/")
+ or filename == ".."
+ or filename.startswith("../")
+ ):
+ return None
+
+ parts.append(filename)
+
+ return posixpath.join(*parts)