From cbe466f03f38e926ff6447cfe19d76423ea45da7 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Oct 2019 09:34:58 -0700 Subject: [PATCH 1/4] bpo-33604: Raise TypeError on missing hmac arg. Also updates the documentation to clarify the situation surrounding the digestmod parameter that is required despite its position in the argument list as of 3.8.0 as well as removing old python2 era references to "binary strings". We indavertently had this raise ValueError in 3.8.0 for the missing arg. This is not considered an API change as no reasonable code would be catching this missing argument error in order to handle it. --- Doc/library/hmac.rst | 8 ++++--- Lib/hmac.py | 53 +++++++++++++++++++++++-------------------- Lib/test/test_hmac.py | 9 +++++++- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst index dc994b07c35c007..058a47527b9f7f3 100644 --- a/Doc/library/hmac.rst +++ b/Doc/library/hmac.rst @@ -14,12 +14,13 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. -.. function:: new(key, msg=None, digestmod=None) +.. function:: new(key, msg=None, digestmod=b'') Return a new hmac object. *key* is a bytes or bytearray object giving the secret key. If *msg* is present, the method call ``update(msg)`` is made. *digestmod* is the digest name, digest constructor or module for the HMAC - object to use. It supports any name suitable to :func:`hashlib.new`. + object to use. It may be any name suitable to :func:`hashlib.new`. + Despite its argument position, it is required. .. versionchanged:: 3.4 Parameter *key* can be a bytes or bytearray object. @@ -28,6 +29,8 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. .. deprecated-removed:: 3.4 3.8 MD5 as implicit default digest for *digestmod* is deprecated. + The digestmod parameter is now required. Pass it as a keyword + argument to avoid awkwardness when you do not have an initial msg. .. function:: digest(key, msg, digest) @@ -127,7 +130,6 @@ This module also provides the following helper function: a timing attack could theoretically reveal information about the types and lengths of *a* and *b*—but not their values. - .. versionadded:: 3.3 diff --git a/Lib/hmac.py b/Lib/hmac.py index 890eaba08e8f6fb..27b86165fcfd095 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -1,4 +1,4 @@ -"""HMAC (Keyed-Hashing for Message Authentication) Python module. +"""HMAC (Keyed-Hashing for Message Authentication) module. Implements the HMAC algorithm as described by RFC 2104. """ @@ -30,23 +30,25 @@ class HMAC: """ blocksize = 64 # 512-bit HMAC; can be changed in subclasses. - def __init__(self, key, msg = None, digestmod = None): + def __init__(self, key, msg=None, digestmod=b''): """Create a new HMAC object. - key: key for the keyed hash object. - msg: Initial input for the hash, if provided. - digestmod: Required. A module supporting PEP 247. *OR* - A hashlib constructor returning a new hash object. *OR* - A hash name suitable for hashlib.new(). + key: bytes or buffer, key for the keyed hash object. + msg: bytes or buffer, Initial input for the hash or None. + digestmod: A hash name suitable for hashlib.new(). *OR* + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. - Note: key and msg must be a bytes or bytearray objects. + Required as of 3.8, despite its position after the optional + msg argument. Passing it as a keyword argument is + recommended, though not required for legacy API reasons. """ if not isinstance(key, (bytes, bytearray)): raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) - if digestmod is None: - raise ValueError('`digestmod` is required.') + if not digestmod: + raise TypeError("Missing required parameter 'digestmod'.") if callable(digestmod): self.digest_cons = digestmod @@ -90,8 +92,7 @@ def name(self): return "hmac-" + self.inner.name def update(self, msg): - """Update this hashing object with the string msg. - """ + """Feed data from msg into this hashing object.""" self.inner.update(msg) def copy(self): @@ -119,7 +120,7 @@ def _current(self): def digest(self): """Return the hash value of this hashing object. - This returns a string containing 8-bit data. The object is + This returns the hmac value as bytes. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ @@ -132,30 +133,34 @@ def hexdigest(self): h = self._current() return h.hexdigest() -def new(key, msg = None, digestmod = None): +def new(key, msg=None, digestmod=b''): """Create a new hashing object and return it. - key: The starting key for the hash. - msg: if available, will immediately be hashed into the object's starting - state. + key: bytes or buffer, The starting key for the hash. + msg: bytes or buffer, Initial input for the hash, or None. + digestmod: A hash name suitable for hashlib.new(). *OR* + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. + + Required as of 3.8, despite its position after the optional + msg argument. Passing it as a keyword argument is + recommended, though not required for legacy API reasons. - You can now feed arbitrary strings into the object using its update() + You can now feed arbitrary bytes into the object using its update() method, and can ask for the hash value at any time by calling its digest() - method. + or hexdigest() methods. """ return HMAC(key, msg, digestmod) def digest(key, msg, digest): - """Fast inline implementation of HMAC + """Fast inline implementation of HMAC. - key: key for the keyed hash object. - msg: input message + key: bytes or buffer, The key for the keyed hash object. + msg: bytes or buffer, Input message. digest: A hash name suitable for hashlib.new() for best performance. *OR* A hashlib constructor returning a new hash object. *OR* A module supporting PEP 247. - - Note: key and msg must be a bytes or bytearray objects. """ if (_hashopenssl is not None and isinstance(digest, str) and digest in _openssl_md_meths): diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 1bbf201727d7a84..b3f5921562826e0 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -312,10 +312,17 @@ def digest(self): self.fail('Expected warning about small block_size') def test_with_digestmod_no_default(self): - with self.assertRaises(ValueError): + """The digestmod parameter is required as of Python 3.8.""" + with self.assertRaises(TypeError): key = b"\x0b" * 16 data = b"Hi There" hmac.HMAC(key, data, digestmod=None) + with self.assertRaisesRegex(TypeError, r'required.*digestmod'): + hmac.new(b"key", b"message") + with self.assertRaisesRegex(TypeError, r'required.*digestmod'): + hmac.HMAC(b"key", msg=b"msg", digestmod=None) + with self.assertRaisesRegex(TypeError, r'required.*digestmod'): + hmac.HMAC(b"key", msg=b"msg", digestmod='') class ConstructorTestCase(unittest.TestCase): From 20614786db13e596a82ecaaf93b1a2d3ae9fcf07 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Oct 2019 09:47:42 -0700 Subject: [PATCH 2/4] news news news! --- .../next/Library/2019-10-15-09-47-40.bpo-33604.J12cWT.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-10-15-09-47-40.bpo-33604.J12cWT.rst diff --git a/Misc/NEWS.d/next/Library/2019-10-15-09-47-40.bpo-33604.J12cWT.rst b/Misc/NEWS.d/next/Library/2019-10-15-09-47-40.bpo-33604.J12cWT.rst new file mode 100644 index 000000000000000..fbd73003cfcb646 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-10-15-09-47-40.bpo-33604.J12cWT.rst @@ -0,0 +1,3 @@ +Fixed `hmac.new` and `hmac.HMAC` to raise TypeError instead of ValueError +when the digestmod parameter, now required in 3.8, is omitted. Also +clarified the hmac module documentation and docstrings. From a38cc2e2bba26a9938df396afe15906fbee8853c Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Oct 2019 09:49:25 -0700 Subject: [PATCH 3/4] Correct digestmod default to '' not b''. --- Doc/library/hmac.rst | 2 +- Lib/hmac.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst index 058a47527b9f7f3..57ac8bb16120f5d 100644 --- a/Doc/library/hmac.rst +++ b/Doc/library/hmac.rst @@ -14,7 +14,7 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. -.. function:: new(key, msg=None, digestmod=b'') +.. function:: new(key, msg=None, digestmod='') Return a new hmac object. *key* is a bytes or bytearray object giving the secret key. If *msg* is present, the method call ``update(msg)`` is made. diff --git a/Lib/hmac.py b/Lib/hmac.py index 27b86165fcfd095..b769876e6f7745d 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -30,7 +30,7 @@ class HMAC: """ blocksize = 64 # 512-bit HMAC; can be changed in subclasses. - def __init__(self, key, msg=None, digestmod=b''): + def __init__(self, key, msg=None, digestmod=''): """Create a new HMAC object. key: bytes or buffer, key for the keyed hash object. @@ -133,7 +133,7 @@ def hexdigest(self): h = self._current() return h.hexdigest() -def new(key, msg=None, digestmod=b''): +def new(key, msg=None, digestmod=''): """Create a new hashing object and return it. key: bytes or buffer, The starting key for the hash. From d3368ceed7632614574a5778f7eeb3fe4c3baff4 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Oct 2019 10:01:00 -0700 Subject: [PATCH 4/4] clean up the testcase. --- Lib/test/test_hmac.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index b3f5921562826e0..ea00367c8027a69 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -313,16 +313,14 @@ def digest(self): def test_with_digestmod_no_default(self): """The digestmod parameter is required as of Python 3.8.""" - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, r'required.*digestmod'): key = b"\x0b" * 16 data = b"Hi There" hmac.HMAC(key, data, digestmod=None) with self.assertRaisesRegex(TypeError, r'required.*digestmod'): - hmac.new(b"key", b"message") - with self.assertRaisesRegex(TypeError, r'required.*digestmod'): - hmac.HMAC(b"key", msg=b"msg", digestmod=None) + hmac.new(key, data) with self.assertRaisesRegex(TypeError, r'required.*digestmod'): - hmac.HMAC(b"key", msg=b"msg", digestmod='') + hmac.HMAC(key, msg=data, digestmod='') class ConstructorTestCase(unittest.TestCase):