2010-01-25 5 views
3

Я бы хотел создать экземпляр hashlib, а затем сохранить его состояние в некотором роде. Позже я хотел бы воссоздать объект, используя данные состояния, и продолжить его до update(). Наконец, я хотел бы получить hexdigest() общего совокупного объема данных. Настойчивость государства должна выжить на нескольких трассах.Сохраняющееся состояние хэшлиба

Пример:

import hashlib 
m = hashlib.sha1() 
m.update('one') 
m.update('two') 
# somehow, persist the state of m here 

#later, possibly in another process 
# recreate m from the persisted state 
m.update('three') 
m.update('four') 
print m.hexdigest() 
# at this point, m.hexdigest() should be equal to hashlib.sha1().update('onetwothreefour').hextdigest() 

EDIT:

я не нашел хороший способ сделать это с питоном в 2010 году и в конечном итоге написания небольшой помощник приложения в C для достижения этой цели. Тем не менее, есть несколько отличных ответов ниже, которые были недоступны или известны мне в то время.

+0

Вы можете написать решение где-нибудь? – EsseTi

+0

@EsseTi, прошло много лет, но я помню, что смог захватить состояние SHA_CTX, а затем воссоздать контекст в подобном состоянии позже в другом процессе. – anthony

ответ

2

Вы можете сделать это таким образом, используя ctypes, нет помощника приложения в C необходимо: -

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.ру

#! /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 

Примечание: Я хотел бы поблагодарить PM2Ring за его замечательный код.

+1

Это феноменальный ответ и отличный пример использования ctypes. Спасибо. – anthony

+0

В итоге я написал библиотеку, которая делала что-то очень похожее: https://github.com/kislyuk/rehash – weaver

+0

@anthony Рад, что вам это нравится. ;) FWIW, мой оригинальный ответ [здесь] (http://stackoverflow.com/a/26878137/4014959) –

1

hashlib.sha1 является оберткой вокруг библиотеки C, поэтому вы не сможете ее рассолить.

Он должен был бы осуществить __getstate__ и __setstate__ методы для Python, чтобы получить доступ к его внутреннему состоянию

Вы можете использовать pure Python реализацию sha1, если он достаточно быстр для ваших требований

-1

Вы можете легко построить объект-обертку вокруг хэш-объекта, который может прозрачно сохранять данные.

Очевидным недостатком является то, что для восстановления состояния необходимо сохранить хешированные данные, поэтому в зависимости от размера данных, с которыми вы имеете дело, это может не соответствовать вашим потребностям. Но он должен работать до нескольких десятков МБ.

К сожалению, хэшлиб не раскрывает хеш-алгоритмы как правильные классы, а скорее дает фабричные функции, которые строят хэш-объекты, поэтому мы не можем должным образом подклассифицировать их без загрузки зарезервированных символов - ситуации, которую я бы предпочел избежать. Это означает, что вы должны создать свой класс-оболочку с самого начала, что не так, что накладные расходы от Python в любом случае.

вот пример код, который может даже заполнить ваши потребности:

import hashlib 
from cStringIO import StringIO 

class PersistentSha1(object): 
    def __init__(self, salt=""): 
     self.__setstate__(salt) 

    def update(self, data): 
     self.__data.write(data) 
     self.hash.update(data) 

    def __getattr__(self, attr): 
     return getattr(self.hash, attr) 

    def __setstate__(self, salt=""): 
     self.__data = StringIO() 
     self.__data.write(salt) 
     self.hash = hashlib.sha1(salt) 

    def __getstate__(self): 
     return self.data 

    def _get_data(self): 
     self.__data.seek(0) 
     return self.__data.read() 

    data = property(_get_data, __setstate__) 

Вы можете получить доступ к «данным» члену самому, чтобы получить и установить состояние прямо, или вы можете использовать функции травильного питона:

>>> a = PersistentSha1() 
>>> a 
<__main__.PersistentSha1 object at 0xb7d10f0c> 
>>> a.update("lixo") 
>>> a.data 
'lixo' 
>>> a.hexdigest() 
'6d6332a54574aeb35dcde5cf6a8774f938a65bec' 
>>> import pickle 
>>> b = pickle.dumps(a) 
>>> 
>>> c = pickle.loads(b) 
>>> c.hexdigest() 
'6d6332a54574aeb35dcde5cf6a8774f938a65bec' 

>>> c.data 
'lixo' 
+4

Это хороший пример того, как билдировать класс picklable, но хранить хэшированные данные - это не выход, он может быть огромным. Сам хэш-контекст крошечный, но кажется, что Python не может его разоблачить. – anthony

1

Я тоже столкнулся с этой проблемой и не нашел никакого существующего решения, поэтому я написал книгу, которая делает что-то очень похожее на то, что описал Devesh Saini: 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()