Skip to content

Commit 6be49ee

Browse files
authored
gh-136787: improve exception messages for invalid hash algorithms (#136802)
1 parent 800d37f commit 6be49ee

File tree

8 files changed

+198
-48
lines changed

8 files changed

+198
-48
lines changed

Lib/hashlib.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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,10 +125,13 @@ 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)
@@ -154,6 +162,8 @@ def __hash_new(name, *args, **kwargs):
154162
optionally initialized with data (which must be a bytes-like object).
155163
"""
156164
if name in __block_openssl_constructor:
165+
# __block_openssl_constructor is expected to contain strings only
166+
assert isinstance(name, str), f"unexpected name: {name}"
157167
# Prefer our builtin blake2 implementation.
158168
return __get_builtin_constructor(name)(*args, **kwargs)
159169
try:

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()
@@ -243,6 +255,8 @@ def digest(key, msg, digest):
243255

244256
def _compute_digest_fallback(key, msg, digest):
245257
digest_cons = _get_digest_constructor(digest)
258+
if _is_shake_constructor(digest_cons):
259+
raise ValueError(f"unsupported hash algorithm {digest}")
246260
inner = digest_cons()
247261
outer = digest_cons()
248262
blocksize = getattr(inner, 'block_size', 64)

Lib/test/test_hashlib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ def test_clinic_signature_errors(self):
343343

344344
def test_unknown_hash(self):
345345
self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam')
346-
self.assertRaises(TypeError, hashlib.new, 1)
346+
# ensure that the exception message remains consistent
347+
err = re.escape("new() argument 'name' must be str, not int")
348+
self.assertRaisesRegex(TypeError, err, hashlib.new, 1)
347349

348350
def test_new_upper_to_lower(self):
349351
self.assertEqual(hashlib.new("SHA256").name, "sha256")
@@ -370,7 +372,9 @@ def test_get_builtin_constructor(self):
370372
sys.modules['_md5'] = _md5
371373
else:
372374
del sys.modules['_md5']
373-
self.assertRaises(TypeError, get_builtin_constructor, 3)
375+
# ensure that the exception message remains consistent
376+
err = re.escape("new() argument 'name' must be str, not int")
377+
self.assertRaises(TypeError, err, get_builtin_constructor, 3)
374378
constructor = get_builtin_constructor('md5')
375379
self.assertIs(constructor, _md5.md5)
376380
self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5'])

Lib/test/test_hmac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ def raiser():
960960
with self.assertRaisesRegex(RuntimeError, "custom exception"):
961961
func(b'key', b'msg', raiser)
962962

963-
with self.assertRaisesRegex(ValueError, 'hash type'):
963+
with self.assertRaisesRegex(ValueError, 'unsupported hash algorithm'):
964964
func(b'key', b'msg', 'unknown')
965965

966966
with self.assertRaisesRegex(AttributeError, 'new'):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`hashlib`: improve exception messages when a hash algorithm is not
2+
recognized, blocked by the current security policy or incompatible with
3+
the desired operation (for instance, using HMAC with SHAKE).
4+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)