Skip to content

Commit 6be49ee

Browse files
authored
pythongh-136787: improve exception messages for invalid hash algorithms (python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy