Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/security.py: 23%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import hashlib
4import hmac
5import os
6import posixpath
7import secrets
9SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
10DEFAULT_PBKDF2_ITERATIONS = 1_000_000
12_os_alt_seps: list[str] = list(
13 sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/"
14)
15_windows_device_files = {
16 "CON",
17 "PRN",
18 "AUX",
19 "NUL",
20 *(f"COM{i}" for i in range(10)),
21 *(f"LPT{i}" for i in range(10)),
22}
25def gen_salt(length: int) -> str:
26 """Generate a random string of SALT_CHARS with specified ``length``."""
27 if length <= 0:
28 raise ValueError("Salt length must be at least 1.")
30 return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
33def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
34 method, *args = method.split(":")
35 salt_bytes = salt.encode()
36 password_bytes = password.encode()
38 if method == "scrypt":
39 if not args:
40 n = 2**15
41 r = 8
42 p = 1
43 else:
44 try:
45 n, r, p = map(int, args)
46 except ValueError:
47 raise ValueError("'scrypt' takes 3 arguments.") from None
49 maxmem = 132 * n * r * p # ideally 128, but some extra seems needed
50 return (
51 hashlib.scrypt(
52 password_bytes, salt=salt_bytes, n=n, r=r, p=p, maxmem=maxmem
53 ).hex(),
54 f"scrypt:{n}:{r}:{p}",
55 )
56 elif method == "pbkdf2":
57 len_args = len(args)
59 if len_args == 0:
60 hash_name = "sha256"
61 iterations = DEFAULT_PBKDF2_ITERATIONS
62 elif len_args == 1:
63 hash_name = args[0]
64 iterations = DEFAULT_PBKDF2_ITERATIONS
65 elif len_args == 2:
66 hash_name = args[0]
67 iterations = int(args[1])
68 else:
69 raise ValueError("'pbkdf2' takes 2 arguments.")
71 return (
72 hashlib.pbkdf2_hmac(
73 hash_name, password_bytes, salt_bytes, iterations
74 ).hex(),
75 f"pbkdf2:{hash_name}:{iterations}",
76 )
77 else:
78 raise ValueError(f"Invalid hash method '{method}'.")
81def generate_password_hash(
82 password: str, method: str = "scrypt", salt_length: int = 16
83) -> str:
84 """Securely hash a password for storage. A password can be compared to a stored hash
85 using :func:`check_password_hash`.
87 The following methods are supported:
89 - ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default
90 is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`.
91 - ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``,
92 the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`.
94 Default parameters may be updated to reflect current guidelines, and methods may be
95 deprecated and removed if they are no longer considered secure. To migrate old
96 hashes, you may generate a new hash when checking an old hash, or you may contact
97 users with a link to reset their password.
99 :param password: The plaintext password.
100 :param method: The key derivation function and parameters.
101 :param salt_length: The number of characters to generate for the salt.
103 .. versionchanged:: 3.1
104 The default iterations for pbkdf2 was increased to 1,000,000.
106 .. versionchanged:: 2.3
107 Scrypt support was added.
109 .. versionchanged:: 2.3
110 The default iterations for pbkdf2 was increased to 600,000.
112 .. versionchanged:: 2.3
113 All plain hashes are deprecated and will not be supported in Werkzeug 3.0.
114 """
115 salt = gen_salt(salt_length)
116 h, actual_method = _hash_internal(method, salt, password)
117 return f"{actual_method}${salt}${h}"
120def check_password_hash(pwhash: str, password: str) -> bool:
121 """Securely check that the given stored password hash, previously generated using
122 :func:`generate_password_hash`, matches the given password.
124 Methods may be deprecated and removed if they are no longer considered secure. To
125 migrate old hashes, you may generate a new hash when checking an old hash, or you
126 may contact users with a link to reset their password.
128 :param pwhash: The hashed password.
129 :param password: The plaintext password.
131 .. versionchanged:: 2.3
132 All plain hashes are deprecated and will not be supported in Werkzeug 3.0.
133 """
134 try:
135 method, salt, hashval = pwhash.split("$", 2)
136 except ValueError:
137 return False
139 return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)
142def safe_join(directory: str, *pathnames: str) -> str | None:
143 """Safely join zero or more untrusted path components to a base
144 directory to avoid escaping the base directory.
146 :param directory: The trusted base directory.
147 :param pathnames: The untrusted path components relative to the
148 base directory.
149 :return: A safe path, otherwise ``None``.
151 .. versionchanged:: 3.1.4
152 Special device names are disallowed on Windows.
153 """
154 if not directory:
155 # Ensure we end up with ./path if directory="" is given,
156 # otherwise the first untrusted part could become trusted.
157 directory = "."
159 parts = [directory]
161 for filename in pathnames:
162 if filename != "":
163 filename = posixpath.normpath(filename)
165 if (
166 any(sep in filename for sep in _os_alt_seps)
167 or (
168 os.name == "nt"
169 and os.path.splitext(filename)[0].upper() in _windows_device_files
170 )
171 or os.path.isabs(filename)
172 # ntpath.isabs doesn't catch this on Python < 3.11
173 or filename.startswith("/")
174 or filename == ".."
175 or filename.startswith("../")
176 ):
177 return None
179 parts.append(filename)
181 return posixpath.join(*parts)