【问题标题】:Persisting sha256 hash objects?持久化 sha256 哈希对象?
【发布时间】:2015-01-06 10:13:17
【问题描述】:

我需要一个 Python/C/C++/Java 实现,它可以暂停散列进度存储文件中的进度,使得进度可在稍后阶段从该文件中恢复

无论上面列出的用什么语言编写,它都应该在 Python 中正常工作。建议您可以提供它以与“hashlib”一起工作,但这不是必需的。另外,如果这样的东西已经存在,那么一个链接就足够了。

对于一个想法,你的实现应该实现什么。

import hashlib
import hashpersist #THIS IS NEEDED.

sha256 = hashlib.sha256("Hello ")
hashpersist.save_state(sha256, open('test_file', 'w'))

sha256_recovered = hashpersist.load_state(open('test_file', 'r'))
sha256_recovered.update("World")
print sha256_recovered.hexdigest()

这应该给出与我们使用标准 sha256 函数对“Hello World”进行简单哈希处理时相同的输出。

a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

【问题讨论】:

  • Persisting hashlib state的可能重复
  • 正如你毫无疑问地发现你不能腌制 hashlib 的 HASH 对象;有关说明和一些选项,请参阅Persisting hashlib state。但是您可以通过使用更大的块大小(例如 64kB)来加快散列速度。
  • 我链接到的问题中的主要答案解释了为什么您不能从 hashlib 中持久化 Hash 对象。我同意这些答案中提到的选项不能令人满意。如果你真的需要可以持久化的散列对象,你需要编写自己的模块。您是否尝试过我使用更大块大小的建议?在我的实验中,64 KB(65536 字节)效果很好。
  • 但正如已经解释过的,根本不可能“暂停/恢复标准“hashlib”模块中的 md5 和 sha256 哈希对象”。无法通过 Python hashlib 及其哈希对象以任何方式访问使其成为可能所需的状态数据。然而,一个新的、功能等效的 hashlib 实现可以允许这种暂停和恢复,因为使用 OpenSSL 的 C 程序可以访问 struct SHAstate_st aka SHA_CTX .请参阅<openssl/sha.h>(在我的系统上,标头位于/usr/include)。

标签: java python c++ c hash


【解决方案1】:

事实证明,将 hashlib 重写为可恢复的比我想象的要容易,至少是 SHA-256 部分。我花了一些时间使用使用 OpenSSL 加密库的 C 代码,但后来我意识到我不需要所有这些东西,我可以使用 ctypes。

rehash.py

#! /usr/bin/env python

''' A resumable implementation of SHA-256 using ctypes with the OpenSSL crypto library

    Written by PM 2Ring 2014.11.13
'''

from ctypes import *

SHA_LBLOCK = 16
SHA256_DIGEST_LENGTH = 32

class SHA256_CTX(Structure):
    _fields_ = [
        ("h", c_long * 8),
        ("Nl", c_long),
        ("Nh", c_long),
        ("data", c_long * SHA_LBLOCK),
        ("num", c_uint),
        ("md_len", c_uint)
    ]

HashBuffType = c_ubyte * SHA256_DIGEST_LENGTH

#crypto = cdll.LoadLibrary("libcrypto.so")
crypto = cdll.LoadLibrary("libeay32.dll" if os.name == "nt" else "libssl.so")

class sha256(object):
    digest_size = SHA256_DIGEST_LENGTH

    def __init__(self, datastr=None):
        self.ctx = SHA256_CTX()
        crypto.SHA256_Init(byref(self.ctx))
        if datastr:
            self.update(datastr)

    def update(self, datastr):
        crypto.SHA256_Update(byref(self.ctx), datastr, c_int(len(datastr)))

    #Clone the current context
    def _copy_ctx(self):
        ctx = SHA256_CTX()
        pointer(ctx)[0] = self.ctx
        return ctx

    def copy(self):
        other = sha256()
        other.ctx = self._copy_ctx()
        return other

    def digest(self):
        #Preserve context in case we get called before hashing is
        # really finished, since SHA256_Final() clears the SHA256_CTX
        ctx = self._copy_ctx()
        hashbuff = HashBuffType()
        crypto.SHA256_Final(hashbuff, byref(self.ctx))
        self.ctx = ctx
        return str(bytearray(hashbuff))

    def hexdigest(self):
        return self.digest().encode('hex')

#Tests
def main():
    import cPickle
    import hashlib

    data = ("Nobody expects ", "the spammish ", "imposition!")

    print "rehash\n"

    shaA = sha256(''.join(data))
    print shaA.hexdigest()
    print repr(shaA.digest())
    print "digest size =", shaA.digest_size
    print

    shaB = sha256()
    shaB.update(data[0])
    print shaB.hexdigest()

    #Test pickling
    sha_pickle = cPickle.dumps(shaB, -1)
    print "Pickle length:", len(sha_pickle)
    shaC = cPickle.loads(sha_pickle)

    shaC.update(data[1])
    print shaC.hexdigest()

    #Test copying. Note that copy can be pickled
    shaD = shaC.copy()

    shaC.update(data[2])
    print shaC.hexdigest()


    #Verify against hashlib.sha256()
    print "\nhashlib\n"

    shaD = hashlib.sha256(''.join(data))
    print shaD.hexdigest()
    print repr(shaD.digest())
    print "digest size =", shaD.digest_size
    print

    shaE = hashlib.sha256(data[0])
    print shaE.hexdigest()

    shaE.update(data[1])
    print shaE.hexdigest()

    #Test copying. Note that hashlib copy can NOT be pickled
    shaF = shaE.copy()
    shaF.update(data[2])
    print shaF.hexdigest()


