Skip to content

Commit c0330a1

Browse files
authored
Merge pull request #6 from brentru/add-sha-1
Add SHA-1 Module
2 parents 2fc6a9b + 37818a2 commit c0330a1

File tree

3 files changed

+225
-10
lines changed

3 files changed

+225
-10
lines changed

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,25 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2121
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
SOFTWARE.
24+
25+
26+
The MIT License (MIT)
27+
28+
Copyright (c) 2013-2015 AJ Alt
29+
30+
Permission is hereby granted, free of charge, to any person obtaining a copy of
31+
this software and associated documentation files (the "Software"), to deal in
32+
the Software without restriction, including without limitation the rights to
33+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
34+
the Software, and to permit persons to whom the Software is furnished to do so,
35+
subject to the following conditions:
36+
37+
The above copyright notice and this permission notice shall be included in all
38+
copies or substantial portions of the Software.
39+
40+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
42+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
43+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
44+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
45+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

adafruit_hashlib/_sha1.py

+184-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
21
# The MIT License (MIT)
32
#
4-
# Brent Rubell for Adafruit Industries, 2019
3+
# Copyright (c) 2013-2015 AJ Alt
4+
# Modified by Brent Rubell for Adafruit Industries, 2019
55
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
@@ -24,10 +24,188 @@
2424
`_sha1.py`
2525
======================================================
2626
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
2834
"""
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+
29123
# pylint: disable=too-few-public-methods, invalid-name
30124
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()])

examples/hashlib_simpletest.py

100644100755
+19-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44
# Bytes-to-encode
55
byte_string = b"CircuitPython"
66

7+
# Create a SHA-1 message
8+
print("--SHA1--")
9+
m = hashlib.sha1()
10+
# Update the hash object with byte_string
11+
m.update(byte_string)
12+
# Obtain the digest, digest size, and block size
13+
print(
14+
"Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
15+
m.hexdigest(), m.digest_size, m.block_size))
16+
# Validate the digest against CPython3 hashlib-sha1
17+
assert (
18+
m.hexdigest() == "62c6e222ccd72f21b8ce0c61f42860d6c70954c0"
19+
), "Digest does not match expected string."
20+
21+
722
# Create a SHA-224 message
823
print("--SHA224--")
924
m = hashlib.sha224()
@@ -13,7 +28,7 @@
1328
print(
1429
"Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
1530
m.hexdigest(), m.digest_size, m.block_size))
16-
# Validate the digest
31+
# Validate the digest against CPython hashlib-sha224
1732
assert (
1833
m.hexdigest() == "744535a10879be6b18bbcdd135032891346f530a7845d580f7869f36"
1934
), "Digest does not match expected string."
@@ -26,7 +41,7 @@
2641
# Obtain the digest, digest size, and block size
2742
print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
2843
m.hexdigest(), m.digest_size, m.block_size))
29-
# Validate the digest
44+
# Validate the digest against CPython hashlib-sha256
3045
assert (
3146
m.hexdigest() == "3ce8334ca39e66afb9c37d571da4caad68ab4a8bcbd6d584f75e4268e36c0954"
3247
), "Digest does not match expected string."
@@ -39,7 +54,7 @@
3954
# Obtain the digest, digest size, and block size
4055
print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
4156
m.hexdigest(), m.digest_size, m.block_size))
42-
# Validate the digest
57+
# Validate the digest against CPython hashlib-sha384
4358
assert (
4459
m.hexdigest() == "7a12f0815f5511b8ba52c67922d1ae86dfd9bfcc4e0799ad89a9f01fc526c8f074ddb5948c06db9893536f2e65c7621b"
4560
), "Digest does not match expected string."
@@ -52,7 +67,7 @@
5267
# Obtain the digest, digest size, and block size
5368
print("Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
5469
m.hexdigest(), m.digest_size, m.block_size))
55-
# Validate the digest
70+
# Validate the digest against CPython hashlib-sha512
5671
assert (
5772
m.hexdigest() == "20a88a9b04aa490e457f8980e57331bc85c4d6ca30735a9e502f817e74011a9ece07078e53adf70c232ac91f6c79d4cd6cc69426cd77535645fe9016a71122c2"
5873
), "Digest does not match expected string."

0 commit comments

Comments
 (0)