Skip to content

Commit f95260d

Browse files
jimmodpgeorge
authored andcommitted
python-stdlib/hmac: Update to work with built-in hash functions.
This library was non-functional unless used with the micropython-lib pure-Python implementation of hashlib, even if the device provides sha1 and sha256. This updates hmac to be significantly more RAM efficient (removes the 512-byte table), and functional with the built-in hash functions. The only unsupported function is "copy", but this is non-critical, and now fails with a NotSupportedError. Signed-off-by: Jim Mussared <[email protected]>
1 parent 9fdf046 commit f95260d

File tree

3 files changed

+73
-141
lines changed

3 files changed

+73
-141
lines changed

python-stdlib/hmac/hmac.py

+50-124
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,87 @@
1-
"""HMAC (Keyed-Hashing for Message Authentication) Python module.
2-
3-
Implements the HMAC algorithm as described by RFC 2104.
4-
"""
5-
6-
import warnings as _warnings
7-
8-
# from _operator import _compare_digest as compare_digest
9-
import hashlib as _hashlib
10-
11-
PendingDeprecationWarning = None
12-
RuntimeWarning = None
13-
14-
trans_5C = bytes((x ^ 0x5C) for x in range(256))
15-
trans_36 = bytes((x ^ 0x36) for x in range(256))
16-
17-
18-
def translate(d, t):
19-
return bytes(t[x] for x in d)
20-
21-
22-
# The size of the digests returned by HMAC depends on the underlying
23-
# hashing module used. Use digest_size from the instance of HMAC instead.
24-
digest_size = None
1+
# Implements the hmac module from the Python standard library.
252

263

274
class HMAC:
28-
"""RFC 2104 HMAC class. Also complies with RFC 4231.
29-
30-
This supports the API for Cryptographic Hash Functions (PEP 247).
31-
"""
32-
33-
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
34-
355
def __init__(self, key, msg=None, digestmod=None):
36-
"""Create a new HMAC object.
37-
38-
key: key for the keyed hash object.
39-
msg: Initial input for the hash, if provided.
40-
digestmod: A module supporting PEP 247. *OR*
41-
A hashlib constructor returning a new hash object. *OR*
42-
A hash name suitable for hashlib.new().
43-
Defaults to hashlib.md5.
44-
Implicit default to hashlib.md5 is deprecated and will be
45-
removed in Python 3.6.
46-
47-
Note: key and msg must be a bytes or bytearray objects.
48-
"""
49-
506
if not isinstance(key, (bytes, bytearray)):
51-
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
7+
raise TypeError("key: expected bytes/bytearray")
8+
9+
import hashlib
5210

5311
if digestmod is None:
54-
_warnings.warn(
55-
"HMAC() without an explicit digestmod argument " "is deprecated.",
56-
PendingDeprecationWarning,
57-
2,
58-
)
59-
digestmod = _hashlib.md5
12+
# TODO: Default hash algorithm is now deprecated.
13+
digestmod = hashlib.md5
6014

