/ adafruit_hashlib / _sha1.py
_sha1.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2013-2015 AJ Alt
  4  # Modified by Brent Rubell for Adafruit Industries, 2019
  5  #
  6  # Permission is hereby granted, free of charge, to any person obtaining a copy
  7  # of this software and associated documentation files (the "Software"), to deal
  8  # in the Software without restriction, including without limitation the rights
  9  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  # copies of the Software, and to permit persons to whom the Software is
 11  # furnished to do so, subject to the following conditions:
 12  #
 13  # The above copyright notice and this permission notice shall be included in
 14  # all copies or substantial portions of the Software.
 15  #
 16  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  # THE SOFTWARE.
 23  """
 24  `_sha1.py`
 25  ======================================================
 26  SHA1 Hash Algorithm.
 27  
 28  Pure-Python implementation by AJ Alt
 29  https://github.com/ajalt/python-sha1/blob/master/sha1.py
 30  
 31  Modified by Brent Rubell, 2019
 32  
 33  * Author(s): AJ Alt, Brent Rubell
 34  """
 35  import struct
 36  from io import BytesIO
 37  from micropython import const
 38  
 39  # SHA Block size and message digest sizes, in bytes.
 40  SHA_BLOCKSIZE = 64
 41  SHA_DIGESTSIZE = 20
 42  
 43  # initial hash value [FIPS 5.3.1]
 44  K0 = const(0x5A827999)
 45  K1 = const(0x6ED9EBA1)
 46  K2 = const(0x8F1BBCDC)
 47  K3 = const(0xCA62C1D6)
 48  
 49  
 50  def _getbuf(data):
 51      """Converts data into ascii,
 52      returns bytes of data.
 53      :param str bytes bytearray data: Data to convert.
 54  
 55      """
 56      if isinstance(data, str):
 57          return data.encode("ascii")
 58      return bytes(data)
 59  
 60  
 61  def _left_rotate(n, b):
 62      """Left rotate a 32-bit integer, n, by b bits.
 63      :param int n: 32-bit integer
 64      :param int b: Desired rotation amount, in bits.
 65  
 66      """
 67      return ((n << b) | (n >> (32 - b))) & 0xFFFFFFFF
 68  
 69  
 70  # pylint: disable=invalid-name, too-many-arguments
 71  def _hash_computation(chunk, h0, h1, h2, h3, h4):
 72      """Processes 64-bit chunk of data and returns new digest variables.
 73      Per FIPS [6.1.2]
 74      :param bytes bytearray chunk: 64-bit bytearray
 75      :param list h_tuple: List of hash values for the chunk
 76  
 77      """
 78      assert len(chunk) == 64, "Chunk size should be 64-bits"
 79  
 80      w = [0] * 80
 81  
 82      # Break chunk into sixteen 4-byte big-endian words w[i]
 83      for i in range(16):
 84          w[i] = struct.unpack(b">I", chunk[i * 4 : i * 4 + 4])[0]
 85  
 86      # Extend the sixteen 4-byte words into eighty 4-byte words
 87      for i in range(16, 80):
 88          w[i] = _left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)
 89  
 90      # Init. hash values for chunk
 91      a = h0
 92      b = h1
 93      c = h2
 94      d = h3
 95      e = h4
 96  
 97      for i in range(80):
 98          if 0 <= i <= 19:
 99              # Use alternative 1 for f from FIPS PB 180-1 to avoid bitwise not
100              f = d ^ (b & (c ^ d))
101              k = K0
102          elif 20 <= i <= 39:
103              f = b ^ c ^ d
104              k = K1
105          elif 40 <= i <= 59:
106              f = (b & c) | (b & d) | (c & d)
107              k = K2
108          elif 60 <= i <= 79:
109              f = b ^ c ^ d
110              k = K3
111  
112          a, b, c, d, e = (
113              (_left_rotate(a, 5) + f + e + k + w[i]) & 0xFFFFFFFF,
114              a,
115              _left_rotate(b, 30),
116              c,
117              d,
118          )
119  
120      # Add to chunk's hash result so far
121      h0 = (h0 + a) & 0xFFFFFFFF
122      h1 = (h1 + b) & 0xFFFFFFFF
123      h2 = (h2 + c) & 0xFFFFFFFF
124      h3 = (h3 + d) & 0xFFFFFFFF
125      h4 = (h4 + e) & 0xFFFFFFFF
126  
127      return h0, h1, h2, h3, h4
128  
129  
130  # pylint: disable=too-few-public-methods, invalid-name
131  class sha1:
132      """SHA-1 Hash Object
133  
134      """
135  
136      digest_size = SHA_DIGESTSIZE
137      block_size = SHA_BLOCKSIZE
138      name = "sha1"
139  
140      def __init__(self, data=None):
141          """Construct a SHA-1 hash object.
142          :param bytes data: Optional data to process
143  
144          """
145          # Initial Digest Variables
146          self._h = (0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0)
147  
148          # bytes object with 0 <= len < 64 used to store the end of the message
149          # if the message length is not congruent to 64
150          self._unprocessed = b""
151  
152          # Length in bytes of all data that has been processed so far
153          self._msg_byte_len = 0
154  
155          if data:
156              self.update(data)
157  
158      def _create_digest(self):
159          """Returns finalized digest variables for the data processed so far.
160  
161          """
162          # pre-processing
163          message = self._unprocessed
164          message_len = self._msg_byte_len + len(message)
165  
166          # add trailing '1' bit (+ 0's padding) to string [FIPS 5.1.1]
167          message += b"\x80"
168  
169          # append 0 <= k < 512 bits '0', so that the resulting message length (in bytes)
170          # is congruent to 56 (mod 64)
171          message += b"\x00" * ((56 - (message_len + 1) % 64) % 64)
172  
173          # append ml, the original message length, as a 64-bit big-endian integer.
174          message_bit_length = message_len * 8
175          message += struct.pack(b">Q", message_bit_length)
176  
177          # Process the final chunk
178          h = _hash_computation(message[:64], *self._h)
179          if len(message) == 64:
180              return h
181          return _hash_computation(message[64:], *h)
182  
183      def update(self, data):
184          """Updates the hash object with bytes-like object, data.
185          :param bytes data: bytearray or bytes object
186  
187          """
188          # if we get a string, convert to a bytearray objects
189          data = _getbuf(data)
190  
191          # Use BytesIO for stream-like reading
192          if isinstance(data, (bytes, bytearray)):
193              data = BytesIO(data)
194  
195          # Try to build a chunk out of the unprocessed data, if any
196          chunk = self._unprocessed + data.read(64 - len(self._unprocessed))
197  
198          while len(chunk) == 64:
199              self._h = _hash_computation(chunk, *self._h)
200              # increase the length of the message by 64 bytes
201              self._msg_byte_len += 64
202              # read the next 64 bytes
203              chunk = data.read(64)
204  
205          self._unprocessed = chunk
206          return self
207  
208      def digest(self):
209          """Returns the digest of the data passed to the update()
210          method so far.
211  
212          """
213          return b"".join(struct.pack(b">I", h) for h in self._create_digest())
214  
215      def hexdigest(self):
216          """Like digest() except the digest is returned as a string object of
217            double length, containing only hexadecimal digits.
218  
219          """
220          return "".join(["%.2x" % i for i in self.digest()])