https://github.com/python/cpython/commit/1a80008fb0baa5fad4bea14d87f1396d88e0b2b0
commit: 1a80008fb0baa5fad4bea14d87f1396d88e0b2b0
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: picnixz <[email protected]>
date: 2026-10-21T17:35:24Z
summary:

[3.14] gh-155428: align `hmac.digest` and `hmac.HMAC.digest` outputs when using 
 small block sizes (GH-155429) (#160061)

gh-155428: align `hmac.digest` and `hmac.HMAC.digest` outputs when using  small 
block sizes (GH-155429)
(cherry picked from commit 815dc4726c5cddd0b8b8d3737ce00fbd6d480316)

Co-authored-by: Bénédikt Tran <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-09-14-26-48.gh-issue-155428.wi0_fI.rst
M Lib/hmac.py
M Lib/test/test_hmac.py

diff --git a/Lib/hmac.py b/Lib/hmac.py
index 587aeacdf02cb0..2a248cbfec83f3 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -39,6 +39,27 @@ def digest_wrapper(d=b''):
     return digest_wrapper
 
 
+def _select_blocksize(obj, default, stacklevel=3):
+    if hasattr(obj, 'block_size'):
+        blocksize = obj.block_size
+        if blocksize < 16:
+            import warnings
+
+            warnings.warn(f"block_size of {blocksize} seems too small; "
+                          f"using our default of {default}.",
+                          RuntimeWarning, stacklevel=stacklevel)
+            blocksize = default
+    else:
+        import warnings
+
+        warnings.warn("No block_size attribute on given digest object; "
+                      f"Assuming {default}.",
+                      RuntimeWarning, stacklevel=stacklevel)
+        blocksize = default
+    return blocksize
+
+
+
 class HMAC:
     """RFC 2104 HMAC class.  Also complies with RFC 4231.
 
@@ -106,8 +127,6 @@ def _init_builtin_hmac(self, key, msg, digestmod):
         self.block_size = self._hmac.block_size
 
     def _init_old(self, key, msg, digestmod):
-        import warnings
-
         digest_cons = _get_digest_constructor(digestmod)
 
         self._hmac = None
@@ -115,19 +134,7 @@ def _init_old(self, key, msg, digestmod):
         self._inner = digest_cons()
         self.digest_size = self._inner.digest_size
 
-        if hasattr(self._inner, 'block_size'):
-            blocksize = self._inner.block_size
-            if blocksize < 16:
-                warnings.warn(f"block_size of {blocksize} seems too small; "
-                              f"using our default of {self.blocksize}.",
-                              RuntimeWarning, 2)
-                blocksize = self.blocksize  # pragma: no cover
-        else:
-            warnings.warn("No block_size attribute on given digest object; "
-                          f"Assuming {self.blocksize}.",
-                          RuntimeWarning, 2)
-            blocksize = self.blocksize  # pragma: no cover
-
+        blocksize = _select_blocksize(self._inner, self.blocksize)
         if len(key) > blocksize:
             key = digest_cons(key).digest()
 
@@ -261,7 +268,7 @@ def _compute_digest_fallback(key, msg, digest):
     digest_cons = _get_digest_constructor(digest)
     inner = digest_cons()
     outer = digest_cons()
-    blocksize = getattr(inner, 'block_size', 64)
+    blocksize = _select_blocksize(inner, HMAC.blocksize)
     if len(key) > blocksize:
         key = digest_cons(key).digest()
     key = key.ljust(blocksize, b'\0')
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 692e0abbb7efc0..e811540c9b9d4c 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -1494,17 +1494,36 @@ def update(self, v):
                 self._x.update(v)
             def digest(self):
                 return self._x.digest()
+            def copy(self):
+                h = MockCrazyHash()
+                h._x = self._x.copy()
+                return h
 
         with warnings.catch_warnings():
             warnings.simplefilter('error', RuntimeWarning)
             with self.assertRaises(RuntimeWarning):
                 hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
                 self.fail('Expected warning about missing block_size')
+            with self.assertRaises(RuntimeWarning):
+                hmac.digest(b'a', b'b', MockCrazyHash)
+                self.fail('Expected warning about missing block_size')
 
             MockCrazyHash.block_size = 1
             with self.assertRaises(RuntimeWarning):
                 hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
                 self.fail('Expected warning about small block_size')
+            with self.assertRaises(RuntimeWarning):
+                hmac.digest(b'a', b'b', MockCrazyHash)
+                self.fail('Expected warning about small block_size')
+
+        # gh-155428: hmac.HMAC() and hmac.digest() must both
+        # fall back to the default block size (64 for SHA-256).
+        expect = hmac.digest(b'a', b'b', 'sha256')
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', RuntimeWarning)
+            h = hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
+            self.assertEqual(h.digest(), expect)
+            self.assertEqual(hmac.digest(b'a', b'b', MockCrazyHash), expect)
 
     @hashlib_helper.requires_hashdigest('sha256')
     def test_with_fallback(self):
diff --git 
a/Misc/NEWS.d/next/Library/2026-08-09-14-26-48.gh-issue-155428.wi0_fI.rst 
b/Misc/NEWS.d/next/Library/2026-08-09-14-26-48.gh-issue-155428.wi0_fI.rst
new file mode 100644
index 00000000000000..f0c6201cbe592e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-09-14-26-48.gh-issue-155428.wi0_fI.rst
@@ -0,0 +1,3 @@
+Ensure that :func:`hmac.digest` and :meth:`hmac.HMAC.digest` output
+identical values when using a digest's block size smaller than 16. 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]

Reply via email to