diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst index 1d928ea8600..8fa1435f881 100644 --- a/Doc/library/hmac.rst +++ b/Doc/library/hmac.rst @@ -18,13 +18,20 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. 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 constructor or module for the HMAC object to use. - It defaults to the :data:`hashlib.md5` constructor. + *digestmod* is the digest name, digest constructor or module for the HMAC + object to use. It supports any name suitable to :func:`hashlib.new` and + defaults to the :data:`hashlib.md5` constructor. .. versionchanged:: 3.4 Parameter *key* can be a bytes or bytearray object. Parameter *msg* can be of any type supported by :mod:`hashlib`. + Paramter *digestmod* can be the name of a hash algorithm. + + .. deprecated:: 3.4 + MD5 as implicit default digest for *digestmod* is deprecated. + + An HMAC object has the following methods: .. method:: HMAC.update(msg) diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 19b20bf0f04..42a6fef67ee 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -842,6 +842,9 @@ Deprecated Python modules, functions and methods * The :mod:`formatter` module is pending deprecation and is slated for removal in Python 3.6. +* MD5 as default digestmod for :mod:`hmac` is deprecated. Python 3.6 will + require an explicit digest name or constructor as *digestmod* argument. + Deprecated functions and types of the C API ------------------------------------------- diff --git a/Lib/hmac.py b/Lib/hmac.py index d13b205bbb9..873327bf37c 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -5,6 +5,7 @@ Implements the HMAC algorithm as described by RFC 2104. import warnings as _warnings from _operator import _compare_digest as compare_digest +import hashlib as _hashlib trans_5C = bytes((x ^ 0x5C) for x in range(256)) trans_36 = bytes((x ^ 0x36) for x in range(256)) @@ -28,8 +29,11 @@ class HMAC: key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. *OR* - A hashlib constructor returning a new hash object. + A hashlib constructor returning a new hash object. *OR* + A hash name suitable for hashlib.new(). Defaults to hashlib.md5. + Implicit default to hashlib.md5 is deprecated and will be + removed in Python 3.6. Note: key and msg must be a bytes or bytearray objects. """ @@ -38,11 +42,14 @@ class HMAC: raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) if digestmod is None: - import hashlib - digestmod = hashlib.md5 + _warnings.warn("HMAC() without an explicit digestmod argument " + "is deprecated.", PendingDeprecationWarning, 2) + digestmod = _hashlib.md5 if callable(digestmod): self.digest_cons = digestmod + elif isinstance(digestmod, str): + self.digest_cons = lambda d=b'': _hashlib.new(digestmod, d) else: self.digest_cons = lambda d=b'': digestmod.new(d) diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 0994f2bd01e..b29487acdd0 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -554,7 +554,7 @@ class IMAP4: import hmac pwd = (self.password.encode('ASCII') if isinstance(self.password, str) else self.password) - return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest() + return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest() def logout(self): diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 27fda9f22c6..8ad6fd5f3e5 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -719,7 +719,7 @@ def deliver_challenge(connection, authkey): assert isinstance(authkey, bytes) message = os.urandom(MESSAGE_LENGTH) connection.send_bytes(CHALLENGE + message) - digest = hmac.new(authkey, message).digest() + digest = hmac.new(authkey, message, 'md5').digest() response = connection.recv_bytes(256) # reject large message if response == digest: connection.send_bytes(WELCOME) @@ -733,7 +733,7 @@ def answer_challenge(connection, authkey): message = connection.recv_bytes(256) # reject large message assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message message = message[len(CHALLENGE):] - digest = hmac.new(authkey, message).digest() + digest = hmac.new(authkey, message, 'md5').digest() connection.send_bytes(digest) response = connection.recv_bytes(256) # reject large message if response != WELCOME: diff --git a/Lib/smtplib.py b/Lib/smtplib.py index e1a32ed0c80..69ae845dca4 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -579,7 +579,7 @@ class SMTP: def encode_cram_md5(challenge, user, password): challenge = base64.decodebytes(challenge) response = user + " " + hmac.HMAC(password.encode('ascii'), - challenge).hexdigest() + challenge, 'md5').hexdigest() return encode_base64(response.encode('ascii'), eol='') def encode_plain(user, password): diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index efd63ad7be5..eb23b26578c 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -10,7 +10,9 @@ class TestVectorsTestCase(unittest.TestCase): # Test the HMAC module against test vectors from the RFC. def md5test(key, data, digest): - h = hmac.HMAC(key, data) + h = hmac.HMAC(key, data, digestmod=hashlib.md5) + self.assertEqual(h.hexdigest().upper(), digest.upper()) + h = hmac.HMAC(key, data, digestmod='md5') self.assertEqual(h.hexdigest().upper(), digest.upper()) md5test(b"\x0b" * 16, @@ -46,6 +48,9 @@ class TestVectorsTestCase(unittest.TestCase): def shatest(key, data, digest): h = hmac.HMAC(key, data, digestmod=hashlib.sha1) self.assertEqual(h.hexdigest().upper(), digest.upper()) + h = hmac.HMAC(key, data, digestmod='sha1') + self.assertEqual(h.hexdigest().upper(), digest.upper()) + shatest(b"\x0b" * 20, b"Hi There", @@ -76,10 +81,13 @@ class TestVectorsTestCase(unittest.TestCase): b"and Larger Than One Block-Size Data"), "e8e99d0f45237d786d6bbaa7965c7808bbff1a91") - def _rfc4231_test_cases(self, hashfunc): + def _rfc4231_test_cases(self, hashfunc, hashname): def hmactest(key, data, hexdigests): h = hmac.HMAC(key, data, digestmod=hashfunc) self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc]) + h = hmac.HMAC(key, data, digestmod=hashname) + self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc]) + # 4.2. Test Case 1 hmactest(key = b'\x0b'*20, @@ -189,16 +197,16 @@ class TestVectorsTestCase(unittest.TestCase): }) def test_sha224_rfc4231(self): - self._rfc4231_test_cases(hashlib.sha224) + self._rfc4231_test_cases(hashlib.sha224, 'sha224') def test_sha256_rfc4231(self): - self._rfc4231_test_cases(hashlib.sha256) + self._rfc4231_test_cases(hashlib.sha256, 'sha256') def test_sha384_rfc4231(self): - self._rfc4231_test_cases(hashlib.sha384) + self._rfc4231_test_cases(hashlib.sha384, 'sha384') def test_sha512_rfc4231(self): - self._rfc4231_test_cases(hashlib.sha512) + self._rfc4231_test_cases(hashlib.sha512, 'sha512') def test_legacy_block_size_warnings(self): class MockCrazyHash(object): @@ -222,6 +230,13 @@ class TestVectorsTestCase(unittest.TestCase): hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash) self.fail('Expected warning about small block_size') + def test_with_digestmod_warning(self): + with self.assertWarns(PendingDeprecationWarning): + key = b"\x0b" * 16 + data = b"Hi There" + digest = "9294727A3638BB1C13F48EF8158BFC9D" + h = hmac.HMAC(key, data) + self.assertEqual(h.hexdigest().upper(), digest) class ConstructorTestCase(unittest.TestCase): diff --git a/Lib/test/test_pep247.py b/Lib/test/test_pep247.py index 7f104728565..b85a26ad656 100644 --- a/Lib/test/test_pep247.py +++ b/Lib/test/test_pep247.py @@ -15,12 +15,14 @@ class Pep247Test(unittest.TestCase): self.assertTrue(module.digest_size is None or module.digest_size > 0) self.check_object(module.new, module.digest_size, key) - def check_object(self, cls, digest_size, key): + def check_object(self, cls, digest_size, key, digestmod=None): if key is not None: - obj1 = cls(key) - obj2 = cls(key, b'string') - h1 = cls(key, b'string').digest() - obj3 = cls(key) + if digestmod is None: + digestmod = md5 + obj1 = cls(key, digestmod=digestmod) + obj2 = cls(key, b'string', digestmod=digestmod) + h1 = cls(key, b'string', digestmod=digestmod).digest() + obj3 = cls(key, digestmod=digestmod) obj3.update(b'string') h2 = obj3.digest() else: diff --git a/Misc/NEWS b/Misc/NEWS index 62f1c3b98ae..1aaf8c159bb 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -59,6 +59,9 @@ Core and Builtins Library ------- +- Issue #17276: MD5 as default digestmod for HMAC is deprecated. The HMAC + module supports digestmod names, e.g. hmac.HMAC('sha1'). + - Issue #19449: in csv's writerow, handle non-string keys when generating the error message that certain keys are not in the 'fieldnames' list.