【问题标题】:MD5 Python implementation giving unexpected resultsMD5 Python 实现给出了意想不到的结果
【发布时间】: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 个字节是初始位数的小端编码。我现在能想到的就是询问是否有人在我的实施中发现了任何错误。

【问题讨论】:

    标签: python hash md5


    【解决方案1】:

    似乎我犯了一个非常简单但很难发现的错误。我在一些应该使用按位的&amp;| 的地方使用了Python 的逻辑andor

    【讨论】:

      猜你喜欢
      • 2019-05-19
      • 2017-01-05
      • 2021-10-14
      • 1970-01-01
      • 1970-01-01
      • 2020-11-23
      • 2021-11-23
      • 2021-12-17
      • 2021-04-22
      相关资源
      最近更新 更多