Skip to content

Commit 89d7608

Browse files
committed
add HMAC python interface
1 parent 807eb3c commit 89d7608

File tree

1 file changed

+60
-30
lines changed

1 file changed

+60
-30
lines changed

Lib/hmac.py

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
Implements the HMAC algorithm as described by RFC 2104.
44
"""
55

6-
import warnings as _warnings
76
try:
87
import _hashlib as _hashopenssl
98
except ImportError:
@@ -14,7 +13,10 @@
1413
compare_digest = _hashopenssl.compare_digest
1514
_functype = type(_hashopenssl.openssl_sha256) # builtin type
1615

17-
import hashlib as _hashlib
16+
try:
17+
import _hmac
18+
except ImportError:
19+
_hmac = None
1820

1921
trans_5C = bytes((x ^ 0x5C) for x in range(256))
2022
trans_36 = bytes((x ^ 0x36) for x in range(256))
@@ -23,12 +25,26 @@
2325
# hashing module used. Use digest_size from the instance of HMAC instead.
2426
digest_size = None
2527

28+
def _get_digest_constructor(digest_like):
29+
if callable(digest_like):
30+
return digest_like
31+
if isinstance(digest_like, str):
32+
def digest_wrapper(d=b''):
33+
import hashlib
34+
return hashlib.new(digest_like, d)
35+
else:
36+
def digest_wrapper(d=b''):
37+
return digest_like.new(d)
38+
return digest_wrapper
2639

2740
class HMAC:
2841
"""RFC 2104 HMAC class. Also complies with RFC 4231.
2942
3043
This supports the API for Cryptographic Hash Functions (PEP 247).
3144
"""
45+
46+
# Note: self.blocksize is the default blocksize; self.block_size
47+
# is effective block size as well as the public API attribute.
3248
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
3349

3450
__slots__ = (
@@ -50,31 +66,45 @@ def __init__(self, key, msg=None, digestmod=''):
5066
"""
5167

5268
if not isinstance(key, (bytes, bytearray)):
53-
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
69+
raise TypeError(f"key: expected bytes or bytearray, "
70+
f"but got {type(key).__name__!r}")
5471

5572
if not digestmod:
5673
raise TypeError("Missing required argument 'digestmod'.")
5774

75+
self.__init(key, msg, digestmod)
76+
77+
def __init(self, key, msg, digestmod):
5878
if _hashopenssl and isinstance(digestmod, (str, _functype)):
5979
try:
60-
self._init_hmac(key, msg, digestmod)
80+
self._init_openssl_hmac(key, msg, digestmod)
81+
return
6182
except _hashopenssl.UnsupportedDigestmodError:
62-
self._init_old(key, msg, digestmod)
63-
else:
64-
self._init_old(key, msg, digestmod)
83+
pass
84+
if _hmac and isinstance(digestmod, str):
85+
try:
86+
self._init_builtin_hmac(key, msg, digestmod)
87+
return
88+
except _hmac.UnknownHashError:
89+
pass
90+
self._init_old(key, msg, digestmod)
6591

66-
def _init_hmac(self, key, msg, digestmod):
92+
def _init_openssl_hmac(self, key, msg, digestmod):
6793
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
6894
self.digest_size = self._hmac.digest_size
6995
self.block_size = self._hmac.block_size
7096

97+
_init_hmac = _init_openssl_hmac # for backward compatibility (if any)
98+
99+
def _init_builtin_hmac(self, key, msg, digestmod):
100+
self._hmac = _hmac.new(key, msg, digestmod=digestmod)
101+
self.digest_size = self._hmac.digest_size
102+
self.block_size = self._hmac.block_size
103+
71104
def _init_old(self, key, msg, digestmod):
72-
if callable(digestmod):
73-
digest_cons = digestmod
74-
elif isinstance(digestmod, str):
75-
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
76-
else:
77-
digest_cons = lambda d=b'': digestmod.new(d)
105+
import warnings
106+
107+
digest_cons = _get_digest_constructor(digestmod)
78108

79109
self._hmac = None
80110
self._outer = digest_cons()
@@ -84,21 +114,19 @@ def _init_old(self, key, msg, digestmod):
84114
if hasattr(self._inner, 'block_size'):
85115
blocksize = self._inner.block_size
86116
if blocksize < 16:
87-
_warnings.warn('block_size of %d seems too small; using our '
88-
'default of %d.' % (blocksize, self.blocksize),
89-
RuntimeWarning, 2)
117+
warnings.warn(f"block_size of {blocksize} seems too small; "
118+
f"using our default of {self.blocksize}.",
119+
RuntimeWarning, 2)
90120
blocksize = self.blocksize
91121
else:
92-
_warnings.warn('No block_size attribute on given digest object; '
93-
'Assuming %d.' % (self.blocksize),
94-
RuntimeWarning, 2)
122+
warnings.warn("No block_size attribute on given digest object; "
123+
f"Assuming {self.blocksize}.",
124+
RuntimeWarning, 2)
95125
blocksize = self.blocksize
96126

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

100-
# self.blocksize is the default blocksize. self.block_size is
101-
# effective block size as well as the public API attribute.
102130
self.block_size = blocksize
103131

104132
key = key.ljust(blocksize, b'\0')
@@ -183,7 +211,6 @@ def new(key, msg=None, digestmod=''):
183211
"""
184212
return HMAC(key, msg, digestmod)
185213

186-
187214
def digest(key, msg, digest):
188215
"""Fast inline implementation of HMAC.
189216
@@ -199,19 +226,22 @@ def digest(key, msg, digest):
199226
except _hashopenssl.UnsupportedDigestmodError:
200227
pass
201228

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)
229+
if _hmac is not None and isinstance(digest, str):
230+
try:
231+
return _hmac.compute_digest(key, msg, digest)
232+
except (OverflowError, _hmac.UnknownHashError):
233+
pass
234+
235+
return _compute_digest_fallback(key, msg, digest)
208236

237+
def _compute_digest_fallback(key, msg, digest):
238+
digest_cons = _get_digest_constructor(digest)
209239
inner = digest_cons()
210240
outer = digest_cons()
211241
blocksize = getattr(inner, 'block_size', 64)
212242
if len(key) > blocksize:
213243
key = digest_cons(key).digest()
214-
key = key + b'\x00' * (blocksize - len(key))
244+
key = key.ljust(blocksize, b'\0')
215245
inner.update(key.translate(trans_36))
216246
outer.update(key.translate(trans_5C))
217247
inner.update(msg)

0 commit comments

Comments
 (0)