Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def __hash_new(name, data=b'', **kwargs):
algorithms_available = algorithms_available.union(
_hashlib.openssl_md_meth_names)
except ImportError:
_hashlib = None
new = __py_new
__get_hash = __get_builtin_constructor

Expand Down
86 changes: 51 additions & 35 deletions Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import _hashlib as _hashopenssl
except ImportError:
_hashopenssl = None
_openssl_md_meths = None
_functype = None
from _operator import _compare_digest as compare_digest
else:
_openssl_md_meths = frozenset(_hashopenssl.openssl_md_meth_names)
compare_digest = _hashopenssl.compare_digest
_functype = type(_hashopenssl.openssl_sha256) # builtin type

import hashlib as _hashlib

trans_5C = bytes((x ^ 0x5C) for x in range(256))
Expand All @@ -23,7 +24,6 @@
digest_size = None



class HMAC:
"""RFC 2104 HMAC class. Also complies with RFC 4231.

Expand All @@ -32,7 +32,7 @@ class HMAC:
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.

__slots__ = (
"_digest_cons", "_inner", "_outer", "block_size", "digest_size"
"_hmac", "_inner", "_outer", "block_size", "digest_size"
)

def __init__(self, key, msg=None, digestmod=''):
Expand All @@ -55,15 +55,30 @@ def __init__(self, key, msg=None, digestmod=''):
if not digestmod:
raise TypeError("Missing required parameter 'digestmod'.")

if _hashopenssl and isinstance(digestmod, (str, _functype)):
try:
self._init_hmac(key, msg, digestmod)
except ValueError:
self._init_old(key, msg, digestmod)
else:
self._init_old(key, msg, digestmod)

def _init_hmac(self, key, msg, digestmod):
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
self.digest_size = self._hmac.digest_size
self.block_size = self._hmac.block_size

def _init_old(self, key, msg, digestmod):
if callable(digestmod):
self._digest_cons = digestmod
digest_cons = digestmod
elif isinstance(digestmod, str):
self._digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
else:
self._digest_cons = lambda d=b'': digestmod.new(d)
digest_cons = lambda d=b'': digestmod.new(d)

self._outer = self._digest_cons()
self._inner = self._digest_cons()
self._hmac = None
self._outer = digest_cons()
self._inner = digest_cons()
self.digest_size = self._inner.digest_size

if hasattr(self._inner, 'block_size'):
Expand All @@ -79,13 +94,13 @@ def __init__(self, key, msg=None, digestmod=''):
RuntimeWarning, 2)
blocksize = self.blocksize

if len(key) > blocksize:
key = digest_cons(key).digest()

# self.blocksize is the default blocksize. self.block_size is
# effective block size as well as the public API attribute.
self.block_size = blocksize

if len(key) > blocksize:
key = self._digest_cons(key).digest()

key = key.ljust(blocksize, b'\0')
self._outer.update(key.translate(trans_5C))
self._inner.update(key.translate(trans_36))
Expand All @@ -94,23 +109,15 @@ def __init__(self, key, msg=None, digestmod=''):

@property
def name(self):
return "hmac-" + self._inner.name

@property
def digest_cons(self):
return self._digest_cons

@property
def inner(self):
return self._inner

@property
def outer(self):
return self._outer
if self._hmac:
return self._hmac.name
else:
return f"hmac-{self._inner.name}"

def update(self, msg):
"""Feed data from msg into this hashing object."""
self._inner.update(msg)
inst = self._hmac or self._inner
inst.update(msg)

def copy(self):
"""Return a separate copy of this hashing object.
Expand All @@ -119,20 +126,27 @@ def copy(self):
"""
# Call __new__ directly to avoid the expensive __init__.
other = self.__class__.__new__(self.__class__)
other._digest_cons = self._digest_cons
other.digest_size = self.digest_size
other._inner = self._inner.copy()
other._outer = self._outer.copy()
if self._hmac:
other._hmac = self._hmac.copy()
other._inner = other._outer = None
else:
other._hmac = None
other._inner = self._inner.copy()
other._outer = self._outer.copy()
return other

def _current(self):
"""Return a hash object for the current state.

