Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/security.py: 21%

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

62 statements  

1from __future__ import annotations 

2 

3import hashlib 

4import hmac 

5import os 

6import posixpath 

7import secrets 

8 

9DEFAULT_PBKDF2_ITERATIONS = 1_000_000 

10 

11_os_alt_seps: list[str] = list( 

12 sep for sep in [os.sep, os.altsep] if sep is not None and sep != "/" 

13) 

14# https://chrisdenton.github.io/omnipath/Special%20Dos%20Device%20Names.html 

15_windows_device_files = { 

16 "AUX", 

17 "CON", 

18 "CONIN$", 

19 "CONOUT$", 

20 *(f"COM{c}" for c in "123456789¹²³"), 

21 *(f"LPT{c}" for c in "123456789¹²³"), 

22 "NUL", 

23 "PRN", 

24} 

25 

26 

27def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]: 

28 method, *args = method.split(":") 

29 salt_bytes = salt.encode() 

30 password_bytes = password.encode() 

31 

32 # Security note: If the method contains args, they came from a stored, 

33 # trusted hash previously generated by generate_password_hash. An attacker 

34 # who could modify them can already compromise the entire application. 

35 

36 if method == "scrypt": 

37 if not args: 

38 n = 2**15 

39 r = 8 

40 p = 1 

41 else: 

42 try: 

43 n, r, p = map(int, args) 

44 except ValueError: 

45 raise ValueError("'scrypt' takes 3 arguments.") from None 

46 

47 maxmem = 132 * n * r * p # ideally 128, but some extra seems needed 

48 return ( 

49 hashlib.scrypt( 

50 password_bytes, salt=salt_bytes, n=n, r=r, p=p, maxmem=maxmem 

51 ).hex(), 

52 f"scrypt:{n}:{r}:{p}", 

53 ) 

54 elif method == "pbkdf2": 

55 len_args = len(args) 

56 

57 if len_args == 0: 

58 hash_name = "sha256" 

59 iterations = DEFAULT_PBKDF2_ITERATIONS 

60 elif len_args == 1: 

61 hash_name = args[0] 

62 iterations = DEFAULT_PBKDF2_ITERATIONS 

63 elif len_args == 2: 

64 hash_name = args[0] 

65 iterations = int(args[1]) 

66 else: 

67 raise ValueError("'pbkdf2' takes 2 arguments.") 

68 

69 return ( 

70 hashlib.pbkdf2_hmac( 

71 hash_name, password_bytes, salt_bytes, iterations 

72 ).hex(), 

73 f"pbkdf2:{hash_name}:{iterations}", 

74 ) 

75 else: 

76 raise ValueError(f"Invalid hash method '{method}'.") 

77 

78 

79def generate_password_hash( 

80 password: str, method: str = "scrypt", salt_length: int = 16 

81) -> str: 

82 """Securely hash a password for storage. A password can be compared to a stored hash 

83 using :func:`check_password_hash`. 

84 

85 The following methods are supported: 

86 

87 - ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default 

88 is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`. 

89 - ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``, 

90 the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`. 

91 

92 Default parameters may be updated to reflect current guidelines, and methods may be 

93 deprecated and removed if they are no longer considered secure. To migrate old 

94 hashes, you may generate a new hash when checking an old hash, or you may contact 

95 users with a link to reset their password. 

96 

97 :param password: The plaintext password. 

98 :param method: The key derivation function and parameters. 

99 :param salt_length: The number of characters to generate for the salt. 

100 

101 .. versionchanged:: 3.1 

102 The default iterations for pbkdf2 was increased to 1,000,000. 

103 

104 .. versionchanged:: 3.0 

105 All plain hashes are no longer supported. 

106 

107 .. versionchanged:: 2.3 

108 Scrypt support was added. 

109 

110 .. versionchanged:: 2.3 

111 The default iterations for pbkdf2 was increased to 600,000. 

112 """ 

113 if salt_length <= 0: 

114 raise ValueError("Salt length must be at least 1.") 

115 

116 salt = secrets.token_urlsafe(salt_length) 

117 h, actual_method = _hash_internal(method, salt, password) 

118 return f"{actual_method}${salt}${h}" 

119 

120 

121def check_password_hash(pwhash: str, password: str) -> bool: 

122 """Securely check that the given stored password hash, previously generated using 

123 :func:`generate_password_hash`, matches the given password. 

124 

125 Methods may be deprecated and removed if they are no longer considered secure. To 

126 migrate old hashes, you may generate a new hash when checking an old hash, or you 

127 may contact users with a link to reset their password. 

128 

129 :param pwhash: The hashed password. 

130 :param password: The plaintext password. 

131 

132 .. versionchanged:: 3.0 

133 All plain hashes are no longer supported. 

134 """ 

135 try: 

136 method, salt, hashval = pwhash.split("$", 2) 

137 except ValueError: 

138 return False 

139 

140 return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval) 

141 

142 

143def safe_join(directory: str, *untrusted: str) -> str | None: 

144 """Safely join zero or more untrusted path components to a trusted base 

145 directory to avoid escaping the base directory. Return ``None`` if the path 

146 is not safe. 

147 

148 The untrusted path is assumed to be from/for a URL, such as for serving 

149 files. Therefore, it should only use the forward slash ``/`` path separator, 

150 and will be joined using that separator. On Windows, the backslash ``\\`` 

151 separator is not allowed. 

152 

153 This only considers the path name, it does not validate if it does or does 

154 not exist, if it is a symlink, or other properties of the file or 

155 filesystem. It's up to the application to ensure that the contents of 

156 ``directory`` can be trusted. 

157 

158 On Windows, special device names such as ``CON`` and ``NUL`` are not 

159 allowed, as this is used for serving files with :func:`.send_from_directory` 

160 and those files will hang when read. 

161 

162 NTFS alternate data streams (ADS) like ``filename:stream`` are 

163 allowed. :func:`.secure_filename` will remove the ``:`` when saving. 

164 

165 :param directory: The trusted base directory. 

166 :param untrusted: The untrusted path components relative to the 

167 base directory. 

168 :return: A safe path, otherwise ``None``. 

169 

170 .. versionchanged:: 3.1.9 

171 Special device names with empty ADS stream markers are not allowed on 

172 Windows. 

173 

174 .. versionchanged:: 3.1.6 

175 Special device names in multi-segment paths are not allowed on Windows. 

176 

177 .. versionchanged:: 3.1.5 

178 More special device names, regardless of extension or trailing spaces, 

179 are not allowed on Windows. 

180 

181 .. versionchanged:: 3.1.4 

182 Special device names are not allowed on Windows. 

183 """ 

184 if not directory: 

185 # Ensure we end up with ./path if directory="" is given, 

186 # otherwise the first untrusted part could become trusted. 

187 directory = "." 

188 

189 parts = [directory] 

190 

191 for part in untrusted: 

192 if not part: 

193 continue 

194 

195 part = posixpath.normpath(part) 

196 

197 if ( 

198 os.path.isabs(part) 

199 # ntpath.isabs doesn't catch this 

200 or part.startswith("/") 

201 or part == ".." 

202 or part.startswith("../") 

203 or any(sep in part for sep in _os_alt_seps) 

204 or ( 

205 os.name == "nt" 

206 and any( 

207 p.partition(":")[0].partition(".")[0].strip().upper() 

208 in _windows_device_files 

209 for p in part.split("/") 

210 ) 

211 ) 

212 ): 

213 return None 

214 

215 parts.append(part) 

216 

217 return posixpath.join(*parts)