if __name__ == '__main__':
    main()

resumable_SHA-256.py

#! /usr/bin/env python

''' Resumable SHA-256 hash for large files using the OpenSSL crypto library

    The hashing process may be interrupted by Control-C (SIGINT) or SIGTERM.
    When a signal is received, hashing continues until the end of the
    current chunk, then the current file position, total file size, and
    the sha object is saved to a file. The name of this file is formed by
    appending '.hash' to the name of the file being hashed.

    Just re-run the program to resume hashing. The '.hash' file will be deleted
    once hashing is completed.

    Written by PM 2Ring 2014.11.14
'''

import cPickle as pickle
import os
import signal
import sys

import rehash

quit = False

blocksize = 1<<16   # 64kB
blocksperchunk = 1<<8

chunksize = blocksize * blocksperchunk

def handler(signum, frame):
    global quit
    print "\nGot signal %d, cleaning up." % signum
    quit = True


def do_hash(fname, filesize):
    hashname = fname + '.hash'
    if os.path.exists(hashname):
        with open(hashname, 'rb') as f:
            pos, fsize, sha = pickle.load(f)
        if fsize != filesize:
            print "Error: file size of '%s' doesn't match size recorded in '%s'" % (fname, hashname)
            print "%d != %d. Aborting" % (fsize, filesize)
            exit(1)
    else:
        pos, fsize, sha = 0, filesize, rehash.sha256()

    finished = False
    with open(fname, 'rb') as f:
        f.seek(pos)
        while not (quit or finished):
            for _ in xrange(blocksperchunk):
                block = f.read(blocksize)
                if block == '':
                    finished = True
                    break
                sha.update(block)

            pos += chunksize
            sys.stderr.write(" %6.2f%% of %d\r" % (100.0 * pos / fsize, fsize))
            if finished or quit:
                break

    if quit:
        with open(hashname, 'wb') as f:
            pickle.dump((pos, fsize, sha), f, -1)
    elif os.path.exists(hashname):
        os.remove(hashname)

    return (not quit), pos, sha.hexdigest()


def main():
    if len(sys.argv) != 2:
        print "Resumable SHA-256 hash of a file."
        print "Usage:\npython %s filename\n" % sys.argv[0]
        exit(1)

    fname = sys.argv[1]
    filesize = os.path.getsize(fname)

    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)

    finished, pos, hexdigest = do_hash(fname, filesize)
    if finished:
        print "%s  %s" % (hexdigest, fname)
    else:
        print "sha-256 hash of '%s' incomplete" % fname
        print "%s" % hexdigest
        print "%d / %d bytes processed." % (pos, filesize)


if __name__ == '__main__':
    main()

演示

import rehash
import pickle
sha=rehash.sha256("Hello ")
s=pickle.dumps(sha.ctx)
sha=rehash.sha256()
sha.ctx=pickle.loads(s)
sha.update("World")
print sha.hexdigest()

输出

a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

编辑

我刚刚做了一个小修改,以允许rehash 也可以在 Windows 上工作,尽管我只在 WinXP 上测试过它。 libeay32.dll 可以在当前目录中,也可以在系统库搜索路径中的某个位置,例如WINDOWS\system32。我相当古老(而且大部分未使用)的 XP 安装找不到 .dll,即使它已被 OpenOffice 和 Avira 使用。所以我只是将它从 Avira 文件夹复制到 system32。现在它完美地工作了。 :)

【讨论】:

  • 它给出了,OSError: libcrypto.so: cannot open shared object file...你能写/更新如何让它工作的指令吗?
  • 也许你没有那个库文件,但如果你在 Linux 上并且安装了 OpenSSL,你应该有。但是尝试将库更改为“libssl.so”,即将crypto = cdll.LoadLibrary("libcrypto.so") 更改为crypto = cdll.LoadLibrary("libssl.so")。如果您不使用 Linux 或其他形式的 Unix,则可能需要使用稍微不同的语法。
  • 我对 Windows 不是很熟悉,但是看起来 Windows 上的 OpenSSL 加密库名称是 ssleay32.dlllibssl.so(主要的 OpenSSL 库)等价于 libeay32.dll
  • 我尝试将名称更改为libssl.so,但没有效果。它继续引发相同的异常。我还尝试通过执行apt-get install openssl 来安装openssl,并尝试了这两个名称,即libcrypto.solibssl.so。同样的问题!
  • 太棒了!谢谢。 :大笑:。稍作改动后,它也可以在 Windows 上运行,虽然我只在 WinXP 上测试过。
猜你喜欢
  • 1970-01-01
  • 2017-04-06
  • 1970-01-01
  • 1970-01-01
  • 2011-06-19
  • 2014-01-14
  • 1970-01-01
  • 2017-06-21
  • 1970-01-01
相关资源
最近更新 更多