Skip to content

Commit 33da5bf

Browse files
committed
Updated Lib/hmac.py to cpython 3.11 version and included hmac test suite
1 parent e0a2948 commit 33da5bf

File tree

2 files changed

+777
-46
lines changed

2 files changed

+777
-46
lines changed

Lib/hmac.py

Lines changed: 121 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
"""HMAC (Keyed-Hashing for Message Authentication) Python module.
1+
"""HMAC (Keyed-Hashing for Message Authentication) module.
22
33
Implements the HMAC algorithm as described by RFC 2104.
44
"""
55

66
import warnings as _warnings
7-
from _operator import _compare_digest as compare_digest
7+
try:
8+
import _hashlib as _hashopenssl
9+
except ImportError:
10+
_hashopenssl = None
11+
_functype = None
12+
from _operator import _compare_digest as compare_digest
13+
else:
14+
compare_digest = _hashopenssl.compare_digest
15+
_functype = type(_hashopenssl.openssl_sha256) # builtin type
16+
817
import hashlib as _hashlib
918

1019
trans_5C = bytes((x ^ 0x5C) for x in range(256))
@@ -15,50 +24,65 @@
1524
digest_size = None
1625

1726

18-
1927
class HMAC:
2028
"""RFC 2104 HMAC class. Also complies with RFC 4231.
2129
2230
This supports the API for Cryptographic Hash Functions (PEP 247).
2331
"""
2432
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
2533

26-
def __init__(self, key, msg = None, digestmod = None):
34+
__slots__ = (
35+
"_hmac", "_inner", "_outer", "block_size", "digest_size"
36+
)
37+
38+
def __init__(self, key, msg=None, digestmod=''):
2739
"""Create a new HMAC object.
2840
29-
key: key for the keyed hash object.
30-
msg: Initial input for the hash, if provided.
31-
digestmod: A module supporting PEP 247. *OR*
41+
key: bytes or buffer, key for the keyed hash object.
42+
msg: bytes or buffer, Initial input for the hash or None.
43+
digestmod: A hash name suitable for hashlib.new(). *OR*
3244
A hashlib constructor returning a new hash object. *OR*
33-
A hash name suitable for hashlib.new().
34-
Defaults to hashlib.md5.
35-
Implicit default to hashlib.md5 is deprecated and will be
36-
removed in Python 3.6.
45+
A module supporting PEP 247.
3746
38-
Note: key and msg must be a bytes or bytearray objects.
47+
Required as of 3.8, despite its position after the optional
48+
msg argument. Passing it as a keyword argument is
49+
recommended, though not required for legacy API reasons.
3950
"""
4051

4152
if not isinstance(key, (bytes, bytearray)):
4253
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
4354

44-
if digestmod is None:
45-
_warnings.warn("HMAC() without an explicit digestmod argument "
46-
"is deprecated.", PendingDeprecationWarning, 2)
47-
digestmod = _hashlib.md5
55+
if not digestmod:
56+
raise TypeError("Missing required parameter 'digestmod'.")
57+
58+
if _hashopenssl and isinstance(digestmod, (str, _functype)):
59+
try:
60+
self._init_hmac(key, msg, digestmod)
61+
except _hashopenssl.UnsupportedDigestmodError:
62+
self._init_old(key, msg, digestmod)
63+
else:
64+
self._init_old(key, msg, digestmod)
65+
66+
def _init_hmac(self, key, msg, digestmod):
67+
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
68+
self.digest_size = self._hmac.digest_size
69+
self.block_size = self._hmac.block_size
4870

71+
def _init_old(self, key, msg, digestmod):
4972
if callable(digestmod):
50-
self.digest_cons = digestmod
73+
digest_cons = digestmod
5174
elif isinstance(digestmod, str):
52-
self.digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
75+
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
5376
else:
54-
self.digest_cons = lambda d=b'': digestmod.new(d)
77+
digest_cons = lambda d=b'': digestmod.new(d)
5578

56-
self.outer = self.digest_cons()
57-
self.inner = self.digest_cons()
58-
self.digest_size = self.inner.digest_size
79+
self._hmac = None
80+
self._outer = digest_cons()
81+
self._inner = digest_cons()
82+
self.digest_size = self._inner.digest_size
5983

60-
if hasattr(self.inner, 'block_size'):
61-
blocksize = self.inner.block_size
84+
if hasattr(self._inner, 'block_size'):
85+
blocksize = self._inner.block_size
6286
if blocksize < 16:
6387
_warnings.warn('block_size of %d seems too small; using our '
6488
'default of %d.' % (blocksize, self.blocksize),
@@ -70,27 +94,30 @@ def __init__(self, key, msg = None, digestmod = None):
7094
RuntimeWarning, 2)
7195
blocksize = self.blocksize
7296

97+
if len(key) > blocksize:
98+
key = digest_cons(key).digest()
99+
73100
# self.blocksize is the default blocksize. self.block_size is
74101
# effective block size as well as the public API attribute.
75102
self.block_size = blocksize
76103

77-
if len(key) > blocksize:
78-
key = self.digest_cons(key).digest()
79-
80104
key = key.ljust(blocksize, b'\0')
81-
self.outer.update(key.translate(trans_5C))
82-
self.inner.update(key.translate(trans_36))
105+
self._outer.update(key.translate(trans_5C))
106+
self._inner.update(key.translate(trans_36))
83107
if msg is not None:
84108
self.update(msg)
85109

86110
@property
87111
def name(self):
88-
return "hmac-" + self.inner.name
112+
if self._hmac:
113+
return self._hmac.name
114+
else:
115+
return f"hmac-{self._inner.name}"
89116

90117
def update(self, msg):
91-
"""Update this hashing object with the string msg.
92-
"""
93-
self.inner.update(msg)
118+
"""Feed data from msg into this hashing object."""
119+
inst = self._hmac or self._inner
120+
inst.update(msg)
94121

95122
def copy(self):
96123
"""Return a separate copy of this hashing object.
@@ -99,25 +126,32 @@ def copy(self):
99126
"""
100127
# Call __new__ directly to avoid the expensive __init__.
101128
other = self.__class__.__new__(self.__class__)
102-
other.digest_cons = self.digest_cons
103129
other.digest_size = self.digest_size
104-
other.inner = self.inner.copy()
105-
other.outer = self.outer.copy()
130+
if self._hmac:
131+
other._hmac = self._hmac.copy()
132+
other._inner = other._outer = None
133+
else:
134+
other._hmac = None
135+
other._inner = self._inner.copy()
136+
other._outer = self._outer.copy()
106137
return other
107138

108139
def _current(self):
109140
"""Return a hash object for the current state.
110141
111142
To be used only internally with digest() and hexdigest().
112143
"""
113-
h = self.outer.copy()
114-
h.update(self.inner.digest())
115-
return h
144+
if self._hmac:
145+
return self._hmac
146+
else:
147+
h = self._outer.copy()
148+
h.update(self._inner.digest())
149+
return h
116150

117151
def digest(self):
118152
"""Return the hash value of this hashing object.
119153
120-
This returns a string containing 8-bit data. The object is
154+
This returns the hmac value as bytes. The object is
121155
not altered in any way by this function; you can continue
122156
updating the object after calling this function.
123157
"""
@@ -130,15 +164,56 @@ def hexdigest(self):
130164
h = self._current()
131165
return h.hexdigest()
132166

133-
def new(key, msg = None, digestmod = None):
167+
def new(key, msg=None, digestmod=''):
134168
"""Create a new hashing object and return it.
135169
136-
key: The starting key for the hash.
137-
msg: if available, will immediately be hashed into the object's starting
138-
state.
170+
key: bytes or buffer, The starting key for the hash.
171+
msg: bytes or buffer, Initial input for the hash, or None.
172+
digestmod: A hash name suitable for hashlib.new(). *OR*
173+
A hashlib constructor returning a new hash object. *OR*
174+
A module supporting PEP 247.
175+
176+
Required as of 3.8, despite its position after the optional
177+
msg argument. Passing it as a keyword argument is
178+
recommended, though not required for legacy API reasons.
139179
140-
You can now feed arbitrary strings into the object using its update()
180+
You can now feed arbitrary bytes into the object using its update()
141181
method, and can ask for the hash value at any time by calling its digest()
142-
method.
182+
or hexdigest() methods.
143183
"""
144184
return HMAC(key, msg, digestmod)
185+
186+
187+
def digest(key, msg, digest):
188+
"""Fast inline implementation of HMAC.
189+
190+
key: bytes or buffer, The key for the keyed hash object.
191+
msg: bytes or buffer, Input message.
192+
digest: A hash name suitable for hashlib.new() for best performance. *OR*
193+
A hashlib constructor returning a new hash object. *OR*
194+
A module supporting PEP 247.
195+
"""
196+
if _hashopenssl is not None and isinstance(digest, (str, _functype)):
197+
try:
198+
return _hashopenssl.hmac_digest(key, msg, digest)
199+
except _hashopenssl.UnsupportedDigestmodError:
200+
pass
201+
202+
if callable(digest):
203+
digest_cons = digest
204+
elif isinstance(digest, str):
205+
digest_cons = lambda d=b'': _hashlib.new(digest, d)
206+
else:
207+
digest_cons = lambda d=b'': digest.new(d)
208+
209+
inner = digest_cons()
210+
outer = digest_cons()
211+
blocksize = getattr(inner, 'block_size', 64)
212+
if len(key) > blocksize:
213+
key = digest_cons(key).digest()
214+
key = key + b'\x00' * (blocksize - len(key))
215+
inner.update(key.translate(trans_36))
216+
outer.update(key.translate(trans_5C))
217+
inner.update(msg)
218+
outer.update(inner.digest())
219+
return outer.digest()

0 commit comments

Comments
 (0)