gh-136929: ensure that hashlib.<name> does not raise AttributeError (#136933)

Previously, if OpenSSL was not present and built-in cryptographic extension modules
were disabled, requesting `hashlib.<name>` raised `AttributeError` and an ERROR log
message with the exception traceback is emitted when importing `hashlib`. 

Now, the named constructor function will always be available but raises a `ValueError`
at runtime indicating that the algorithm is not supported. The log message has also
been reworded to be less verbose.
This commit is contained in:
Bénédikt Tran
2025-07-25 16:49:09 +02:00
committed by GitHub
parent ea06ae5b5e
commit 7ce2f101c4
3 changed files with 43 additions and 4 deletions

View File

@@ -261,16 +261,39 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
return digestobj
__logging = None
for __func_name in __always_supported:
# try them all, some may not work due to the OpenSSL
# version not supporting that algorithm.
try:
globals()[__func_name] = __get_hash(__func_name)
except ValueError:
import logging
logging.exception('code for hash %s was not found.', __func_name)
except ValueError as __exc:
import logging as __logging
__logging.error('hash algorithm %s will not be supported at runtime '
'[reason: %s]', __func_name, __exc)
# The following code can be simplified in Python 3.19
# once "string" is removed from the signature.
__code = f'''\
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
if data is __UNSET and string is not __UNSET:
import warnings
warnings.warn(
"the 'string' keyword parameter is deprecated since "
"Python 3.15 and slated for removal in Python 3.19; "
"use the 'data' keyword parameter or pass the data "
"to hash as a positional argument instead",
DeprecationWarning, stacklevel=2)
if data is not __UNSET and string is not __UNSET:
raise TypeError("'data' and 'string' are mutually exclusive "
"and support for 'string' keyword parameter "
"is slated for removal in a future version.")
raise ValueError("unsupported hash algorithm {__func_name}")
'''
exec(__code, {"__UNSET": object()}, __locals := {})
globals()[__func_name] = __locals[__func_name]
del __exc, __code, __locals
# Cleanup locals()
del __always_supported, __func_name, __get_hash
del __py_new, __hash_new, __get_openssl_constructor
del __logging