/ 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()])