Skip to content

Commit a3487f5

Browse files
authored
Merge pull request #17 from timhawes/main
Implement MD5 hash algorithm
2 parents e96b413 + 916cbf9 commit a3487f5

File tree

3 files changed

+343
-8
lines changed

3 files changed

+343
-8
lines changed

LICENSES/RSA-MD.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
2+
rights reserved.
3+
4+
License to copy and use this software is granted provided that it
5+
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
6+
Algorithm" in all material mentioning or referencing this software
7+
or this function.
8+
9+
License is also granted to make and use derivative works provided
10+
that such works are identified as "derived from the RSA Data
11+
Security, Inc. MD5 Message-Digest Algorithm" in all material
12+
mentioning or referencing the derived work.
13+
14+
RSA Data Security, Inc. makes no representations concerning either
15+
the merchantability of this software or the suitability of this
16+
software for any particular purpose. It is provided "as is"
17+
without express or implied warranty of any kind.
18+
19+
These notices must be retained in any copies of any part of this
20+
documentation and/or software.

adafruit_hashlib/_md5.py

Lines changed: 307 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,317 @@
1-
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
1+
# SPDX-FileCopyrightText: 1991-1992 RSA Data Security, Inc
2+
# SPDX-FileCopyrightText: 2021 Tim Hawes
23
#
3-
# SPDX-License-Identifier: MIT
4+
# SPDX-License-Identifier: RSA-MD
5+
#
6+
# Derived from:
7+
#
8+
# MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
9+
#
10+
# Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
11+
# rights reserved.
12+
#
13+
# License to copy and use this software is granted provided that it
14+
# is identified as the "RSA Data Security, Inc. MD5 Message-Digest
15+
# Algorithm" in all material mentioning or referencing this software
16+
# or this function.
17+
#
18+
# License is also granted to make and use derivative works provided
19+
# that such works are identified as "derived from the RSA Data
20+
# Security, Inc. MD5 Message-Digest Algorithm" in all material
21+
# mentioning or referencing the derived work.
22+
#
23+
# RSA Data Security, Inc. makes no representations concerning either
24+
# the merchantability of this software or the suitability of this
25+
# software for any particular purpose. It is provided "as is"
26+
# without express or implied warranty of any kind.
27+
#
28+
# These notices must be retained in any copies of any part of this
29+
# documentation and/or software.
430

