动态增长/流数据的哈希算法?

7 投票
3 回答
3962 浏览
提问于 2025-04-16 16:52

有没有什么算法可以让你从一个已知的哈希值继续进行哈希计算?比如说,客户端先把一部分文件上传到ServerA,我可以得到这个上传内容的md5值。然后,客户端再把剩下的文件部分上传到ServerB,这时候我能不能把md5的状态转移到ServerB,然后继续进行哈希计算呢?

我几年前在comp.lang.python上发现过一个基于md5的很酷的黑科技,但它是用ctypes来处理特定版本的md5.so_md5.dll,所以这个代码在不同的Python解释器版本或者其他编程语言中并不太适用。而且,md5模块从Python 2.5开始就被弃用了,所以我需要找一个更通用的解决方案。

更重要的是,哈希的状态能不能存储在十六进制的哈希值里面?(这样我就可以用已有的哈希值继续对一串数据进行哈希,而不是用一些不太干净的内部黑科技。)

3 个回答

2

我也遇到了这个问题,找不到现成的解决办法,于是我写了一个库,利用ctypes来拆解OpenSSL的数据结构,这个结构保存了哈希器的状态。你可以在这里找到它:https://github.com/kislyuk/rehash。下面是一个例子:

import pickle, rehash
hasher = rehash.sha256(b"foo")
state = pickle.dumps(hasher)

hasher2 = pickle.loads(state)
hasher2.update(b"bar")

assert hasher2.hexdigest() == rehash.sha256(b"foobar").hexdigest()
2

从理论上讲,这是可能的(md5 到目前为止 应该包含你继续所需的所有 状态),但看起来普通的API并没有提供你需要的东西。如果你可以接受使用CRC的话,这可能会简单得多,因为CRC在你需要的“流式”情况下更常用。你可以看看这里:

binascii.crc32(data[, crc])

crc32() 函数接受一个可选的 crc 输入,这个输入是你要继续的校验和。

希望这能帮到你。

2

不是从已知的摘要开始,而是从已知的状态开始。你可以使用一个纯Python的MD5实现,并保存它的状态。这里有一个使用PyPy的_md5.py的例子:

import _md5

def md5_getstate(md):
    return (md.A, md.B, md.C, md.D, md.count + [], md.input + [], md.length)

def md5_continue(state):
    md = _md5.new()
    (md.A, md.B, md.C, md.D, md.count, md.input, md.length) = state
    return md

m1 = _md5.new()
m1.update("hello, ")
state = md5_getstate(m1)
m2 = md5_continue(state)
m2.update("world!")
print m2.hexdigest()

m = _md5.new()
m.update("hello, world!")
print m.hexdigest()

正如e.dan提到的,你也可以使用几乎任何校验和算法(比如CRC、Adler、Fletcher),但它们并不能很好地保护你免受故意的数据修改,只能防止随机错误。

补充一下:当然,你也可以使用你提到的线程中的ctypes重新实现序列化方法,以更便携的方式(不使用魔法常量)。我相信这应该是与版本和架构无关的(在Python 2.4-2.7上测试过,包括i386和x86_64):

# based on idea from http://groups.google.com/group/comp.lang.python/msg/b1c5bb87a3ff5e34

try:
    import _md5 as md5
except ImportError:
    # python 2.4
    import md5
import ctypes

def md5_getstate(md):
    if type(md) is not md5.MD5Type:
        raise TypeError, 'not an MD5Type instance'
    return ctypes.string_at(id(md) + object.__basicsize__,
                            md5.MD5Type.__basicsize__ - object.__basicsize__)

def md5_continue(state):
    md = md5.new()
    assert len(state) == md5.MD5Type.__basicsize__ - object.__basicsize__, \
           'invalid state'    
    ctypes.memmove(id(md) + object.__basicsize__,
                   ctypes.c_char_p(state),
                   len(state))
    return md

m1 = md5.new()
m1.update("hello, ")
state = md5_getstate(m1)
m2 = md5_continue(state)
m2.update("world!")
print m2.hexdigest()

m = md5.new()
m.update("hello, world!")
print m.hexdigest()

这不兼容Python 3,因为它没有_md5/md5模块。

不幸的是,hashlib的openssl_md5实现不适合这种黑科技,因为OpenSSL的EVP API没有提供任何可靠序列化EVP_MD_CTX对象的调用或方法。

撰写回答