diff --git a/Lib/hmac.py b/Lib/hmac.py index e50d355fc60871..9d3fae8b1b1597 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -241,6 +241,14 @@ def digest(key, msg, digest): if _hashopenssl and isinstance(digest, (str, _functype)): try: return _hashopenssl.hmac_digest(key, msg, digest) + except OverflowError: + # OpenSSL's HMAC limits the size of the key to INT_MAX. + # Instead of falling back to HACL* implementation which + # may still not be supported due to a too large key, we + # directly switch to the pure Python fallback instead + # even if we could have used streaming HMAC for small keys + # but large messages. + return _compute_digest_fallback(key, msg, digest) except _hashopenssl.UnsupportedDigestmodError: pass @@ -248,6 +256,10 @@ def digest(key, msg, digest): try: return _hmac.compute_digest(key, msg, digest) except (OverflowError, _hmac.UnknownHashError): + # HACL* HMAC limits the size of the key to UINT32_MAX + # so we fallback to the pure Python implementation even + # if streaming HMAC may have been used for small keys + # and large messages. pass return _compute_digest_fallback(key, msg, digest) diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 02ded86678343f..5c29369d10b143 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -21,20 +21,21 @@ import hmac import hashlib import random -import test.support.hashlib_helper as hashlib_helper import types import unittest -import unittest.mock as mock import warnings from _operator import _compare_digest as operator_compare_digest +from test.support import _4G, bigmemtest from test.support import check_disallow_instantiation +from test.support import hashlib_helper, import_helper from test.support.hashlib_helper import ( BuiltinHashFunctionsTrait, HashFunctionsTrait, NamedHashFunctionsTrait, OpenSSLHashFunctionsTrait, ) -from test.support.import_helper import import_fresh_module, import_module +from test.support.import_helper import import_fresh_module +from unittest.mock import patch try: import _hashlib @@ -727,7 +728,7 @@ def setUpClass(cls): super().setUpClass() for meth in ['_init_openssl_hmac', '_init_builtin_hmac']: fn = getattr(cls.hmac.HMAC, meth) - cm = mock.patch.object(cls.hmac.HMAC, meth, autospec=True, wraps=fn) + cm = patch.object(cls.hmac.HMAC, meth, autospec=True, wraps=fn) cls.enterClassContext(cm) @classmethod @@ -949,7 +950,11 @@ class PyConstructorTestCase(ThroughObjectMixin, PyConstructorBaseMixin, class PyModuleConstructorTestCase(ThroughModuleAPIMixin, PyConstructorBaseMixin, unittest.TestCase): - """Test the hmac.new() and hmac.digest() functions.""" + """Test the hmac.new() and hmac.digest() functions. + + Note that "self.hmac" is imported by blocking "_hashlib" and "_hmac". + For testing functions in "hmac", extend PyMiscellaneousTests instead. + """ def test_hmac_digest_digestmod_parameter(self): func = self.hmac_digest @@ -1445,9 +1450,8 @@ def test_hmac_constructor_uses_builtin(self): hmac = import_fresh_module("hmac", blocked=["_hashlib"]) def watch_method(cls, name): - return mock.patch.object( - cls, name, autospec=True, wraps=getattr(cls, name) - ) + wraps = getattr(cls, name) + return patch.object(cls, name, autospec=True, wraps=wraps) with ( watch_method(hmac.HMAC, '_init_openssl_hmac') as f, @@ -1499,6 +1503,48 @@ def test_with_fallback(self): finally: cache.pop('foo') + @hashlib_helper.requires_openssl_hashdigest("md5") + @bigmemtest(size=_4G + 5, memuse=2, dry_run=False) + def test_hmac_digest_overflow_error_openssl_only(self, size): + hmac = import_fresh_module("hmac", blocked=["_hmac"]) + self.do_test_hmac_digest_overflow_error_switch_to_slow(hmac, size) + + @hashlib_helper.requires_builtin_hashdigest("_md5", "md5") + @bigmemtest(size=_4G + 5, memuse=2, dry_run=False) + def test_hmac_digest_overflow_error_builtin_only(self, size): + hmac = import_fresh_module("hmac", blocked=["_hashlib"]) + self.do_test_hmac_digest_overflow_error_switch_to_slow(hmac, size) + + def do_test_hmac_digest_overflow_error_switch_to_slow(self, hmac, size): + """Check that hmac.digest() falls back to pure Python. + + The *hmac* argument implements the HMAC module interface. + The *size* argument is a large key size or message size that would + trigger an OverflowError in the C implementation(s) of hmac.digest(). + """ + + bigkey = b'K' * size + bigmsg = b'M' * size + + with patch.object(hmac, "_compute_digest_fallback") as slow: + hmac.digest(bigkey, b'm', "md5") + slow.assert_called_once() + + with patch.object(hmac, "_compute_digest_fallback") as slow: + hmac.digest(b'k', bigmsg, "md5") + slow.assert_called_once() + + @hashlib_helper.requires_hashdigest("md5", openssl=True) + @bigmemtest(size=_4G + 5, memuse=2, dry_run=False) + def test_hmac_digest_no_overflow_error_in_fallback(self, size): + hmac = import_fresh_module("hmac", blocked=["_hashlib", "_hmac"]) + + for key, msg in [(b'K' * size, b'm'), (b'k', b'M' * size)]: + with self.subTest(keysize=len(key), msgsize=len(msg)): + with patch.object(hmac, "_compute_digest_fallback") as slow: + hmac.digest(key, msg, "md5") + slow.assert_called_once() + class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase): """HMAC-BLAKE2 is not standardized as BLAKE2 is a keyed hash function. @@ -1511,7 +1557,7 @@ class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.blake2 = import_module("_blake2") + cls.blake2 = import_helper.import_module("_blake2") cls.blake2b = cls.blake2.blake2b cls.blake2s = cls.blake2.blake2s diff --git a/Misc/NEWS.d/next/Library/2025-07-21-11-56-47.gh-issue-136912.zWosAL.rst b/Misc/NEWS.d/next/Library/2025-07-21-11-56-47.gh-issue-136912.zWosAL.rst new file mode 100644 index 00000000000000..6c5f31145f76d1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-21-11-56-47.gh-issue-136912.zWosAL.rst @@ -0,0 +1,3 @@ +:func:`hmac.digest` now properly handles large keys and messages +by falling back to the pure Python implementation when necessary. +Patch by Bénédikt Tran. 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