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

7

是否有任何算法可以从已知的哈希摘要继续进行哈希?例如,客户端将文件的一部分上传到ServerA,我可以得到已上传内容的md5总和,然后客户端将文件的其余部分上传到ServerB,我是否可以将md5内部状态传输到ServerB并完成哈希?

多年前,我在comp.lang.python上找到了一种基于md5的酷炫的黑魔法, 但它使用ctypes针对特定版本的md5.so_md5.dll,因此它不是适用于不同Python解释器版本或其他编程语言的可移植代码。此外,自2.5以来,md5模块已在Python标准库中弃用,因此我需要找到更通用的解决方案。

而且,哈希的状态是否可以存储在十六进制摘要本身中?(这样我就可以使用现有的哈希摘要继续哈希数据流,而不是使用不规范的内部方法。)

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

不是从已知的摘要中获取,而是从已知的状态中获取。你可以使用一个纯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()

由于没有_md5/md5模块,它与Python 3不兼容。

不幸的是,hashlib的openssl_md5实现对于这种黑客攻击并不适用,因为OpenSSL EVP API没有提供任何调用/方法来可靠地序列化EVP_MD_CTX对象。


Pypy的纯Python MD5实现很酷。但是标准CPython附带的openssl_md5怎么样? - est
@est,对于openssl_md5,你无法可靠地实现它,因为openssl本身没有提供EVP_MD_CTX序列化的API,所以任何实现都将依赖于openssl版本。但是你仍然可以通过CPython的_md5模块进行一些修改。我会在我的答案中添加这个方法。 - abbot
太棒了,伙计!但是为什么我们不能使用 cyptes 直接调用 libopenssl.so 并获取 EVP_MD_CTX 呢? - est
libssl.so无法帮助,因为它并没有提供EVP_MD_CTX序列化的API,只能进行内存复制。因此,您无法使用文档API将EVP_MD_CTX“存储”到某个字节数组中以便在其他地方“恢复”它,您只能在运行进程中复制它。对于复制,您可以使用Python hashlib API中提供的copy()方法。当然,您可以黑客攻击libssl.so并手动序列化EVP_MD_CTX,但这将取决于openssl版本。 - abbot
1
谢谢。看起来确实需要一些技巧。http://stackoverflow.com/questions/5880456/expose-hashlib-pyd-internals-for-evp-md-ctx - est

2
这在理论上是可能的(到目前为止,md5应该包含你需要继续的所有状态),但看起来常规API并没有提供你所需的内容。如果你可以用CRC代替,这可能会更容易,因为在像你需要的“流式”情况下,CRC更常用。请参见此处:binascii.crc32(data[, crc])crc32()接受一个可选的crc输入,它是要从哪个校验和继续的。希望这能帮到你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接