531
"""
632
`_md5.py`
733
======================================================
834
MD5 Hash Algorithm.
9-
* Author(s): Brent Rubell
35+
36+
Based on:
37+
https://tools.ietf.org/html/rfc1321
38+
https://gist.github.com/HoLyVieR/11e464a91b290e33b38e
39+
40+
Modified for Python3 and CircuitPython by Tim Hawes.
41+
42+
* Author(s): RSA Data Security, Olivier Arteau, Tim Hawes
1043
"""
11-
# pylint: disable=too-few-public-methods, invalid-name
44+
# pylint: disable=invalid-name,missing-function-docstring,too-many-arguments
45+
46+
import binascii
47+
import struct
48+
from micropython import const
49+
50+
51+
# Constants
52+
53+
54+
S11 = const(7)
55+
S12 = const(12)
56+
S13 = const(17)
57+
S14 = const(22)
58+
S21 = const(5)
59+
S22 = const(9)
60+
S23 = const(14)
61+
S24 = const(20)
62+
S31 = const(4)
63+
S32 = const(11)
64+
S33 = const(16)
65+
S34 = const(23)
66+
S41 = const(6)
67+
S42 = const(10)
68+
S43 = const(15)
69+
S44 = const(21)
70+
PADDING = b"\x80" + (b"\x00" * 63)
71+
72+
73+
# F, G, H and I are basic MD5 functions.
74+
75+
76+
def F(x, y, z):
77+
return (x & y) | ((~x) & z)
78+
79+
80+
def G(x, y, z):
81+
return (x & z) | (y & (~z))
82+
83+
84+
def H(x, y, z):
85+
return x ^ y ^ z
86+
87+
88+
def I(x, y, z):
89+
return y ^ (x | (~z))
90+
91+
92+
# ROTATE_LEFT rotates x left n bits.
93+
94+
95+
def ROTATE_LEFT(x, n):
96+
x = x & 0xFFFFFFFF
97+
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
98+
99+
100+
# FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
101+
# Rotation is separate from addition to prevent recomputation.
102+
103+
104+
def FF(a, b, c, d, x, s, ac):
105+
a = a + F(b, c, d) + x + ac
106+
a = ROTATE_LEFT(a, s)
107+
a = a + b
108+
return a
109+
110+
111+
def GG(a, b, c, d, x, s, ac):
112+
a = a + G(b, c, d) + x + ac
113+
a = ROTATE_LEFT(a, s)
114+
a = a + b
115+
return a
116+
117+
118+
def HH(a, b, c, d, x, s, ac):
119+
a = a + H(b, c, d) + x + ac
120+
a = ROTATE_LEFT(a, s)
121+
a = a + b
122+
return a
123+
124+
125+
def II(a, b, c, d, x, s, ac):
126+
a = a + I(b, c, d) + x + ac
127+
a = ROTATE_LEFT(a, s)
128+
a = a + b
129+
return a
130+
131+
132+
def encode(data, length):
133+
"""Encodes input (UINT4) into output (unsigned char). Assumes length is
134+
a multiple of 4.
135+
"""
136+
k = length >> 2
137+
return struct.pack(*("%iI" % k,) + tuple(data[:k]))
138+
139+
140+
def decode(data, length):
141+
"""Decodes input (unsigned char) into output (UINT4). Assumes length is
142+
a multiple of 4.
143+
"""
144+
k = length >> 2
145+
return struct.unpack("%iI" % k, data[:length])
146+
147+
12148
class md5:
13-
"""RSA MD5 Algorithm class."""
149+
"""Returns a md5 hash object; optionally initialized with a string"""
150+
151+
digest_size = 16
152+
block_size = 64
153+
name = "md5"
154+
155+
def __init__(self, string=b""):
156+
"""Constructs an MD5 hash object."""
157+
self.count = 0
158+
self.buffer = b""
159+
160+
# Load magic initialization constants.
161+
self.state = (0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476)
162+
163+
if string:
164+
self.update(string)
165+
166+
def update(self, data):
167+
"""Update the hash object with the bytes-like object."""
168+
data_len = len(data)
169+
170+
# Compute number of bytes mod 64
171+
index = int(self.count >> 3) & 0x3F
172+
173+
# Update number of bits
174+
self.count = self.count + (data_len << 3)
175+
176+
part_len = md5.block_size - index
177+
178+
# Transform as many times as possible.
179+
if data_len >= part_len:
180+
self.buffer = self.buffer[:index] + data[:part_len]
181+
self._transform(self.buffer)
182+
i = part_len
183+
while i + 63 < data_len:
184+
self._transform(data[i : i + md5.block_size])
185+
i = i + md5.block_size
186+
index = 0
187+
else:
188+
i = 0
189+
190+
# Buffer remaining input
191+
self.buffer = self.buffer[:index] + data[i:data_len]
192+
193+
def digest(self):
194+
"""Return the digest of the data passed to the update() method so far."""
195+
# Save digest state
196+
_buffer, _count, _state = self.buffer, self.count, self.state
197+
198+
# Save number of bits
199+
bits = self.count
200+
201+
# Pad out to 56 mod 64.
202+
index = (self.count >> 3) & 0x3F
203+
if index < 56:
204+
pad_len = 56 - index
205+
else:
206+
pad_len = 120 - index
207+
self.update(PADDING[:pad_len])
208+
209+
# Append length (before padding)
210+
self.update(encode((bits & 0xFFFFFFFF, bits >> 32), 8))
211+
212+
# Save digest output
213+
result = self.state
214+
215+
# Restore digest state
216+
self.buffer, self.count, self.state = _buffer, _count, _state
217+
218+
return encode(result, md5.digest_size)
219+
220+
def hexdigest(self):
221+
"""Like digest() except the digest is returned as a string object of
222+
double length, containing only hexadecimal digits.
223+
"""
224+
return binascii.hexlify(self.digest()).decode("ascii")
225+
226+
def copy(self):
227+
"""Return a copy (“clone”) of the hash object."""
228+
new = md5()
229+
new.count = self.count
230+
new.buffer = self.buffer
231+
new.state = self.state
232+
return new
233+
234+
def _transform(self, block):
235+
"""MD5 basic transformation. Transforms state based on block."""
236+
# pylint: disable=invalid-name,too-many-statements
237+
a, b, c, d = self.state
238+
x = decode(block, md5.block_size)
239+
240+
# Round 1
241+
a = FF(a, b, c, d, x[0], S11, 0xD76AA478)
242+
d = FF(d, a, b, c, x[1], S12, 0xE8C7B756)
243+
c = FF(c, d, a, b, x[2], S13, 0x242070DB)
244+
b = FF(b, c, d, a, x[3], S14, 0xC1BDCEEE)
245+
a = FF(a, b, c, d, x[4], S11, 0xF57C0FAF)
246+
d = FF(d, a, b, c, x[5], S12, 0x4787C62A)
247+
c = FF(c, d, a, b, x[6], S13, 0xA8304613)
248+
b = FF(b, c, d, a, x[7], S14, 0xFD469501)
249+
a = FF(a, b, c, d, x[8], S11, 0x698098D8)
250+
d = FF(d, a, b, c, x[9], S12, 0x8B44F7AF)
251+
c = FF(c, d, a, b, x[10], S13, 0xFFFF5BB1)
252+
b = FF(b, c, d, a, x[11], S14, 0x895CD7BE)
253+
a = FF(a, b, c, d, x[12], S11, 0x6B901122)
254+
d = FF(d, a, b, c, x[13], S12, 0xFD987193)
255+
c = FF(c, d, a, b, x[14], S13, 0xA679438E)
256+
b = FF(b, c, d, a, x[15], S14, 0x49B40821)
257+
258+
# Round 2
259+
a = GG(a, b, c, d, x[1], S21, 0xF61E2562)
260+
d = GG(d, a, b, c, x[6], S22, 0xC040B340)
261+
c = GG(c, d, a, b, x[11], S23, 0x265E5A51)
262+
b = GG(b, c, d, a, x[0], S24, 0xE9B6C7AA)
263+
a = GG(a, b, c, d, x[5], S21, 0xD62F105D)
264+
d = GG(d, a, b, c, x[10], S22, 0x02441453)
265+
c = GG(c, d, a, b, x[15], S23, 0xD8A1E681)
266+
b = GG(b, c, d, a, x[4], S24, 0xE7D3FBC8)
267+
a = GG(a, b, c, d, x[9], S21, 0x21E1CDE6)
268+
d = GG(d, a, b, c, x[14], S22, 0xC33707D6)
269+
c = GG(c, d, a, b, x[3], S23, 0xF4D50D87)
270+
b = GG(b, c, d, a, x[8], S24, 0x455A14ED)
271+
a = GG(a, b, c, d, x[13], S21, 0xA9E3E905)
272+
d = GG(d, a, b, c, x[2], S22, 0xFCEFA3F8)
273+
c = GG(c, d, a, b, x[7], S23, 0x676F02D9)
274+
b = GG(b, c, d, a, x[12], S24, 0x8D2A4C8A)
275+
276+
# Round 3
277+
a = HH(a, b, c, d, x[5], S31, 0xFFFA3942)
278+
d = HH(d, a, b, c, x[8], S32, 0x8771F681)
279+
c = HH(c, d, a, b, x[11], S33, 0x6D9D6122)
280+
b = HH(b, c, d, a, x[14], S34, 0xFDE5380C)
281+
a = HH(a, b, c, d, x[1], S31, 0xA4BEEA44)
282+
d = HH(d, a, b, c, x[4], S32, 0x4BDECFA9)
283+
c = HH(c, d, a, b, x[7], S33, 0xF6BB4B60)
284+
b = HH(b, c, d, a, x[10], S34, 0xBEBFBC70)
285+
a = HH(a, b, c, d, x[13], S31, 0x289B7EC6)
286+
d = HH(d, a, b, c, x[0], S32, 0xEAA127FA)
287+
c = HH(c, d, a, b, x[3], S33, 0xD4EF3085)
288+
b = HH(b, c, d, a, x[6], S34, 0x04881D05)
289+
a = HH(a, b, c, d, x[9], S31, 0xD9D4D039)
290+
d = HH(d, a, b, c, x[12], S32, 0xE6DB99E5)
291+
c = HH(c, d, a, b, x[15], S33, 0x1FA27CF8)
292+
b = HH(b, c, d, a, x[2], S34, 0xC4AC5665)
293+
294+
# Round 4
295+
a = II(a, b, c, d, x[0], S41, 0xF4292244)
296+
d = II(d, a, b, c, x[7], S42, 0x432AFF97)
297+
c = II(c, d, a, b, x[14], S43, 0xAB9423A7)
298+
b = II(b, c, d, a, x[5], S44, 0xFC93A039)
299+
a = II(a, b, c, d, x[12], S41, 0x655B59C3)
300+
d = II(d, a, b, c, x[3], S42, 0x8F0CCC92)
301+
c = II(c, d, a, b, x[10], S43, 0xFFEFF47D)
302+
b = II(b, c, d, a, x[1], S44, 0x85845DD1)
303+
a = II(a, b, c, d, x[8], S41, 0x6FA87E4F)
304+
d = II(d, a, b, c, x[15], S42, 0xFE2CE6E0)
305+
c = II(c, d, a, b, x[6], S43, 0xA3014314)
306+
b = II(b, c, d, a, x[13], S44, 0x4E0811A1)
307+
a = II(a, b, c, d, x[4], S41, 0xF7537E82)
308+
d = II(d, a, b, c, x[11], S42, 0xBD3AF235)
309+
c = II(c, d, a, b, x[2], S43, 0x2AD7D2BB)
310+
b = II(b, c, d, a, x[9], S44, 0xEB86D391)
14311

