【发布时间】:2021-10-23 08:37:04
【问题描述】:
我已经根据 Wikipedia description 编写了以下 MD5 哈希的尝试实现/最小可运行示例:
import numpy as np
import math
# List of shifts taken from Wikipedia
shifts = np.array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
], dtype = np.uint8)
# Procedure for generating these terms, also from Wikipedia
K = np.zeros(64, dtype = np.uint32)
for i in range(64):
K[i] = int(abs(math.sin(i + 1)) * 2 ** 32)
# Starting terms from... Wikipedia
a0 = np.uint32(0x67452301)
b0 = np.uint32(0xefcdab89)
c0 = np.uint32(0x98badcfe)
d0 = np.uint32(0x10325476)
def md5(message):
'''Digest message via MD5.
Message given as bytes like object'''
length = len(message)
# Pad with one 1 bit, then zero bits, then the length LE-order (8 bytes) so that is a multiple of 64 bytes
padded = message + b'\x80' + b'\0' * ((56 - length - 1) % 64) + (length << 3).to_bytes(8, 'little')
a, b, c, d = a0, b0, c0, d0
for chunk_no in range(len(padded) // 64):
a1, b1, c1, d1 = a, b, c, d
chunk = padded[chunk_no * 64 : (chunk_no + 1) * 64]
# Break the chunk up into 16 4-byte LE words
M = np.array([int.from_bytes(chunk[j * 4 : (j + 1) * 4], 'little') for j in range(16)], dtype = np.uint32)
for i in range(64):
quarter = i >> 4
if quarter == 0:
f = (b1 and c1) or ((~b1) and d1)
g = i
elif quarter == 1:
f = (d1 and b1) or ((~d1) and c1)
g = (5 * i + 1) & 15
elif quarter == 2:
f = b1 ^ c1 ^ d1
g = (3 * i + 5) & 15
else:
f = c1 ^ (b1 or (~d1))
g = (7 * i) & 15
f = (f + a1 + K[i] + M[g]) & 0xffffffff
a1, d1, c1, b1 = d1, c1, b1, (b1 + ((f << shifts[i]) | (f >> (32 - shifts[i])))) & 0xffffffff
a = (a + a1) & 0xffffffff
b = (b + b1) & 0xffffffff
c = (c + c1) & 0xffffffff
d = (d + d1) & 0xffffffff
a, b, c, d = int(a), int(b), int(c), int(d) # Allow shifting past the limits of the uint32 type
return (d << 96) | (c << 64) | (b << 32) | a
def test():
data = b''
digest = md5(data)
print(f'MD5({data}) = {digest:032x}')
根据同一篇文章,我作为测试输入传递的空字节字符串应该返回d41d8cd98f00b204e9800998ecf8427e。相反,我得到b6ea9854c6e3520c278e5a1bdeda64da。如果 Wikipedia 文章使用空终止字符串而不是我上面的纯字节表示,我还发现我的实现产生了 md5(b'\0') = e9bd4ae5b18e3f1728405b15138df6ca,这也不是预期值。
到目前为止,我在我的实现中没有看到任何明显的错误,而且由于所有的迭代操作和混乱,我不确定我的错误在哪里。
在所有将字节放在一起形成单词的情况下,或者反之亦然,我都使用 little-endian 编码,这似乎是 rfc1321 的约定。虽然没有包含在上面的代码中,但我至少可以调试到我知道我的K 与维基百科上给出的表格一致,并且我的消息被填充到总长度为 64 字节,最后8 个字节是初始位数的小端编码。我现在能想到的就是询问是否有人在我的实施中发现了任何错误。
【问题讨论】: