/ adafruit_azureiot / hmac.py
hmac.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2020 Jim Bennett 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `HMAC` 24 ================================================================================ 25 26 HMAC (Keyed-Hashing for Message Authentication) Python module. 27 Implements the HMAC algorithm as described by RFC 2104. 28 29 This is here as code instead of using https://github.com/jimbobbennett/CircuitPython_HMAC.git 30 as we only need sha256, so just having the code we need saves 19k of RAM 31 32 """ 33 34 # pylint: disable=C0103, W0108, R0915, C0116, C0115 35 36 37 def __translate(key, translation): 38 return bytes(translation[x] for x in key) 39 40 41 TRANS_5C = bytes((x ^ 0x5C) for x in range(256)) 42 TRANS_36 = bytes((x ^ 0x36) for x in range(256)) 43 44 SHA_BLOCKSIZE = 64 45 SHA_DIGESTSIZE = 32 46 47 48 def new_shaobject(): 49 """Struct. for storing SHA information.""" 50 return { 51 "digest": [0] * 8, 52 "count_lo": 0, 53 "count_hi": 0, 54 "data": [0] * SHA_BLOCKSIZE, 55 "local": 0, 56 "digestsize": 0, 57 } 58 59 60 def sha_init(): 61 """Initialize the SHA digest.""" 62 sha_info = new_shaobject() 63 sha_info["digest"] = [ 64 0x6A09E667, 65 0xBB67AE85, 66 0x3C6EF372, 67 0xA54FF53A, 68 0x510E527F, 69 0x9B05688C, 70 0x1F83D9AB, 71 0x5BE0CD19, 72 ] 73 sha_info["count_lo"] = 0 74 sha_info["count_hi"] = 0 75 sha_info["local"] = 0 76 sha_info["digestsize"] = 32 77 return sha_info 78 79 80 ROR = lambda x, y: (((x & 0xFFFFFFFF) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xFFFFFFFF 81 Ch = lambda x, y, z: (z ^ (x & (y ^ z))) 82 Maj = lambda x, y, z: (((x | y) & z) | (x & y)) 83 S = lambda x, n: ROR(x, n) 84 R = lambda x, n: (x & 0xFFFFFFFF) >> n 85 Sigma0 = lambda x: (S(x, 2) ^ S(x, 13) ^ S(x, 22)) 86 Sigma1 = lambda x: (S(x, 6) ^ S(x, 11) ^ S(x, 25)) 87 Gamma0 = lambda x: (S(x, 7) ^ S(x, 18) ^ R(x, 3)) 88 Gamma1 = lambda x: (S(x, 17) ^ S(x, 19) ^ R(x, 10)) 89 90 91 def sha_transform(sha_info): 92 W = [] 93 94 d = sha_info["data"] 95 for i in range(0, 16): 96 W.append((d[4 * i] << 24) + (d[4 * i + 1] << 16) + (d[4 * i + 2] << 8) + d[4 * i + 3]) 97 98 for i in range(16, 64): 99 W.append((Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFF) 100 101 ss = sha_info["digest"][:] 102 103 # pylint: disable=too-many-arguments, line-too-long 104 def RND(a, b, c, d, e, f, g, h, i, ki): 105 """Compress""" 106 t0 = h + Sigma1(e) + Ch(e, f, g) + ki + W[i] 107 t1 = Sigma0(a) + Maj(a, b, c) 108 d += t0 109 h = t0 + t1 110 return d & 0xFFFFFFFF, h & 0xFFFFFFFF 111 112 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98) 113 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x71374491) 114 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCF) 115 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA5) 116 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25B) 117 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1) 118 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4) 119 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5) 120 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98) 121 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B01) 122 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE) 123 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3) 124 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74) 125 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE) 126 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A7) 127 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174) 128 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C1) 129 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786) 130 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC6) 131 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC) 132 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F) 133 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA) 134 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DC) 135 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA) 136 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152) 137 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D) 138 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C8) 139 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7) 140 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF3) 141 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147) 142 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351) 143 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x14292967) 144 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A85) 145 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B2138) 146 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC) 147 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D13) 148 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A7354) 149 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB) 150 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E) 151 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C85) 152 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A1) 153 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664B) 154 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70) 155 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A3) 156 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819) 157 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD6990624) 158 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E3585) 159 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA070) 160 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116) 161 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C08) 162 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774C) 163 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5) 164 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3) 165 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4A) 166 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F) 167 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3) 168 ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE) 169 ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F) 170 ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814) 171 ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC70208) 172 ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA) 173 ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEB) 174 ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7) 175 ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2) 176 177 # Feedback 178 dig = [] 179 for i, x in enumerate(sha_info["digest"]): 180 dig.append((x + ss[i]) & 0xFFFFFFFF) 181 sha_info["digest"] = dig 182 183 184 def sha_update(sha_info, buffer): 185 """Update the SHA digest. 186 :param dict sha_info: SHA Digest. 187 :param str buffer: SHA buffer size. 188 """ 189 if isinstance(buffer, str): 190 raise TypeError("Unicode strings must be encoded before hashing") 191 count = len(buffer) 192 buffer_idx = 0 193 clo = (sha_info["count_lo"] + (count << 3)) & 0xFFFFFFFF 194 if clo < sha_info["count_lo"]: 195 sha_info["count_hi"] += 1 196 sha_info["count_lo"] = clo 197 198 sha_info["count_hi"] += count >> 29 199 200 if sha_info["local"]: 201 i = SHA_BLOCKSIZE - sha_info["local"] 202 if i > count: 203 i = count 204 205 # copy buffer 206 for x in enumerate(buffer[buffer_idx : buffer_idx + i]): 207 sha_info["data"][sha_info["local"] + x[0]] = x[1] 208 209 count -= i 210 buffer_idx += i 211 212 sha_info["local"] += i 213 if sha_info["local"] == SHA_BLOCKSIZE: 214 sha_transform(sha_info) 215 sha_info["local"] = 0 216 else: 217 return 218 219 while count >= SHA_BLOCKSIZE: 220 # copy buffer 221 sha_info["data"] = list(buffer[buffer_idx : buffer_idx + SHA_BLOCKSIZE]) 222 count -= SHA_BLOCKSIZE 223 buffer_idx += SHA_BLOCKSIZE 224 sha_transform(sha_info) 225 226 # copy buffer 227 pos = sha_info["local"] 228 sha_info["data"][pos : pos + count] = list(buffer[buffer_idx : buffer_idx + count]) 229 sha_info["local"] = count 230 231 232 def getbuf(s): 233 if isinstance(s, str): 234 return s.encode("ascii") 235 return bytes(s) 236 237 238 def sha_final(sha_info): 239 """Finish computing the SHA Digest.""" 240 lo_bit_count = sha_info["count_lo"] 241 hi_bit_count = sha_info["count_hi"] 242 count = (lo_bit_count >> 3) & 0x3F 243 sha_info["data"][count] = 0x80 244 count += 1 245 if count > SHA_BLOCKSIZE - 8: 246 # zero the bytes in data after the count 247 sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) 248 sha_transform(sha_info) 249 # zero bytes in data 250 sha_info["data"] = [0] * SHA_BLOCKSIZE 251 else: 252 sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) 253 254 sha_info["data"][56] = (hi_bit_count >> 24) & 0xFF 255 sha_info["data"][57] = (hi_bit_count >> 16) & 0xFF 256 sha_info["data"][58] = (hi_bit_count >> 8) & 0xFF 257 sha_info["data"][59] = (hi_bit_count >> 0) & 0xFF 258 sha_info["data"][60] = (lo_bit_count >> 24) & 0xFF 259 sha_info["data"][61] = (lo_bit_count >> 16) & 0xFF 260 sha_info["data"][62] = (lo_bit_count >> 8) & 0xFF 261 sha_info["data"][63] = (lo_bit_count >> 0) & 0xFF 262 263 sha_transform(sha_info) 264 265 dig = [] 266 for i in sha_info["digest"]: 267 dig.extend([((i >> 24) & 0xFF), ((i >> 16) & 0xFF), ((i >> 8) & 0xFF), (i & 0xFF)]) 268 return bytes(dig) 269 270 271 # pylint: disable=protected-access 272 class sha256: 273 digest_size = digestsize = SHA_DIGESTSIZE 274 block_size = SHA_BLOCKSIZE 275 name = "sha256" 276 277 def __init__(self, s=None): 278 """Constructs a SHA256 hash object. 279 """ 280 self._sha = sha_init() 281 if s: 282 sha_update(self._sha, getbuf(s)) 283 284 def update(self, s): 285 """Updates the hash object with a bytes-like object, s.""" 286 sha_update(self._sha, getbuf(s)) 287 288 def digest(self): 289 """Returns the digest of the data passed to the update() 290 method so far.""" 291 return sha_final(self._sha.copy())[: self._sha["digestsize"]] 292 293 def hexdigest(self): 294 """Like digest() except the digest is returned as a string object of 295 double length, containing only hexadecimal digits. 296 """ 297 return "".join(["%.2x" % i for i in self.digest()]) 298 299 def copy(self): 300 """Return a copy (“clone”) of the hash object. 301 """ 302 new = sha256() 303 new._sha = self._sha.copy() 304 return new 305 306 307 class HMAC: 308 """RFC 2104 HMAC class. Also complies with RFC 4231. 309 310 This supports the API for Cryptographic Hash Functions (PEP 247). 311 """ 312 313 blocksize = 64 # 512-bit HMAC; can be changed in subclasses. 314 315 def __init__(self, key, msg=None): 316 """Create a new HMAC object. 317 318 key: key for the keyed hash object. 319 msg: Initial input for the hash, if provided. 320 digestmod: A module supporting PEP 247. *OR* 321 A hashlib constructor returning a new hash object. *OR* 322 A hash name suitable for hashlib.new(). 323 Defaults to hashlib.md5. 324 Implicit default to hashlib.md5 is deprecated and will be 325 removed in Python 3.6. 326 327 Note: key and msg must be a bytes or bytearray objects. 328 """ 329 330 if not isinstance(key, (bytes, bytearray)): 331 raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) 332 333 digestmod = sha256 334 335 self.digest_cons = digestmod 336 337 self.outer = self.digest_cons() 338 self.inner = self.digest_cons() 339 self.digest_size = self.inner.digest_size 340 341 if hasattr(self.inner, "block_size"): 342 blocksize = self.inner.block_size 343 if blocksize < 16: 344 blocksize = self.blocksize 345 else: 346 blocksize = self.blocksize 347 348 # self.blocksize is the default blocksize. self.block_size is 349 # effective block size as well as the public API attribute. 350 self.block_size = blocksize 351 352 if len(key) > blocksize: 353 key = self.digest_cons(key).digest() 354 355 key = key + bytes(blocksize - len(key)) 356 self.outer.update(__translate(key, TRANS_5C)) 357 self.inner.update(__translate(key, TRANS_36)) 358 if msg is not None: 359 self.update(msg) 360 361 @property 362 def name(self): 363 """Return the name of this object 364 """ 365 return "hmac-" + self.inner.name 366 367 def update(self, msg): 368 """Update this hashing object with the string msg. 369 """ 370 self.inner.update(msg) 371 372 def copy(self): 373 """Return a separate copy of this hashing object. 374 375 An update to this copy won't affect the original object. 376 """ 377 # Call __new__ directly to avoid the expensive __init__. 378 other = self.__class__.__new__(self.__class__) 379 other.digest_cons = self.digest_cons 380 other.digest_size = self.digest_size 381 other.inner = self.inner.copy() 382 other.outer = self.outer.copy() 383 return other 384 385 def _current(self): 386 """Return a hash object for the current state. 387 388 To be used only internally with digest() and hexdigest(). 389 """ 390 hmac = self.outer.copy() 391 hmac.update(self.inner.digest()) 392 return hmac 393 394 def digest(self): 395 """Return the hash value of this hashing object. 396 397 This returns a string containing 8-bit data. The object is 398 not altered in any way by this function; you can continue 399 updating the object after calling this function. 400 """ 401 hmac = self._current() 402 return hmac.digest() 403 404 def hexdigest(self): 405 """Like digest(), but returns a string of hexadecimal digits instead. 406 """ 407 hmac = self._current() 408 return hmac.hexdigest() 409 410 411 def new_hmac(key, msg=None): 412 """Create a new hashing object and return it. 413 414 key: The starting key for the hash. 415 msg: if available, will immediately be hashed into the object's starting 416 state. 417 418 You can now feed arbitrary strings into the object using its update() 419 method, and can ask for the hash value at any time by calling its digest() 420 method. 421 """ 422 return HMAC(key, msg)