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)