|
1 |
| - |
2 | 1 | # The MIT License (MIT)
|
3 | 2 | #
|
4 |
| -# Brent Rubell for Adafruit Industries, 2019 |
| 3 | +# Copyright (c) 2013-2015 AJ Alt |
| 4 | +# Modified by Brent Rubell for Adafruit Industries, 2019 |
5 | 5 | #
|
6 | 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
|
7 | 7 | # of this software and associated documentation files (the "Software"), to deal
|
|
24 | 24 | `_sha1.py`
|
25 | 25 | ======================================================
|
26 | 26 | SHA1 Hash Algorithm.
|
27 |
| -* Author(s): Brent Rubell |
| 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 |
28 | 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 | +def _getbuf(data): |
| 50 | + """Converts data into ascii, |
| 51 | + returns bytes of data. |
| 52 | + :param str bytes bytearray data: Data to convert. |
| 53 | +
|
| 54 | + """ |
| 55 | + if isinstance(data, str): |
| 56 | + return data.encode('ascii') |
| 57 | + return bytes(data) |
| 58 | + |
| 59 | + |
| 60 | +def _left_rotate(n, b): |
| 61 | + """Left rotate a 32-bit integer, n, by b bits. |
| 62 | + :param int n: 32-bit integer |
| 63 | + :param int b: Desired rotation amount, in bits. |
| 64 | +
|
| 65 | + """ |
| 66 | + return ((n << b) | (n >> (32 - b))) & 0xffffffff |
| 67 | + |
| 68 | +# pylint: disable=invalid-name, too-many-arguments |
| 69 | +def _hash_computation(chunk, h0, h1, h2, h3, h4): |
| 70 | + """Processes 64-bit chunk of data and returns new digest variables. |
| 71 | + Per FIPS [6.1.2] |
| 72 | + :param bytes bytearray chunk: 64-bit bytearray |
| 73 | + :param list h_tuple: List of hash values for the chunk |
| 74 | +
|
| 75 | + """ |
| 76 | + assert len(chunk) == 64, "Chunk size should be 64-bits" |
| 77 | + |
| 78 | + w = [0] * 80 |
| 79 | + |
| 80 | + # Break chunk into sixteen 4-byte big-endian words w[i] |
| 81 | + for i in range(16): |
| 82 | + w[i] = struct.unpack(b'>I', chunk[i * 4:i * 4 + 4])[0] |
| 83 | + |
| 84 | + # Extend the sixteen 4-byte words into eighty 4-byte words |
| 85 | + for i in range(16, 80): |
| 86 | + w[i] = _left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) |
| 87 | + |
| 88 | + # Init. hash values for chunk |
| 89 | + a = h0 |
| 90 | + b = h1 |
| 91 | + c = h2 |
| 92 | + d = h3 |
| 93 | + e = h4 |
| 94 | + |
| 95 | + for i in range(80): |
| 96 | + if 0 <= i <= 19: |
| 97 | + # Use alternative 1 for f from FIPS PB 180-1 to avoid bitwise not |
| 98 | + f = d ^ (b & (c ^ d)) |
| 99 | + k = K0 |
| 100 | + elif 20 <= i <= 39: |
| 101 | + f = b ^ c ^ d |
| 102 | + k = K1 |
| 103 | + elif 40 <= i <= 59: |
| 104 | + f = (b & c) | (b & d) | (c & d) |
| 105 | + k = K2 |
| 106 | + elif 60 <= i <= 79: |
| 107 | + f = b ^ c ^ d |
| 108 | + k = K3 |
| 109 | + |
| 110 | + a, b, c, d, e = ((_left_rotate(a, 5) + f + e + k + w[i]) & 0xffffffff, |
| 111 | + a, _left_rotate(b, 30), c, d) |
| 112 | + |
| 113 | + # Add to chunk's hash result so far |
| 114 | + h0 = (h0 + a) & 0xffffffff |
| 115 | + h1 = (h1 + b) & 0xffffffff |
| 116 | + h2 = (h2 + c) & 0xffffffff |
| 117 | + h3 = (h3 + d) & 0xffffffff |
| 118 | + h4 = (h4 + e) & 0xffffffff |
| 119 | + |
| 120 | + return h0, h1, h2, h3, h4 |
| 121 | + |
| 122 | + |
29 | 123 | # pylint: disable=too-few-public-methods, invalid-name
|
30 | 124 | class sha1():
|
31 |
| - """SHA1 hash algorithm.""" |
32 |
| - def __init__(self, s=None): |
33 |
| - raise NotImplementedError("SHA1 digests not currently implemented in this module.") |
| 125 | + """SHA-1 Hash Object |
| 126 | +
|
| 127 | + """ |
| 128 | + digest_size = SHA_DIGESTSIZE |
| 129 | + block_size = SHA_BLOCKSIZE |
| 130 | + name = "sha1" |
| 131 | + def __init__(self): |
| 132 | + """Construct a SHA-1 hash object. |
| 133 | + :param bytes data: data to process |
| 134 | + """ |
| 135 | + # Initial Digest Variables |
| 136 | + self._h = (0x67452301, |
| 137 | + 0xEFCDAB89, |
| 138 | + 0x98BADCFE, |
| 139 | + 0x10325476, |
| 140 | + 0xC3D2E1F0) |
| 141 | + |
| 142 | + # bytes object with 0 <= len < 64 used to store the end of the message |
| 143 | + # if the message length is not congruent to 64 |
| 144 | + self._unprocessed = b'' |
| 145 | + |
| 146 | + # Length in bytes of all data that has been processed so far |
| 147 | + self._msg_byte_len = 0 |
| 148 | + |
| 149 | + def _create_digest(self): |
| 150 | + """Returns finalized digest variables for the data processed so far. |
| 151 | +
|
| 152 | + """ |
| 153 | + # pre-processing |
| 154 | + message = self._unprocessed |
| 155 | + message_len = self._msg_byte_len + len(message) |
| 156 | + |
| 157 | + # add trailing '1' bit (+ 0's padding) to string [FIPS 5.1.1] |
| 158 | + message += b'\x80' |
| 159 | + |
| 160 | + # append 0 <= k < 512 bits '0', so that the resulting message length (in bytes) |
| 161 | + # is congruent to 56 (mod 64) |
| 162 | + message += b'\x00' * ((56 - (message_len + 1) % 64) % 64) |
| 163 | + |
| 164 | + # append ml, the original message length, as a 64-bit big-endian integer. |
| 165 | + message_bit_length = message_len * 8 |
| 166 | + message += struct.pack(b'>Q', message_bit_length) |
| 167 | + |
| 168 | + # Process the final chunk |
| 169 | + h = _hash_computation(message[:64], *self._h) |
| 170 | + if len(message) == 64: |
| 171 | + return h |
| 172 | + return _hash_computation(message[64:], *h) |
| 173 | + |
| 174 | + def update(self, data): |
| 175 | + """Updates the hash object with bytes-like object, data. |
| 176 | + :param bytes data: bytearray or bytes object |
| 177 | +
|
| 178 | + """ |
| 179 | + # if we get a string, convert to a bytearray objects |
| 180 | + data = _getbuf(data) |
| 181 | + |
| 182 | + # Use BytesIO for stream-like reading |
| 183 | + if isinstance(data, (bytes, bytearray)): |
| 184 | + data = BytesIO(data) |
| 185 | + |
| 186 | + # Try to build a chunk out of the unprocessed data, if any |
| 187 | + chunk = self._unprocessed + data.read(64 - len(self._unprocessed)) |
| 188 | + |
| 189 | + while len(chunk) == 64: |
| 190 | + self._h = _hash_computation(chunk, *self._h) |
| 191 | + # increase the length of the message by 64 bytes |
| 192 | + self._msg_byte_len += 64 |
| 193 | + # read the next 64 bytes |
| 194 | + chunk = data.read(64) |
| 195 | + |
| 196 | + self._unprocessed = chunk |
| 197 | + return self |
| 198 | + |
| 199 | + def digest(self): |
| 200 | + """Returns the digest of the data passed to the update() |
| 201 | + method so far. |
| 202 | +
|
| 203 | + """ |
| 204 | + return b''.join(struct.pack(b'>I', h) for h in self._create_digest()) |
| 205 | + |
| 206 | + def hexdigest(self): |
| 207 | + """Like digest() except the digest is returned as a string object of |
| 208 | + double length, containing only hexadecimal digits. |
| 209 | +
|
| 210 | + """ |
| 211 | + return ''.join(['%.2x' % i for i in self.digest()]) |
0 commit comments