Commit cf75a781 authored by Christian Heimes's avatar Christian Heimes

Issue #17276: MD5 as default digestmod for HMAC is deprecated. The HMAC

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