Skip to content

gh-136912: fix handling of OverflowError in hmac.digest #136917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix how large messages and keys are rejected by HMAC
  • Loading branch information
picnixz committed Jul 22, 2025
commit 1082bd595dd0da9a913171309748bc9ee1c1c724
12 changes: 11 additions & 1 deletion Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,23 @@ def digest(key, msg, digest):
if _hashopenssl and isinstance(digest, (str, _functype)):
try:
return _hashopenssl.hmac_digest(key, msg, digest)
except OverflowError:
try:
return _hashopenssl.hmac_new(key, msg, digest).digest()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gpshead This will create a real HMAC object using OpenSSL and handles chunks in C. Alternatively, I can just catch the OverflowError directly and ignore it. The pure Python implementation already handles chunks as we just call .update() which is implemented in C as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: actually OpenSSL still requires a key of size at most INT_MAX and HACL* requires the size to be at most UINT32_MAX. I'll just directly switch to the "slow" python implementation.

except _hashopenssl.UnsupportedDigestmodError:
pass
except _hashopenssl.UnsupportedDigestmodError:
pass

if _hmac and isinstance(digest, str):
try:
return _hmac.compute_digest(key, msg, digest)
except (OverflowError, _hmac.UnknownHashError):
except OverflowError:
try:
return _hmac.new(key, msg, digest).digest()
except _hmac.UnknownHashError:
pass
except _hmac.UnknownHashError:
pass

return _compute_digest_fallback(key, msg, digest)
Expand Down
68 changes: 59 additions & 9 deletions Lib/test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1499,6 +1503,52 @@ def test_with_fallback(self):
finally:
cache.pop('foo')

@hashlib_helper.requires_openssl_hashdigest("md5")
@bigmemtest(size=_4G, memuse=2, dry_run=False)
def test_hmac_digest_overflow_error_openssl_only(self, size):
self.do_test_hmac_digest_overflow_error_fast(size, openssl=True)

@hashlib_helper.requires_builtin_hashdigest("_md5", "md5")
@bigmemtest(size=_4G , memuse=2, dry_run=False)
def test_hmac_digest_overflow_error_builtin_only(self, size):
self.do_test_hmac_digest_overflow_error_fast(size, openssl=False)

def do_test_hmac_digest_overflow_error_fast(self, size, *, openssl):
"""Check that C hmac.digest() works for large inputs."""

if openssl:
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
c_module_name, c_method_name = "_hmac", "new"
else:
hmac = import_fresh_module("hmac", blocked=["_hmac"])
c_module_name, c_method_name = "_hashlib", "hmac_new"

cext = import_helper.import_module(c_module_name)
cnew = getattr(cext, c_method_name)

bigkey = b'K' * size
bigmsg = b'M' * size

with patch.object(hmac, "_compute_digest_fallback") as slow:
with patch.object(cext, c_method_name, wraps=cnew) as new:
self.assertIsInstance(hmac.digest(bigkey, b'm', "md5"), bytes)
new.assert_called_once()
with patch.object(cext, c_method_name, wraps=cnew) as new:
self.assertIsInstance(hmac.digest(b'k', bigmsg, "md5"), bytes)
new.assert_called_once()
slow.assert_not_called()

@hashlib_helper.requires_hashdigest("md5", openssl=True)
@bigmemtest(size=_4G, 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:
self.assertIsInstance(hmac.digest(key, msg, "md5"), bytes)
slow.assert_called_once()


class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
"""HMAC-BLAKE2 is not standardized as BLAKE2 is a keyed hash function.
Expand All @@ -1511,7 +1561,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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
One-shot :func:`hmac.digest` now properly handles large keys and messages
by delegating to :func:`hmac.new` when possible. Patch by Bénédikt Tran.
Loading
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