https://github.com/python/cpython/commit/8ad6eda4830f480380f84c2847acd7f550b2d1c2
commit: 8ad6eda4830f480380f84c2847acd7f550b2d1c2
branch: 3.14
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2025-10-08T12:09:45+02:00
summary:
[3.14] gh-136912: fix handling of `OverflowError` in `hmac.digest` (GH-136917)
(#137116)
The OpenSSL and HACL* implementations of HMAC single-shot
digest computation reject keys whose length exceeds `INT_MAX`
and `UINT32_MAX` respectively. The OpenSSL implementation
also rejects messages whose length exceed `INT_MAX`.
Using such keys in `hmac.digest` previously raised an `OverflowError`
which was propagated to the caller. This commit mitigates this case by
making `hmac.digest` fall back to HMAC's pure Python implementation
which accepts arbitrary large keys or messages.
This change only affects the top-level entrypoint `hmac.digest`, leaving
`_hashopenssl.hmac_digest` and `_hmac.compute_digest` untouched.
(cherry picked from commit d658b9053beaacaae80e318f59a5ddd672aa757a)
files:
A Misc/NEWS.d/next/Library/2025-07-21-11-56-47.gh-issue-136912.zWosAL.rst
M Lib/hmac.py
M Lib/test/test_hmac.py
diff --git a/Lib/hmac.py b/Lib/hmac.py
index 3683a4aa653a0a..16022c9ceb5439 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -229,6 +229,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
@@ -236,6 +244,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 e898644dd8a552..344c6ddf28afcf 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -21,21 +21,21 @@
import hmac
import hashlib
import random
-import test.support
-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
@@ -728,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
@@ -950,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
@@ -1446,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,
@@ -1500,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.
@@ -1512,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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]