diff --git a/LICENSE b/LICENSE index 4542ec6..38edcd9 100644 --- a/LICENSE +++ b/LICENSE @@ -21,3 +21,25 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +The MIT License (MIT) + +Copyright (c) 2013-2015 AJ Alt + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/adafruit_hashlib/_sha1.py b/adafruit_hashlib/_sha1.py index 3e4a624..d3bfd7e 100755 --- a/adafruit_hashlib/_sha1.py +++ b/adafruit_hashlib/_sha1.py @@ -1,7 +1,7 @@ - # The MIT License (MIT) # -# Brent Rubell for Adafruit Industries, 2019 +# Copyright (c) 2013-2015 AJ Alt +# Modified by Brent Rubell for Adafruit Industries, 2019 # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,10 +24,188 @@ `_sha1.py` ====================================================== SHA1 Hash Algorithm. -* Author(s): Brent Rubell + +Pure-Python implementation by AJ Alt +https://github.com/ajalt/python-sha1/blob/master/sha1.py + +Modified by Brent Rubell, 2019 + +* Author(s): AJ Alt, Brent Rubell """ +import struct +from io import BytesIO +from micropython import const + +# SHA Block size and message digest sizes, in bytes. +SHA_BLOCKSIZE = 64 +SHA_DIGESTSIZE = 20 + +# initial hash value [FIPS 5.3.1] +K0 = const(0x5A827999) +K1 = const(0x6ED9EBA1) +K2 = const(0x8F1BBCDC) +K3 = const(0xCA62C1D6) + +def _getbuf(data): + """Converts data into ascii, + returns bytes of data. + :param str bytes bytearray data: Data to convert. + + """ + if isinstance(data, str): + return data.encode('ascii') + return bytes(data) + + +def _left_rotate(n, b): + """Left rotate a 32-bit integer, n, by b bits. + :param int n: 32-bit integer + :param int b: Desired rotation amount, in bits. + + """ + return ((n << b) | (n >> (32 - b))) & 0xffffffff + +# pylint: disable=invalid-name, too-many-arguments +def _hash_computation(chunk, h0, h1, h2, h3, h4): + """Processes 64-bit chunk of data and returns new digest variables. + Per FIPS [6.1.2] + :param bytes bytearray chunk: 64-bit bytearray + :param list h_tuple: List of hash values for the chunk + + """ + assert len(chunk) == 64, "Chunk size should be 64-bits" + + w = [0] * 80 + + # Break chunk into sixteen 4-byte big-endian words w[i] + for i in range(16): + w[i] = struct.unpack(b'>I', chunk[i * 4:i * 4 + 4])[0] + + # Extend the sixteen 4-byte words into eighty 4-byte words + for i in range(16, 80): + w[i] = _left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) + + # Init. hash values for chunk + a = h0 + b = h1 + c = h2 + d = h3 + e = h4 + + for i in range(80): + if 0 <= i <= 19: + # Use alternative 1 for f from FIPS PB 180-1 to avoid bitwise not + f = d ^ (b & (c ^ d)) + k = K0 + elif 20 <= i <= 39: + f = b ^ c ^ d + k = K1 + elif 40 <= i <= 59: + f = (b & c) | (b & d) | (c & d) + k = K2 + elif 60 <= i <= 79: + f = b ^ c ^ d + k = K3 + + a, b, c, d, e = ((_left_rotate(a, 5) + f + e + k + w[i]) & 0xffffffff, + a, _left_rotate(b, 30), c, d) + + # Add to chunk's hash result so far + h0 = (h0 + a) & 0xffffffff + h1 = (h1 + b) & 0xffffffff + h2 = (h2 + c) & 0xffffffff + h3 = (h3 + d) & 0xffffffff + h4 = (h4 + e) & 0xffffffff + + return h0, h1, h2, h3, h4 + + # pylint: disable=too-few-public-methods, invalid-name class sha1(): - """SHA1 hash algorithm.""" - def __init__(self, s=None): - raise NotImplementedError("SHA1 digests not currently implemented in this module.") + """SHA-1 Hash Object + + """ + digest_size = SHA_DIGESTSIZE + block_size = SHA_BLOCKSIZE + name = "sha1" + def __init__(self): + """Construct a SHA-1 hash object. + :param bytes data: data to process + """ + # Initial Digest Variables + self._h = (0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0) + + # bytes object with 0 <= len < 64 used to store the end of the message + # if the message length is not congruent to 64 + self._unprocessed = b'' + + # Length in bytes of all data that has been processed so far + self._msg_byte_len = 0 + + def _create_digest(self): + """Returns finalized digest variables for the data processed so far. + + """ + # pre-processing + message = self._unprocessed + message_len = self._msg_byte_len + len(message) + + # add trailing '1' bit (+ 0's padding) to string [FIPS 5.1.1] + message += b'\x80' + + # append 0 <= k < 512 bits '0', so that the resulting message length (in bytes) + # is congruent to 56 (mod 64) + message += b'\x00' * ((56 - (message_len + 1) % 64) % 64) + + # append ml, the original message length, as a 64-bit big-endian integer. + message_bit_length = message_len * 8 + message += struct.pack(b'>Q', message_bit_length) + + # Process the final chunk + h = _hash_computation(message[:64], *self._h) + if len(message) == 64: + return h + return _hash_computation(message[64:], *h) + + def update(self, data): + """Updates the hash object with bytes-like object, data. + :param bytes data: bytearray or bytes object + + """ + # if we get a string, convert to a bytearray objects + data = _getbuf(data) + + # Use BytesIO for stream-like reading + if isinstance(data, (bytes, bytearray)): + data = BytesIO(data) + + # Try to build a chunk out of the unprocessed data, if any + chunk = self._unprocessed + data.read(64 - len(self._unprocessed)) + + while len(chunk) == 64: + self._h = _hash_computation(chunk, *self._h) + # increase the length of the message by 64 bytes + self._msg_byte_len += 64 + # read the next 64 bytes + chunk = data.read(64) + + self._unprocessed = chunk + return self + + def digest(self): + """Returns the digest of the data passed to the update() + method so far. + + """ + return b''.join(struct.pack(b'>I', h) for h in self._create_digest()) + + def hexdigest(self): + """Like digest() except the digest is returned as a string object of + double length, containing only hexadecimal digits. + + """ + return ''.join(['%.2x' % i for i in self.digest()]) diff --git a/examples/hashlib_simpletest.py b/examples/hashlib_simpletest.py old mode 100644 new mode 100755 index ee55755..1c5016b --- a/examples/hashlib_simpletest.py +++ b/examples/hashlib_simpletest.py @@ -4,6 +4,21 @@ # Bytes-to-encode byte_string = b"CircuitPython" +# Create a SHA-1 message +print("--SHA1--") +m = hashlib.sha1() +# Update the hash object with byte_string +m.update(byte_string) +# Obtain the digest, digest size, and block size +print( + "Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format( + m.hexdigest(), m.digest_size, m.block_size)) +# Validate the digest against CPython3 hashlib-sha1 +assert ( + m.hexdigest() == "62c6e222ccd72f21b8ce0c61f42860d6c70954c0" +), "Digest does not match expected string." + + # Create a SHA-224 message print("--SHA224--") m = hashlib.sha224() @@ -13,7 +28,7 @@ print( "Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format( m.hexdigest(), m.digest_size, m.block_size)) -# Validate the digest +# Validate the digest against CPython hashlib-sha224 assert ( m.hexdigest() == "744535a10879be6b18bbcdd135032891346f530a7845d580f7869f36" ), "Digest does not match expected string." @@ -26,7 +41,7 @@ # Obtain the digest, digest size, and block size print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format( m.hexdigest(), m.digest_size, m.block_size)) -# Validate the digest +# Validate the digest against CPython hashlib-sha256 assert ( m.hexdigest() == "3ce8334ca39e66afb9c37d571da4caad68ab4a8bcbd6d584f75e4268e36c0954" ), "Digest does not match expected string." @@ -39,7 +54,7 @@ # Obtain the digest, digest size, and block size print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format( m.hexdigest(), m.digest_size, m.block_size)) -# Validate the digest +# Validate the digest against CPython hashlib-sha384 assert ( m.hexdigest() == "7a12f0815f5511b8ba52c67922d1ae86dfd9bfcc4e0799ad89a9f01fc526c8f074ddb5948c06db9893536f2e65c7621b" ), "Digest does not match expected string." @@ -52,7 +67,7 @@ # Obtain the digest, digest size, and block size print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format( m.hexdigest(), m.digest_size, m.block_size)) -# Validate the digest +# Validate the digest against CPython hashlib-sha512 assert ( m.hexdigest() == "20a88a9b04aa490e457f8980e57331bc85c4d6ca30735a9e502f817e74011a9ece07078e53adf70c232ac91f6c79d4cd6cc69426cd77535645fe9016a71122c2" ), "Digest does not match expected string."