1
- """HMAC (Keyed-Hashing for Message Authentication) Python module.
1
+ """HMAC (Keyed-Hashing for Message Authentication) MicroPython module.
2
2
3
3
Implements the HMAC algorithm as described by RFC 2104.
4
4
"""
5
5
6
- import warnings as _warnings
6
+ # import warnings as _warnings
7
7
#from _operator import _compare_digest as compare_digest
8
- import hashlib as _hashlib
9
- PendingDeprecationWarning = None
10
- RuntimeWarning = None
8
+ #import hashlib as _hashlib
9
+ #PendingDeprecationWarning = None
10
+ #RuntimeWarning = None
11
+ import uhashlib as _hashlib
11
12
12
13
trans_5C = bytes ((x ^ 0x5C ) for x in range (256 ))
13
14
trans_36 = bytes ((x ^ 0x36 ) for x in range (256 ))
14
15
15
16
def translate (d , t ):
16
- return b'' .join ([ chr (t [x ]).encode ('ascii' ) for x in d ])
17
+ # Using bytes with a throw away array instead of char below
18
+ # to avoid ending up with the wrong key when a key in the
19
+ # form of b'\xAA' is used.
20
+ return b'' .join ([bytes ([t [x ]]) for x in d ])
17
21
18
22
# The size of the digests returned by HMAC depends on the underlying
19
23
# hashing module used. Use digest_size from the instance of HMAC instead.
@@ -26,54 +30,72 @@ class HMAC:
26
30
27
31
This supports the API for Cryptographic Hash Functions (PEP 247).
28
32
"""
29
- blocksize = 64 # 512-bit HMAC; can be changed in subclasses .
33
+ blocksize = 64 # 512-bit HMAC; Both sha1 and sha256 have a 512 bits blocksize .
30
34
31
- def __init__ (self , key , msg = None , digestmod = None ):
35
+ def __init__ (self , key , msg = None , digestmod = None ):
32
36
"""Create a new HMAC object.
33
37
34
38
key: key for the keyed hash object.
35
39
msg: Initial input for the hash, if provided.
36
- digestmod: A module supporting PEP 247. *OR*
37
- A hashlib constructor returning a new hash object. *OR*
38
- A hash name suitable for hashlib.new().
39
- Defaults to hashlib.md5.
40
- Implicit default to hashlib.md5 is deprecated and will be
41
- removed in Python 3.6.
40
+ digestmod: A module supporting PEP 247, *OR*
41
+ A hash name suitable for hashlib.new() *OR*
42
+ A hashlib constructor returning a new hash object.
43
+ Defaults to uhashlib.sha256.
42
44
43
45
Note: key and msg must be a bytes or bytearray objects.
44
46
"""
45
47
48
+ self .finished = False
49
+ self .digest_bytes = None
50
+ self .hex_bytes = None
51
+
46
52
if not isinstance (key , (bytes , bytearray )):
47
53
raise TypeError ("key: expected bytes or bytearray, but got %r" % type (key ).__name__ )
48
54
49
55
if digestmod is None :
50
- _warnings .warn ("HMAC() without an explicit digestmod argument "
51
- "is deprecated." , PendingDeprecationWarning , 2 )
52
- digestmod = _hashlib .md5
56
+ #_warnings.warn("HMAC() without an explicit digestmod argument "
57
+ # "is deprecated.", PendingDeprecationWarning, 2)
58
+ #digestmod = _hashlib.md5
59
+ digestmod = _hashlib .sha256
53
60
54
61
if callable (digestmod ):
55
62
self .digest_cons = digestmod
56
63
elif isinstance (digestmod , str ):
57
- self .digest_cons = lambda d = b'' : _hashlib .new (digestmod , d )
64
+ self .digest_cons = lambda d = b'' : getattr (_hashlib , digestmod )(d )
65
+ elif isinstance (digestmod , (bytes , bytearray )):
66
+ self .digest_cons = lambda d = b'' : getattr (_hashlib , str (digestmod )[2 :- 1 :])(d )
58
67
else :
59
68
self .digest_cons = lambda d = b'' : digestmod .new (d )
60
69
61
70
self .outer = self .digest_cons ()
62
71
self .inner = self .digest_cons ()
63
- self .digest_size = self .inner .digest_size
64
-
65
- if hasattr (self .inner , 'block_size' ):
66
- blocksize = self .inner .block_size
67
- if blocksize < 16 :
68
- _warnings .warn ('block_size of %d seems too small; using our '
69
- 'default of %d.' % (blocksize , self .blocksize ),
70
- RuntimeWarning , 2 )
71
- blocksize = self .blocksize
72
+ #self.digest_size = self.inner.digest_size
73
+
74
+ #if hasattr(self.inner, 'block_size'):
75
+ # blocksize = self.inner.block_size
76
+ # if blocksize < 16:
77
+ # _warnings.warn('block_size of %d seems too small; using our '
78
+ # 'default of %d.' % (blocksize, self.blocksize),
79
+ # RuntimeWarning, 2)
80
+ # blocksize = self.blocksize
81
+
82
+
83
+ if str (self .inner ) == '<sha1>' :
84
+ self .digest_size = 20
85
+ elif str (self .inner ) == '<sha256>' :
86
+ self .digest_size = 32
72
87
else :
73
- _warnings .warn ('No block_size attribute on given digest object; '
74
- 'Assuming %d.' % (self .blocksize ),
75
- RuntimeWarning , 2 )
76
- blocksize = self .blocksize
88
+ #_warnings.warn('No block_size attribute on given digest object; '
89
+ # 'Assuming %d.' % (self.blocksize),
90
+ # RuntimeWarning, 2)
91
+ #blocksize = self.blocksize
92
+
93
+ # uhashlib doesn't provide a digest_size and we only have hardcoded
94
+ # values for the two uhashlib hash functions.
95
+ self .digest_size = None
96
+
97
+ # Both uhashlib supported algorithms have the same blocksize.
98
+ blocksize = self .blocksize
77
99
78
100
# self.blocksize is the default blocksize. self.block_size is
79
101
# effective block size as well as the public API attribute.
@@ -90,60 +112,138 @@ def __init__(self, key, msg = None, digestmod = None):
90
112
91
113
@property
92
114
def name (self ):
93
- return "hmac-" + self .inner . name
115
+ return "hmac-" + str ( self .inner )[ 1 : - 1 :]
94
116
95
117
def update (self , msg ):
96
118
"""Update this hashing object with the string msg.
97
119
"""
98
- self .inner .update (msg )
99
-
100
- def copy (self ):
101
- """Return a separate copy of this hashing object.
102
-
103
- An update to this copy won't affect the original object.
104
- """
120
+ if not self .finished :
121
+ self .inner .update (msg )
122
+ else :
123
+ # MicroPython's uhashlib sha1 and sha256 don't support the
124
+ # copy method (yet) so not being able to update after a
125
+ # digest is generated is a limitation.
126
+ raise ValueError ('Currently, a digest can only be generated once. '
127
+ 'This object is now "spent" and cannot be updated.' )
128
+ #def copy(self):
129
+ # """Return a separate copy of this hashing object.
130
+ # An update to this copy won't affect the original object.
131
+ # """
105
132
# Call __new__ directly to avoid the expensive __init__.
106
- other = self .__class__ .__new__ (self .__class__ )
107
- other .digest_cons = self .digest_cons
108
- other .digest_size = self .digest_size
109
- other .inner = self .inner .copy ()
110
- other .outer = self .outer .copy ()
111
- return other
133
+ # other = self.__class__.__new__(self.__class__)
134
+ # other.digest_cons = self.digest_cons
135
+ # other.digest_size = self.digest_size
136
+ # other.inner = self.inner.copy()
137
+ # other.outer = self.outer.copy()
138
+ # return other
112
139
113
140
def _current (self ):
114
141
"""Return a hash object for the current state.
115
142
116
143
To be used only internally with digest() and hexdigest().
117
144
"""
118
- h = self .outer .copy ()
119
- h .update (self .inner .digest ())
120
- return h
145
+ #h = self.outer.copy()
146
+ #h.update(self.inner.digest())
147
+ #return h
148
+ self .outer .update (self .inner .digest ())
149
+ return self .outer
121
150
122
151
def digest (self ):
123
152
"""Return the hash value of this hashing object.
124
153
125
- This returns a string containing 8-bit data. The object is
126
- not altered in any way by this function; you can continue
154
+ This returns a string containing 8-bit data. You cannot continue
127
155
updating the object after calling this function.
128
156
"""
129
- h = self ._current ()
130
- return h .digest ()
157
+ #h = self._current()
158
+ #return h.digest()
159
+ if not self .finished :
160
+ h = self ._current ()
161
+ self .digest_bytes = h .digest ()
162
+ import ubinascii
163
+ self .hex_bytes = ubinascii .hexlify (self .digest_bytes )
164
+ del (ubinascii )
165
+ self .finished = True
166
+ return self .digest_bytes
131
167
132
168
def hexdigest (self ):
133
169
"""Like digest(), but returns a string of hexadecimal digits instead.
134
170
"""
135
- h = self ._current ()
136
- return h .hexdigest ()
137
-
138
- def new (key , msg = None , digestmod = None ):
171
+ #h = self._current()
172
+ #return h.hexdigest()
173
+ if not self .finished :
174
+ h = self ._current ()
175
+ self .digest_bytes = h .digest ()
176
+ import ubinascii
177
+ self .hex_bytes = ubinascii .hexlify (self .digest_bytes )
178
+ del (ubinascii )
179
+ self .finished = True
180
+ return self .hex_bytes
181
+
182
+ def new (key , msg = None , digestmod = None ):
139
183
"""Create a new hashing object and return it.
140
184
141
185
key: The starting key for the hash.
142
186
msg: if available, will immediately be hashed into the object's starting
143
187
state.
144
188
145
189
You can now feed arbitrary strings into the object using its update()
146
- method, and can ask for the hash value at any time by calling its digest()
190
+ method, and can ask for the hash value only once by calling its digest()
147
191
method.
148
192
"""
149
193
return HMAC (key , msg , digestmod )
194
+
195
+ def compare_digest (a , b , double_hmac = True , digestmod = b'sha256' ):
196
+ """Test two digests for equality in a more secure way than "==".
197
+
198
+ This employs two main defenses, a double HMAC with a nonce (if available)
199
+ to blind the timing side channel (to only leak unpredictable information
200
+ to the side channel) and a constant time comparison.
201
+ https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
202
+
203
+ The comparison is designed to run in constant time to
204
+ avoid leaking information through the timing side channel.
205
+ The constant time nature of this algorithm could be undermined by current
206
+ or future MicroPython optimizations which is why it is (by default)
207
+ additionally protected by the double HMAC.
208
+
209
+ It takes as input the output of digest() or hexdigest() of two
210
+ different HMAC objects, or bytes or a bytearray representing a
211
+ precalculated digest.
212
+ """
213
+ if not isinstance (a , (bytes , bytearray )) or not isinstance (b , (bytes , bytearray )):
214
+ raise TypeError ("Expected bytes or bytearray, but got {} and {}" .format (type (a ).__name__ , type (b ).__name__ ))
215
+
216
+ if len (a ) != len (b ):
217
+ raise ValueError ("This method is only for comparing digests of equal length" )
218
+
219
+ if double_hmac :
220
+ try :
221
+ import uos
222
+ nonce = uos .urandom (64 )
223
+ except ImportError :
224
+ double_hmac = False
225
+ except AttributeError :
226
+ double_hmac = False
227
+
228
+ if double_hmac :
229
+ a = new (nonce , a , digestmod ).digest ()
230
+ b = new (nonce , b , digestmod ).digest ()
231
+
232
+ result = 0
233
+ for index , byte_value in enumerate (a ):
234
+ result |= byte_value ^ b [index ]
235
+ return result == 0
236
+
237
+ def test ():
238
+ """Test suite for the HMAC module"""
239
+ run_tests = False
240
+ try :
241
+ from test_hmac import test_sha_vectors , test_sha256_rfc4231 , test_compare_digest
242
+ run_tests = True
243
+ except ImportError :
244
+ raise AssertionError ('test_hmac not found, skipping all tests.' )
245
+
246
+ if run_tests :
247
+ test_sha_vectors ()
248
+ test_sha256_rfc4231 ()
249
+ test_compare_digest ()
0 commit comments