6115
if callable(digestmod):
62-
self.digest_cons = digestmod
16+
# A hashlib constructor returning a new hash object.
17+
make_hash = digestmod # A
6318
elif isinstance(digestmod, str):
64-
self.digest_cons = lambda d=b"": _hashlib.new(digestmod, d)
65-
else:
66-
self.digest_cons = lambda d=b"": digestmod.new(d)
67-
68-
self.outer = self.digest_cons()
69-
self.inner = self.digest_cons()
70-
self.digest_size = self.inner.digest_size
71-
72-
if hasattr(self.inner, "block_size"):
73-
blocksize = self.inner.block_size
74-
if blocksize < 16:
75-
_warnings.warn(
76-
"block_size of %d seems too small; using our "
77-
"default of %d." % (blocksize, self.blocksize),
78-
RuntimeWarning,
79-
2,
80-
)
81-
blocksize = self.blocksize
19+
# A hash name suitable for hashlib.new().
20+
make_hash = lambda d=b"": hashlib.new(digestmod, d) # B
8221
else:
83-
_warnings.warn(
84-
"No block_size attribute on given digest object; "
85-
"Assuming %d." % (self.blocksize),
86-
RuntimeWarning,
87-
2,
88-
)
89-
blocksize = self.blocksize
90-
91-
# self.blocksize is the default blocksize. self.block_size is
92-
# effective block size as well as the public API attribute.
93-
self.block_size = blocksize
94-
95-
if len(key) > blocksize:
96-
key = self.digest_cons(key).digest()
97-
98-
key = key + bytes(blocksize - len(key))
99-
self.outer.update(translate(key, trans_5C))
100-
self.inner.update(translate(key, trans_36))
22+
# A module supporting PEP 247.
23+
make_hash = digestmod.new # C
24+
25+
self._outer = make_hash()
26+
self._inner = make_hash()
27+
28+
self.digest_size = getattr(self._inner, "digest_size", None)
29+
# If the provided hash doesn't support block_size (e.g. built-in
30+
# hashlib), 64 is the correct default for all built-in hash
31+
# functions (md5, sha1, sha256).
32+
self.block_size = getattr(self._inner, "block_size", 64)
33+
34+
# Truncate to digest_size if greater than block_size.
35+
if len(key) > self.block_size:
36+
key = make_hash(key).digest()
37+
38+
# Pad to block size.
39+
key = key + bytes(self.block_size - len(key))
40+
41+
self._outer.update(bytes(x ^ 0x5C for x in key))
42+
self._inner.update(bytes(x ^ 0x36 for x in key))
43+
10144
if msg is not None:
10245
self.update(msg)
10346

10447
@property
10548
def name(self):
106-
return "hmac-" + self.inner.name
49+
return "hmac-" + getattr(self._inner, "name", type(self._inner).__name__)
10750

10851
def update(self, msg):
109-
"""Update this hashing object with the string msg."""
110-
self.inner.update(msg)
52+
self._inner.update(msg)
11153

11254
def copy(self):
113-
"""Return a separate copy of this hashing object.
114-
115-
An update to this copy won't affect the original object.
116-
"""
55+
if not hasattr(self._inner, "copy"):
56+
# Not supported for built-in hash functions.
57+
raise NotImplementedError()
11758
# Call __new__ directly to avoid the expensive __init__.
11859
other = self.__class__.__new__(self.__class__)
119-
other.digest_cons = self.digest_cons
60+
other.block_size = self.block_size
12061
other.digest_size = self.digest_size
121-
other.inner = self.inner.copy()
122-
other.outer = self.outer.copy()
62+
other._inner = self._inner.copy()
63+
other._outer = self._outer.copy()
12364
return other
12465

12566
def _current(self):
126-
"""Return a hash object for the current state.
127-
128-
To be used only internally with digest() and hexdigest().
129-
"""
130-
h = self.outer.copy()
131-
h.update(self.inner.digest())
67+
h = self._outer
68+
if hasattr(h, "copy"):
69+
# built-in hash functions don't support this, and as a result,
70+
# digest() will finalise the hmac and further calls to
71+
# update/digest will fail.
72+
h = h.copy()
73+
h.update(self._inner.digest())
13274
return h
13375

13476
def digest(self):
135-
"""Return the hash value of this hashing object.
136-
137-
This returns a string containing 8-bit data. The object is
138-
not altered in any way by this function; you can continue
139-
updating the object after calling this function.
140-
"""
14177
h = self._current()
14278
return h.digest()
14379

14480
def hexdigest(self):
145-
"""Like digest(), but returns a string of hexadecimal digits instead."""
146-
h = self._current()
147-
return h.hexdigest()
81+
import binascii
14882

83+
return str(binascii.hexlify(self.digest()), "utf-8")
14984

150-
def new(key, msg=None, digestmod=None):
151-
"""Create a new hashing object and return it.
152-
153-
key: The starting key for the hash.
154-
msg: if available, will immediately be hashed into the object's starting
155-
state.
15685

157-
You can now feed arbitrary strings into the object using its update()
158-
method, and can ask for the hash value at any time by calling its digest()
159-
method.
160-
"""
86+
def new(key, msg=None, digestmod=None):
16187
return HMAC(key, msg, digestmod)

python-stdlib/hmac/metadata.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
srctype = cpython
22
type = module
33
version = 3.4.2-3
4-
depends = warnings, hashlib

python-stdlib/hmac/test_hmac.py

