Skip to content

Commit 7ce2f10

Browse files
authored
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.
1 parent ea06ae5 commit 7ce2f10

File tree

3 files changed

+43
-4
lines changed

3 files changed

+43
-4
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,17 @@ difflib
230230
(Contributed by Jiahao Li in :gh:`134580`.)
231231

232232

233+
hashlib
234+
-------
235+
236+
* Ensure that hash functions guaranteed to be always *available* exist as
237+
attributes of :mod:`hashlib` even if they will not work at runtime due to
238+
missing backend implementations. For instance, ``hashlib.md5`` will no
239+
longer raise :exc:`AttributeError` if OpenSSL is not available and Python
240+
has been built without MD5 support.
241+
(Contributed by Bénédikt Tran in :gh:`136929`.)
242+
243+
233244
http.client
234245
-----------
235246

Lib/hashlib.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,16 +261,39 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
261261
return digestobj
262262

263263

264+
__logging = None
264265
for __func_name in __always_supported:
265266
# try them all, some may not work due to the OpenSSL
266267
# version not supporting that algorithm.
267268
try:
268269
globals()[__func_name] = __get_hash(__func_name)
269-
except ValueError:
270-
import logging
271-
logging.exception('code for hash %s was not found.', __func_name)
272-
270+
except ValueError as __exc:
271+
import logging as __logging
272+
__logging.error('hash algorithm %s will not be supported at runtime '
273+
'[reason: %s]', __func_name, __exc)
274+
# The following code can be simplified in Python 3.19
275+
# once "string" is removed from the signature.
276+
__code = f'''\
277+
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
278+
if data is __UNSET and string is not __UNSET:
279+
import warnings
280+
warnings.warn(
281+
"the 'string' keyword parameter is deprecated since "
282+
"Python 3.15 and slated for removal in Python 3.19; "
283+
"use the 'data' keyword parameter or pass the data "
284+
"to hash as a positional argument instead",
285+
DeprecationWarning, stacklevel=2)
286+
if data is not __UNSET and string is not __UNSET:
287+
raise TypeError("'data' and 'string' are mutually exclusive "
288+
"and support for 'string' keyword parameter "
289+
"is slated for removal in a future version.")
290+
raise ValueError("unsupported hash algorithm {__func_name}")
291+
'''
292+
exec(__code, {"__UNSET": object()}, __locals := {})
293+
globals()[__func_name] = __locals[__func_name]
294+
del __exc, __code, __locals
273295

274296
# Cleanup locals()
275297
del __always_supported, __func_name, __get_hash
276298
del __py_new, __hash_new, __get_openssl_constructor
299+
del __logging
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Ensure that hash functions guaranteed to be always *available* exist as
2+
attributes of :mod:`hashlib` even if they will not work at runtime due to
3+
missing backend implementations. For instance, ``hashlib.md5`` will no
4+
longer raise :exc:`AttributeError` if OpenSSL is not available and Python
5+
has been built without MD5 support. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)