To be used only internally with digest() and hexdigest().
"""
h = self._outer.copy()
h.update(self._inner.digest())
return h
if self._hmac:
return self._hmac
else:
h = self._outer.copy()
h.update(self._inner.digest())
return h

def digest(self):
"""Return the hash value of this hashing object.
Expand Down Expand Up @@ -179,9 +193,11 @@ def digest(key, msg, digest):
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
"""
if (_hashopenssl is not None and
isinstance(digest, str) and digest in _openssl_md_meths):
return _hashopenssl.hmac_digest(key, msg, digest)
if _hashopenssl is not None and isinstance(digest, (str, _functype)):
try:
return _hashopenssl.hmac_digest(key, msg, digest)
except ValueError:
pass
Comment thread
gpshead marked this conversation as resolved.

if callable(digest):
digest_cons = digest
Expand Down
72 changes: 42 additions & 30 deletions Lib/test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
from _operator import _compare_digest as operator_compare_digest

try:
import _hashlib as _hashopenssl
from _hashlib import HMAC as C_HMAC
from _hashlib import hmac_new as c_hmac_new
from _hashlib import compare_digest as openssl_compare_digest
except ImportError:
_hashopenssl = None
C_HMAC = None
c_hmac_new = None
openssl_compare_digest = None
Expand All @@ -32,22 +34,27 @@ def wrapper(*args, **kwargs):

class TestVectorsTestCase(unittest.TestCase):

def asssert_hmac(
self, key, data, digest, hashfunc, hashname, digest_size, block_size
def assert_hmac_internals(
self, h, digest, 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)

def assert_hmac(
self, key, data, digest, hashfunc, hashname, digest_size, block_size
):
h = hmac.HMAC(key, data, digestmod=hashfunc)
self.assert_hmac_internals(
h, digest, hashname, digest_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)
self.assert_hmac_internals(
h, digest, hashname, digest_size, block_size
)

h = hmac.HMAC(key, digestmod=hashname)
h2 = h.copy()
Expand All @@ -56,11 +63,9 @@ def asssert_hmac(
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)
self.assert_hmac_internals(
h, digest, hashname, digest_size, block_size
)

h = hmac.new(key, None, digestmod=hashname)
h.update(data)
Expand All @@ -81,36 +86,43 @@ def asssert_hmac(
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)
)

h = hmac.HMAC.__new__(hmac.HMAC)
h._init_old(key, data, digestmod=hashname)
self.assert_hmac_internals(
h, digest, hashname, digest_size, block_size
)

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)
self.assert_hmac_internals(
h, digest, hashname, digest_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())

func = getattr(_hashopenssl, f"openssl_{hashname}")
h = c_hmac_new(key, data, digestmod=func)
self.assert_hmac_internals(
h, digest, hashname, digest_size, block_size
)

h = hmac.HMAC.__new__(hmac.HMAC)
h._init_hmac(key, data, digestmod=hashname)
self.assert_hmac_internals(
h, digest, hashname, digest_size, block_size
)

@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):
self.asssert_hmac(
self.assert_hmac(
key, data, digest,
hashfunc=hashlib.md5,
hashname="md5",
Expand Down Expand Up @@ -150,7 +162,7 @@ def md5test(key, data, digest):
@hashlib_helper.requires_hashdigest('sha1', openssl=True)
def test_sha_vectors(self):
def shatest(key, data, digest):
self.asssert_hmac(
self.assert_hmac(
key, data, digest,
hashfunc=hashlib.sha1,
hashname="sha1",
Expand Down Expand Up @@ -191,7 +203,7 @@ def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
def hmactest(key, data, hexdigests):
digest = hexdigests[hashfunc]

self.asssert_hmac(
self.assert_hmac(
key, data, digest,
hashfunc=hashfunc,
hashname=hash_name,
Expand Down
Loading