+23-16
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,56 @@
11
import hmac
2-
from hashlib.sha256 import sha256
3-
from hashlib.sha512 import sha512
2+
3+
# Uncomment to use micropython-lib hashlib (supports sha512)
4+
# import sys
5+
# sys.path.append('../hashlib')
6+
7+
import hashlib
48

59
msg = b"zlutoucky kun upel dabelske ody"
610

7-
dig = hmac.new(b"1234567890", msg=msg, digestmod=sha256).hexdigest()
11+
dig = hmac.new(b"1234567890", msg=msg, digestmod=hashlib.sha256).hexdigest()
812

913
print("c735e751e36b08fb01e25794bdb15e7289b82aecdb652c8f4f72f307b39dad39")
1014
print(dig)
1115

1216
if dig != "c735e751e36b08fb01e25794bdb15e7289b82aecdb652c8f4f72f307b39dad39":
1317
raise Exception("Error")
1418

15-
dig = hmac.new(b"1234567890", msg=msg, digestmod=sha512).hexdigest()
19+
if hasattr(hashlib, "sha512"):
20+
dig = hmac.new(b"1234567890", msg=msg, digestmod=hashlib.sha512).hexdigest()
1621

17-
print(
18-
"59942f31b6f5473fb4eb630fabf5358a49bc11d24ebc83b114b4af30d6ef47ea14b673f478586f520a0b9c53b27c8f8dd618c165ef586195bd4e98293d34df1a"
19-
)
20-
print(dig)
22+
print(
23+
"59942f31b6f5473fb4eb630fabf5358a49bc11d24ebc83b114b4af30d6ef47ea14b673f478586f520a0b9c53b27c8f8dd618c165ef586195bd4e98293d34df1a"
24+
)
25+
print(dig)
2126

22-
if (
23-
dig
24-
!= "59942f31b6f5473fb4eb630fabf5358a49bc11d24ebc83b114b4af30d6ef47ea14b673f478586f520a0b9c53b27c8f8dd618c165ef586195bd4e98293d34df1a"
25-
):
26-
raise Exception("Error")
27+
if (
28+
dig
29+
!= "59942f31b6f5473fb4eb630fabf5358a49bc11d24ebc83b114b4af30d6ef47ea14b673f478586f520a0b9c53b27c8f8dd618c165ef586195bd4e98293d34df1a"
30+
):
31+
raise Exception("Error")
32+
else:
33+
print("sha512 not supported")
2734

2835
key = b"\x06\x1au\x90|Xz;o\x1b<\xafGL\xbfn\x8a\xc94YPfC^\xb9\xdd)\x7f\xaf\x85\xa1\xed\x82\xbexp\xaf\x13\x1a\x9d"
2936

30-
dig = hmac.new(key[:20], msg=msg, digestmod=sha256).hexdigest()
37+
dig = hmac.new(key[:20], msg=msg, digestmod=hashlib.sha256).hexdigest()
3138

3239
print("59e332b881df09fdecf569c8b142b27fc989638720aeda2813f82442b6e3d91b")
3340
print(dig)
3441

3542
if dig != "59e332b881df09fdecf569c8b142b27fc989638720aeda2813f82442b6e3d91b":
3643
raise Exception("Error")
3744

38-
dig = hmac.new(key[:32], msg=msg, digestmod=sha256).hexdigest()
45+
dig = hmac.new(key[:32], msg=msg, digestmod=hashlib.sha256).hexdigest()
3946

4047
print("b72fed815cd71acfa3a2f5cf2343679565fa18e7cd92226ab443aabd1fd7b7b0")
4148
print(dig)
4249

4350
if dig != "b72fed815cd71acfa3a2f5cf2343679565fa18e7cd92226ab443aabd1fd7b7b0":
4451
raise Exception("Error")
4552

46-
dig = hmac.new(key, msg=msg, digestmod=sha256).hexdigest()
53+
dig = hmac.new(key, msg=msg, digestmod=hashlib.sha256).hexdigest()
4754

4855
print("4e51beae6c2b0f90bb3e99d8e93a32d168b6c1e9b7d2130e2d668a3b3e10358d")
4956
print(dig)

0 commit comments

Comments
 (0)