如何在Python中从hashlib.sha256派生?

3 投票
4 回答
5064 浏览
提问于 2025-04-16 06:08

一个简单的尝试失败得很惨:

import hashlib

class fred(hashlib.sha256):
    pass

-> TypeError: Error when calling the metaclass bases
       cannot create 'builtin_function_or_method' instances

结果发现,hashlib.sha256是一个可以调用的东西,而不是一个类。再试一些更有创意的东西也不行:

 import hashlib

 class fred(type(hashlib.sha256())):
     pass

 f = fred

 -> TypeError: cannot create 'fred' instances

嗯……

那我该怎么做呢?

我其实想实现的是这个:

class shad_256(sha256):
    """Double SHA - sha256(sha256(data).digest())
Less susceptible to length extension attacks than sha256 alone."""
    def digest(self):
        return sha256(sha256.digest(self)).digest()
    def hexdigest(self):
        return sha256(sha256.digest(self)).hexdigest()

基本上,我希望所有的请求都能通过,只有在有人请求结果的时候,我想插入一个我自己的额外步骤。有没有什么聪明的方法可以用__new__或者某种元类的魔法来实现这个呢?

我有一个解决方案,我对它大致满意,并且已经作为答案发布了,但我真的很想看看有没有人能想到更好的办法。要么是更简洁,几乎不影响可读性,要么是速度更快(特别是在调用update的时候),同时仍然保持一定的可读性。

更新:我做了一些测试:

# test_sha._timehash takes three parameters, the hash object generator to use,
# the number of updates and the size of the updates.

# Built in hashlib.sha256
$ python2.7 -m timeit -n 100 -s 'import test_sha, hashlib' 'test_sha._timehash(hashlib.sha256, 20000, 512)'
100 loops, best of 3: 104 msec per loop

# My wrapper based approach (see my answer)
$ python2.7 -m timeit -n 100 -s 'import test_sha, hashlib' 'test_sha._timehash(test_sha.wrapper_shad_256, 20000, 512)'
100 loops, best of 3: 108 msec per loop

# Glen Maynard's getattr based approach
$ python2.7 -m timeit -n 100 -s 'import test_sha, hashlib' 'test_sha._timehash(test_sha.getattr_shad_256, 20000, 512)'
100 loops, best of 3: 103 msec per loop

4 个回答

2

这是我根据Glen的回答整理出来的答案,我还为他的回答奖励了积分:

import hashlib

class _double_wrapper(object):
    """This wrapper exists because the various hashes from hashlib are
    factory functions and there is no type that can be derived from.
    So this class simulates deriving from one of these factory
    functions as if it were a class and then implements the 'd'
    version of the hash function which avoids length extension attacks
    by applying H(H(text)) instead of just H(text)."""

    __slots__ = ('_wrappedinstance', '_wrappedfactory', 'update')
    def __init__(self, wrappedfactory, *args):
        self._wrappedfactory = wrappedfactory
        self._assign_instance(wrappedfactory(*args))

    def _assign_instance(self, instance):
        "Assign new wrapped instance and set update method."
        self._wrappedinstance = instance
        self.update = instance.update

    def digest(self):
        "return the current digest value"
        return self._wrappedfactory(self._wrappedinstance.digest()).digest()

    def hexdigest(self):
        "return the current digest as a string of hexadecimal digits"
        return self._wrappedfactory(self._wrappedinstance.digest()).hexdigest()

    def copy(self):
        "return a copy of the current hash object"
        new = self.__class__()
        new._assign_instance(self._wrappedinstance.copy())
        return new

    digest_size = property(lambda self: self._wrappedinstance.digest_size,
                           doc="number of bytes in this hashes output")
    digestsize = digest_size
    block_size = property(lambda self: self._wrappedinstance.block_size,
                          doc="internal block size of hash function")

class shad_256(_double_wrapper):
    """
    Double SHA - sha256(sha256(data))
    Less susceptible to length extension attacks than SHA2_256 alone.

    >>> import binascii
    >>> s = shad_256('hello world')
    >>> s.name
    'shad256'
    >>> int(s.digest_size)
    32
    >>> int(s.block_size)
    64
    >>> s.hexdigest()
    'bc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423'
    >>> binascii.hexlify(s.digest()) == s.hexdigest()
    True
    >>> s2 = s.copy()
    >>> s2.digest() == s.digest()
    True
    >>> s2.update("text")
    >>> s2.digest() == s.digest()
    False
    """
    __slots__ = ()
    def __init__(self, *args):
        super(shad_256, self).__init__(hashlib.sha256, *args)
    name = property(lambda self: 'shad256', doc='algorithm name')

虽然这个答案有点啰嗦,但从文档的角度来看,这个类的表现非常好,而且实现得相对清晰。通过Glen的优化,update函数的速度达到了最快。

不过有一个小问题,就是update函数被当作数据成员显示出来,而且没有文档说明。我觉得这是可接受的,因为在可读性和效率之间做了一个权衡。

7

创建一个新的类,继承自对象,然后在初始化方法中创建一个hashlib.sha256的成员变量。接着,定义一些哈希类应该有的方法,并把这些方法的调用转发给成员变量。

大概是这样的:

import hashlib

class MyThing(object):
    def __init__(self):
        self._hasher = hashlib.sha256()

    def digest(self):
        return self._hasher.digest()

其他的方法也可以这样处理。

6

只需要使用 __getattr__,这样所有你没有自己定义的属性就会自动使用底层对象的属性:

import hashlib

class shad_256(object):
    """
    Double SHA - sha256(sha256(data).digest())
    Less susceptible to length extension attacks than sha256 alone.

    >>> s = shad_256('hello world')
    >>> s.digest_size
    32
    >>> s.block_size
    64
    >>> s.sha256.hexdigest()
    'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
    >>> s.hexdigest()
    'bc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423'
    >>> s.nonexistant()
    Traceback (most recent call last):
    ...
    AttributeError: '_hashlib.HASH' object has no attribute 'nonexistant'
    >>> s2 = s.copy()
    >>> s2.digest() == s.digest()
    True
    >>> s2.update("text")
    >>> s2.digest() == s.digest()
    False
    """
    def __init__(self, data=None):
        self.sha256 = hashlib.sha256()
        if data is not None:
            self.update(data)

    def __getattr__(self, key):
        return getattr(self.sha256, key)

    def _get_final_sha256(self):
        return hashlib.sha256(self.sha256.digest())

    def digest(self):
        return self._get_final_sha256().digest()

    def hexdigest(self):
        return self._get_final_sha256().hexdigest()

    def copy(self):
        result = shad_256()
        result.sha256 = self.sha256.copy()
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

这样大部分情况下可以减少 update 调用的开销,但并不是完全消除。如果你想完全消除这个开销,可以在 __init__ 中加上这个(在 copy 中也要相应添加):

self.update = self.sha256.update

这样在查找 update 时就不会再有额外的 __getattr__ 调用了。

这一切都利用了 Python 成员函数的一个非常有用但常常被忽视的特性:函数绑定。记住,你可以这样做:

a = "hello"
b = a.upper
b()

因为获取一个成员函数的引用并不会返回原始函数,而是将那个函数与它的对象绑定在一起。这就是为什么当上面的 __getattr__ 返回 self.sha256.update 时,返回的函数能够正确地作用于 self.sha256,而不是 self

撰写回答