bpo-40645: Implement HMAC in C (GH-20129)

The internal module ``_hashlib`` wraps and exposes OpenSSL's HMAC API. The
new code will be used in Python 3.10 after the internal implementation
details of the pure Python HMAC module are no longer part of the public API.

The code is based on a patch by Petr Viktorin for RHEL and Python 3.6.

Co-Authored-By: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Christian Heimes 2020-05-17 13:49:10 +02:00 committed by GitHub
parent 46545000c2
commit 54f2898fe7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 694 additions and 89 deletions

View file

@ -8,6 +8,13 @@ import warnings
from test.support import hashlib_helper
try:
from _hashlib import HMAC as C_HMAC
from _hashlib import hmac_new as c_hmac_new
except ImportError:
C_HMAC = None
c_hmac_new = None
def ignore_warning(func):
@functools.wraps(func)
@ -21,34 +28,91 @@ def ignore_warning(func):
class TestVectorsTestCase(unittest.TestCase):
def asssert_hmac(
self, key, data, digest, hashfunc, hashname, digest_size, block_size
):
h = hmac.HMAC(key, data, digestmod=hashfunc)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
h = hmac.HMAC(key, data, digestmod=hashname)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
h = hmac.HMAC(key, digestmod=hashname)
h2 = h.copy()
h2.update(b"test update")
h.update(data)
self.assertEqual(h.hexdigest().upper(), digest.upper())
h = hmac.new(key, data, digestmod=hashname)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
h = hmac.new(key, None, digestmod=hashname)
h.update(data)
self.assertEqual(h.hexdigest().upper(), digest.upper())
h = hmac.new(key, digestmod=hashname)
h.update(data)
self.assertEqual(h.hexdigest().upper(), digest.upper())
h = hmac.new(key, data, digestmod=hashfunc)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(
hmac.digest(key, data, digest=hashname),
binascii.unhexlify(digest)
)
self.assertEqual(
hmac.digest(key, data, digest=hashfunc),
binascii.unhexlify(digest)
)
with unittest.mock.patch('hmac._openssl_md_meths', {}):
self.assertEqual(
hmac.digest(key, data, digest=hashname),
binascii.unhexlify(digest)
)
self.assertEqual(
hmac.digest(key, data, digest=hashfunc),
binascii.unhexlify(digest)
)
if c_hmac_new is not None:
h = c_hmac_new(key, data, digestmod=hashname)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
h = c_hmac_new(key, digestmod=hashname)
h2 = h.copy()
h2.update(b"test update")
h.update(data)
self.assertEqual(h.hexdigest().upper(), digest.upper())
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_md5_vectors(self):
# Test the HMAC module against test vectors from the RFC.
def md5test(key, data, digest):
h = hmac.HMAC(key, data, digestmod=hashlib.md5)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, "hmac-md5")
self.assertEqual(h.digest_size, 16)
self.assertEqual(h.block_size, 64)
h = hmac.HMAC(key, data, digestmod='md5')
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, "hmac-md5")
self.assertEqual(h.digest_size, 16)
self.assertEqual(h.block_size, 64)
self.assertEqual(
hmac.digest(key, data, digest='md5'),
binascii.unhexlify(digest)
self.asssert_hmac(
key, data, digest,
hashfunc=hashlib.md5,
hashname="md5",
digest_size=16,
block_size=64
)
with unittest.mock.patch('hmac._openssl_md_meths', {}):
self.assertEqual(
hmac.digest(key, data, digest='md5'),
binascii.unhexlify(digest)
)
md5test(b"\x0b" * 16,
b"Hi There",
@ -82,26 +146,14 @@ class TestVectorsTestCase(unittest.TestCase):
@hashlib_helper.requires_hashdigest('sha1', openssl=True)
def test_sha_vectors(self):
def shatest(key, data, digest):
h = hmac.HMAC(key, data, digestmod=hashlib.sha1)
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, "hmac-sha1")
self.assertEqual(h.digest_size, 20)
self.assertEqual(h.block_size, 64)
h = hmac.HMAC(key, data, digestmod='sha1')
self.assertEqual(h.hexdigest().upper(), digest.upper())
self.assertEqual(h.digest(), binascii.unhexlify(digest))
self.assertEqual(h.name, "hmac-sha1")
self.assertEqual(h.digest_size, 20)
self.assertEqual(h.block_size, 64)
self.assertEqual(
hmac.digest(key, data, digest='sha1'),
binascii.unhexlify(digest)
self.asssert_hmac(
key, data, digest,
hashfunc=hashlib.sha1,
hashname="sha1",
digest_size=20,
block_size=64
)
shatest(b"\x0b" * 20,
b"Hi There",
"b617318655057264e28bc0b6fb378c8ef146be00")
@ -133,37 +185,15 @@ class TestVectorsTestCase(unittest.TestCase):
def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
def hmactest(key, data, hexdigests):
hmac_name = "hmac-" + hash_name
h = hmac.HMAC(key, data, digestmod=hashfunc)
self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
self.assertEqual(h.name, hmac_name)
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
digest = hexdigests[hashfunc]
h = hmac.HMAC(key, data, digestmod=hash_name)
self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
self.assertEqual(h.name, hmac_name)
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
self.assertEqual(
hmac.digest(key, data, digest=hashfunc),
binascii.unhexlify(hexdigests[hashfunc])
self.asssert_hmac(
key, data, digest,
hashfunc=hashfunc,
hashname=hash_name,
digest_size=digest_size,
block_size=block_size
)
self.assertEqual(
hmac.digest(key, data, digest=hash_name),
binascii.unhexlify(hexdigests[hashfunc])
)
with unittest.mock.patch('hmac._openssl_md_meths', {}):
self.assertEqual(
hmac.digest(key, data, digest=hashfunc),
binascii.unhexlify(hexdigests[hashfunc])
)
self.assertEqual(
hmac.digest(key, data, digest=hash_name),
binascii.unhexlify(hexdigests[hashfunc])
)
# 4.2. Test Case 1
hmactest(key = b'\x0b'*20,
@ -385,6 +415,14 @@ class ConstructorTestCase(unittest.TestCase):
except Exception:
self.fail("Constructor call with hashlib.sha256 raised exception.")
@unittest.skipUnless(C_HMAC is not None, 'need _hashlib')
def test_internal_types(self):
# internal types like _hashlib.C_HMAC are not constructable
with self.assertRaisesRegex(
TypeError, "cannot create 'HMAC' instance"
):
C_HMAC()
class SanityTestCase(unittest.TestCase):
@ -395,9 +433,9 @@ class SanityTestCase(unittest.TestCase):
try:
h = hmac.HMAC(b"my secret key", digestmod="sha256")
h.update(b"compute the hash of this text!")
dig = h.digest()
dig = h.hexdigest()
h2 = h.copy()
h.digest()
h.hexdigest()
h.copy()
except Exception:
self.fail("Exception raised during normal usage of HMAC class.")
@ -450,6 +488,21 @@ class CopyTestCase(unittest.TestCase):
self.assertEqual(h1.hexdigest(), h2.hexdigest(),
"Hexdigest of copy doesn't match original hexdigest.")
@hashlib_helper.requires_hashdigest('sha256')
def test_equality_new(self):
# Testing if the copy has the same digests with hmac.new().
h1 = hmac.new(b"key", digestmod="sha256")
h1.update(b"some random text")
h2 = h1.copy()
self.assertTrue(
id(h1) != id(h2), "No real copy of the HMAC instance."
)
self.assertEqual(h1.digest(), h2.digest(),
"Digest of copy doesn't match original digest.")
self.assertEqual(h1.hexdigest(), h2.hexdigest(),
"Hexdigest of copy doesn't match original hexdigest.")
class CompareDigestTestCase(unittest.TestCase):
def test_compare_digest(self):