15-
def __init__(self, s=None):
16-
raise NotImplementedError(
17-
"MD5 digests not currently implemented in this module."
312+
self.state = (
313+
0xFFFFFFFF & (self.state[0] + a),
314+
0xFFFFFFFF & (self.state[1] + b),
315+
0xFFFFFFFF & (self.state[2] + c),
316+
0xFFFFFFFF & (self.state[3] + d),
18317
)

examples/hashlib_simpletest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@
77
# Bytes-to-encode
88
byte_string = b"CircuitPython"
99

10+
# Create an MD5 message
11+
print("--MD5--")
12+
m = hashlib.md5()
13+
# Update the hash object with byte_string
14+
m.update(byte_string)
15+
# Obtain the digest, digest size, and block size
16+
print(
17+
"Msg Digest: {}\nMsg Digest Size: {}\nMsg Block Size: {}".format(
18+
m.hexdigest(), m.digest_size, m.block_size
19+
)
20+
)
21+
# Validate the digest against CPython3 hashlib-md5
22+
assert (
23+
m.hexdigest() == "6a61334a5d9f848bea9affcd82864819"
24+
), "Digest does not match expected string."
25+
1026
# Create a SHA-1 message
1127
print("--SHA1--")
1228
m = hashlib.sha1()

0 commit comments

Comments
 (0)