Skip to content

Commit 5957092

Browse files
authored
Align hashlib and HMAC with Python 3.15 (#9170)
Warn on the deprecated shared string keyword and refresh the complete hashing libraries, canonical suites, and shared helper from CPython v3.15.0 (d220409a13b77de7a6c30abe16ea90db01528edc) with update_lib. Keep the six-file family independent and add no RustPython test guards. Assisted-by: OpenAI Codex:model version unavailable
1 parent fb76135 commit 5957092

6 files changed

Lines changed: 1309 additions & 346 deletions

File tree

‎Lib/hashlib.py‎

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Licensed to PSF under a Contributor Agreement.
33
#
44

5-
__doc__ = """hashlib module - A common interface to many hash functions.
5+
__doc__ = r"""hashlib module - A common interface to many hash functions.
66
77
new(name, data=b'', **kwargs) - returns a new hash object implementing the
88
given hash function; initializing the hash
@@ -12,7 +12,7 @@
1212
than using new(name):
1313
1414
md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(),
15-
sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256.
15+
sha3_224(), sha3_256(), sha3_384(), sha3_512(), shake_128(), and shake_256().
1616
1717
More algorithms may be available on your platform but the above are guaranteed
1818
to exist. See the algorithms_guaranteed and algorithms_available attributes
@@ -21,8 +21,8 @@
2121
NOTE: If you want the adler32 or crc32 hash functions they are available in
2222
the zlib module.
2323
24-
Choose your hash function wisely. Some have known collision weaknesses.
25-
sha384 and sha512 will be slow on 32 bit platforms.
24+
Choose your hash function wisely. Some have known collision weaknesses,
25+
while others may be slower depending on the CPU architecture.
2626
2727
Hash objects have these methods:
2828
- update(data): Update the hash object with the bytes in data. Repeated calls
@@ -36,21 +36,21 @@
3636
efficiently compute the digests of data that share a common
3737
initial substring.
3838
39-
For example, to obtain the digest of the byte string 'Nobody inspects the
40-
spammish repetition':
39+
Assuming that Python has been built with SHA-2 support, the SHA-256 digest
40+
of the byte string b'Nobody inspects the spammish repetition' is computed
41+
as follows:
4142
4243
>>> import hashlib
43-
>>> m = hashlib.md5()
44+
>>> m = hashlib.sha256()
4445
>>> m.update(b"Nobody inspects")
4546
>>> m.update(b" the spammish repetition")
46-
>>> m.digest()
47-
b'\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9'
47+
>>> m.digest() # doctest: +ELLIPSIS
48+
b'\x03\x1e\xdd}Ae\x15\x93\xc5\xfe\\\x00o\xa5u+7...'
4849
4950
More condensed:
5051
51-
>>> hashlib.sha224(b"Nobody inspects the spammish repetition").hexdigest()
52-
'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2'
53-
52+
>>> hashlib.sha256(b"Nobody inspects the spammish repetition").hexdigest()
53+
'031edd7d41651593c5fe5c006fa5752b37fddff7bc4e843aa6af0c950f4b9406'
5454
"""
5555

5656
# This tuple and __get_builtin_constructor() must be modified if a new
@@ -80,6 +80,11 @@
8080
}
8181

8282
def __get_builtin_constructor(name):
83+
if not isinstance(name, str):
84+
# Since this function is only used by new(), we use the same
85+
# exception as _hashlib.new() when 'name' is of incorrect type.
86+
err = f"new() argument 'name' must be str, not {type(name).__name__}"
87+
raise TypeError(err)
8388
cache = __builtin_constructor_cache
8489
constructor = cache.get(name)
8590
if constructor is not None:
@@ -120,20 +125,33 @@ def __get_builtin_constructor(name):
120125
if constructor is not None:
121126
return constructor
122127

123-
raise ValueError('unsupported hash type ' + name)
128+
# Keep the message in sync with hashlib.h::HASHLIB_UNSUPPORTED_ALGORITHM.
129+
raise ValueError(f'unsupported hash algorithm {name}')
124130

125131

126132
def __get_openssl_constructor(name):
133+
# This function is only used until the module has been initialized.
134+
assert isinstance(name, str), "invalid call to __get_openssl_constructor()"
127135
if name in __block_openssl_constructor:
128136
# Prefer our builtin blake2 implementation.
129137
return __get_builtin_constructor(name)
130138
try:
131-
# MD5, SHA1, and SHA2 are in all supported OpenSSL versions
132-
# SHA3/shake are available in OpenSSL 1.1.1+
139+
# Fetch the OpenSSL hash function if it exists,
140+
# independently of the context security policy.
133141
f = getattr(_hashlib, 'openssl_' + name)
134-
# Allow the C module to raise ValueError. The function will be
135-
# defined but the hash not actually available. Don't fall back to
136-
# builtin if the current security policy blocks a digest, bpo#40695.
142+
# Check if the context security policy blocks the digest or not
143+
# by allowing the C module to raise a ValueError. The function
144+
# will be defined but the hash will not be available at runtime.
145+
#
146+
# We use "usedforsecurity=False" to prevent falling back to the
147+
# built-in function in case the security policy does not allow it.
148+
#
149+
# Note that this only affects the explicit named constructors,
150+
# and not the algorithms exposed through hashlib.new() which
151+
# can still be resolved to a built-in function even if the
152+
# current security policy does not allow it.
153+
#
154+
# See https://github.com/python/cpython/issues/84872.
137155
f(usedforsecurity=False)
138156
# Use the C function directly (very fast)
139157
return f
@@ -154,6 +172,8 @@ def __hash_new(name, *args, **kwargs):
154172
optionally initialized with data (which must be a bytes-like object).
155173
"""
156174
if name in __block_openssl_constructor:
175+
# __block_openssl_constructor is expected to contain strings only
176+
assert isinstance(name, str), f"unexpected name: {name}"
157177
# Prefer our builtin blake2 implementation.
158178
return __get_builtin_constructor(name)(*args, **kwargs)
159179
try:
@@ -187,7 +207,8 @@ def __hash_new(name, *args, **kwargs):
187207

188208
try:
189209
# OpenSSL's scrypt requires OpenSSL 1.1+
190-
from _hashlib import scrypt # noqa: F401
210+
from _hashlib import scrypt
211+
__all__ += ('scrypt',)
191212
except ImportError:
192213
pass
193214

@@ -203,7 +224,7 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
203224
*digest* must either be a hash algorithm name as a *str*, a hash
204225
constructor, or a callable that returns a hash object.
205226
"""
206-
# On Linux we could use AF_ALG sockets and sendfile() to archive zero-copy
227+
# On Linux we could use AF_ALG sockets and sendfile() to achieve zero-copy
207228
# hashing with hardware acceleration.
208229
if isinstance(digest, str):
209230
digestobj = new(digest)
@@ -246,9 +267,30 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
246267
try:
247268
globals()[__func_name] = __get_hash(__func_name)
248269
except ValueError:
249-
import logging
250-
logging.exception('code for hash %s was not found.', __func_name)
251-
270+
# Don't log here: logging at import time has global side effects and
271+
# would tell the wrong audience; code that uses a missing algorithm
272+
# gets a ValueError from the stub installed below.
273+
# The following code can be simplified in Python 3.19
274+
# once "string" is removed from the signature.
275+
__code = f'''\
276+
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
277+
if data is __UNSET and string is not __UNSET:
278+
import warnings
279+
warnings.warn(
280+
"the 'string' keyword parameter is deprecated since "
281+
"Python 3.15 and slated for removal in Python 3.19; "
282+
"use the 'data' keyword parameter or pass the data "
283+
"to hash as a positional argument instead",
284+
DeprecationWarning, stacklevel=2)
285+
if data is not __UNSET and string is not __UNSET:
286+
raise TypeError("'data' and 'string' are mutually exclusive "
287+
"and support for 'string' keyword parameter "
288+
"is slated for removal in a future version.")
289+
raise ValueError("unsupported hash algorithm {__func_name}")
290+
'''
291+
exec(__code, {"__UNSET": object()}, __locals := {})
292+
globals()[__func_name] = __locals[__func_name]
293+
del __code, __locals
252294

253295
# Cleanup locals()
254296
del __always_supported, __func_name, __get_hash

‎Lib/hmac.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
digest_size = None
2727

2828

29+
def _is_shake_constructor(digest_like):
30+
if isinstance(digest_like, str):
31+
name = digest_like
32+
else:
33+
h = digest_like() if callable(digest_like) else digest_like.new()
34+
if not isinstance(name := getattr(h, "name", None), str):
35+
return False
36+
return name.startswith(("shake", "SHAKE"))
37+
38+
2939
def _get_digest_constructor(digest_like):
3040
if callable(digest_like):
3141
return digest_like
@@ -109,6 +119,8 @@ def _init_old(self, key, msg, digestmod):
109119
import warnings
110120

111121
digest_cons = _get_digest_constructor(digestmod)
122+
if _is_shake_constructor(digest_cons):
123+
raise ValueError(f"unsupported hash algorithm {digestmod}")
112124

113125
self._hmac = None
114126
self._outer = digest_cons()
@@ -256,6 +268,8 @@ def digest(key, msg, digest):
256268

257269
def _compute_digest_fallback(key, msg, digest):
258270
digest_cons = _get_digest_constructor(digest)
271+
if _is_shake_constructor(digest_cons):
272+
raise ValueError(f"unsupported hash algorithm {digest}")
259273
inner = digest_cons()
260274
outer = digest_cons()
261275
blocksize = getattr(inner, 'block_size', 64)

0 commit comments

Comments
 (0)