Skip to content

Commit 84ea348

Browse files
committed
fix how large messages and keys are rejected by HMAC
1 parent 1e67293 commit 84ea348

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

Lib/hmac.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,23 @@ def digest(key, msg, digest):
241241
if _hashopenssl and isinstance(digest, (str, _functype)):
242242
try:
243243
return _hashopenssl.hmac_digest(key, msg, digest)
244+
except OverflowError:
245+
try:
246+
return _hashopenssl.hmac_new(key, msg, digest).digest()
247+
except _hashopenssl.UnsupportedDigestmodError:
248+
pass
244249
except _hashopenssl.UnsupportedDigestmodError:
245250
pass
246251

247252
if _hmac and isinstance(digest, str):
248253
try:
249254
return _hmac.compute_digest(key, msg, digest)
250-
except (OverflowError, _hmac.UnknownHashError):
255+
except OverflowError:
256+
try:
257+
return _hmac.new(key, msg, digest).digest()
258+
except _hmac.UnknownHashError:
259+
pass
260+
except _hmac.UnknownHashError:
251261
pass
252262

253263
return _compute_digest_fallback(key, msg, digest)

Lib/test/test_hmac.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,22 @@
2121
import hmac
2222
import hashlib
2323
import random
24-
import test.support.hashlib_helper as hashlib_helper
2524
import types
2625
import unittest
2726
import unittest.mock as mock
2827
import warnings
2928
from _operator import _compare_digest as operator_compare_digest
29+
from test.support import _4G, bigmemtest
3030
from test.support import check_disallow_instantiation
31+
from test.support import hashlib_helper, import_helper
3132
from test.support.hashlib_helper import (
3233
BuiltinHashFunctionsTrait,
3334
HashFunctionsTrait,
3435
NamedHashFunctionsTrait,
3536
OpenSSLHashFunctionsTrait,
3637
)
37-
from test.support.import_helper import import_fresh_module, import_module
38+
from test.support.import_helper import import_fresh_module
39+
from unittest.mock import patch
3840

3941
try:
4042
import _hashlib
@@ -949,7 +951,11 @@ class PyConstructorTestCase(ThroughObjectMixin, PyConstructorBaseMixin,
949951

950952
class PyModuleConstructorTestCase(ThroughModuleAPIMixin, PyConstructorBaseMixin,
951953
unittest.TestCase):
952-
"""Test the hmac.new() and hmac.digest() functions."""
954+
"""Test the hmac.new() and hmac.digest() functions.
955+
956+
Note that "self.hmac" is imported by blocking "_hashlib" and "_hmac".
957+
For testing functions in "hmac", extend PyMiscellaneousTests instead.
958+
"""
953959

954960
def test_hmac_digest_digestmod_parameter(self):
955961
func = self.hmac_digest
@@ -1499,6 +1505,55 @@ def test_with_fallback(self):
14991505
finally:
15001506
cache.pop('foo')
15011507

1508+
@hashlib_helper.requires_openssl_hashdigest("md5")
1509+
@bigmemtest(size=_4G, memuse=2, dry_run=False)
1510+
def test_hmac_digest_overflow_error_openssl_only(self, size):
1511+
self.do_test_hmac_digest_overflow_error_fast(size, openssl=True)
1512+
1513+
@hashlib_helper.requires_builtin_hashdigest("_md5", "md5")
1514+
@bigmemtest(size=_4G , memuse=2, dry_run=False)
1515+
def test_hmac_digest_overflow_error_builtin_only(self, size):
1516+
self.do_test_hmac_digest_overflow_error_fast(size, openssl=False)
1517+
1518+
def do_test_hmac_digest_overflow_error_fast(self, size, *, openssl):
1519+
"""Check that C hmac.digest() works for large inputs."""
1520+
1521+
if openssl:
1522+
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
1523+
c_module_name, c_method_name = "_hmac", "new"
1524+
else:
1525+
hmac = import_fresh_module("hmac", blocked=["_hmac"])
1526+
c_module_name, c_method_name = "_hashlib", "hmac_new"
1527+
1528+
cext = import_helper.import_module(c_module_name)
1529+
cnew = getattr(cext, c_method_name)
1530+
1531+
bigkey = b'K' * size
1532+
bigmsg = b'M' * size
1533+
1534+
with patch.object(hmac, "_compute_digest_fallback") as slow:
1535+
with patch.object(cext, c_method_name, wraps=cnew) as new:
1536+
self.assertIsInstance(hmac.digest(bigkey, b'm', "md5"), bytes)
1537+
new.assert_called_once()
1538+
with patch.object(cext, c_method_name, wraps=cnew) as new:
1539+
self.assertIsInstance(hmac.digest(b'k', bigmsg, "md5"), bytes)
1540+
new.assert_called_once()
1541+
slow.assert_not_called()
1542+
1543+
@hashlib_helper.requires_hashdigest("md5", openssl=True)
1544+
@bigmemtest(size=_4G, memuse=2, dry_run=False)
1545+
def test_hmac_digest_no_overflow_error_in_fallback(self, size):
1546+
hmac = import_fresh_module("hmac", blocked=["_hashlib", "_hmac"])
1547+
1548+
for key, msg in [(b'K' * size, b'm'), (b'k', b'M' * size)]:
1549+
with self.subTest(keysize=len(key), msgsize=len(msg)):
1550+
with unittest.mock.patch.object(
1551+
hmac, "_compute_digest_fallback",
1552+
wraps=hmac._compute_digest_fallback,
1553+
) as f:
1554+
self.assertIsInstance(hmac.digest(key, msg, "md5"), bytes)
1555+
f.assert_called_once()
1556+
15021557

15031558
class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15041559
"""HMAC-BLAKE2 is not standardized as BLAKE2 is a keyed hash function.
@@ -1511,7 +1566,7 @@ class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15111566
@classmethod
15121567
def setUpClass(cls):
15131568
super().setUpClass()
1514-
cls.blake2 = import_module("_blake2")
1569+
cls.blake2 = import_helper.import_module("_blake2")
15151570
cls.blake2b = cls.blake2.blake2b
15161571
cls.blake2s = cls.blake2.blake2s
15171572

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
One-shot :func:`hmac.digest` now properly handles large keys and messages
2+
by delegating to :func:`hmac.new` when possible. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)