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

如何从Python中的hashlib.sha256派生?,python,security,cryptography,Python,Security,Cryptography,一次天真的尝试惨遭失败: 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

一次天真的尝试惨遭失败:

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()
基本上,我希望所有的事情都能通过,除了当有人要求结果时,我想插入我自己的额外步骤。有没有一种聪明的方法可以通过
\uuuuu new\uuuuuu
或某种元类魔法来实现这一点

我有一个解决方案,我很高兴我把它作为一个答案,但我真的很有兴趣看看是否有人能想出更好的办法。要么在可读性方面成本很低,但冗长得多,要么更快(尤其是在调用
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

创建一个新类,从对象派生,在init中创建一个hashlib.sha256成员变量,然后定义一个hash类所需的方法,并将其代理到成员变量的相同方法

比如:

import hashlib

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

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

其他方法也是如此。

因此,我根据格伦的答案得出了一个答案,这是我授予他奖金的原因:

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
函数显示为数据成员,并且没有docstring。我认为这是一个可接受的可读性/效率权衡。

只需使用
\uuuu getattr\uuuu
即可使所有未定义的属性返回到基础对象上:

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
调用的开销,但不是完全消除。如果要完全消除它,请将其添加到
\uuuu init\uuuu
(并相应地在
复制中添加):

这将消除查找
update
时额外的
\uuu getattr\uuuu
调用

这一切都利用了Python成员函数的一个更有用且经常被忽略的属性:函数绑定。回想一下,您可以这样做:

a = "hello"
b = a.upper
b()
因为引用成员函数不会返回原始函数,而是返回该函数与其对象的绑定。这就是为什么当上面的
\uuu getattr\uuuu
返回
self.sha256.update
时,返回的函数在
self.sha256
上正确运行,而不是
self

from hashlib import sha256

class shad_256(object):
    def __init__(self, data=''):
        self._hash = sha256(data)

    def __getattr__(self, attr):
        setattr(self, attr, getattr(self._hash, attr))
        return getattr(self, attr)

    def copy(self):
        ret = shad_256()
        ret._hash = self._hash.copy()
        return ret

    def digest(self):
        return sha256(self._hash.digest()).digest()

    def hexdigest(self):
        return sha256(self._hash.digest()).hexdigest()

在实例上找不到的任何属性都由
\uuu getattr\uuu
延迟绑定
copy()
当然需要特别处理。

好吧,那就行了。我希望做一些更聪明的事情,减少输入,因为,你知道,我宁愿在StackOverflow中输入一大堆文本,然后是一堆重复的方法声明。:-)您可以重写getattr或getattribute,并将所有调用代理到self。\我想这会更聪明。@Adam Vandenberg,更聪明,但实际上会更详细,因为getattr必须有条件地应用函数包装器。我将我最终所做的作为答案发布,但如果你能想出更好的办法,我洗耳恭听。它实际上和你在这里写的很相似。对我来说,“聪明”是可读的,简单的代码。这个答案很聪明+1关于我的问题-1是什么?嗯,两次应用SHA256根本不会增强安全性。如果仅在sha256上发生冲突,则摘要是相同的,因此第二个sha256将生成相同的哈希。如果您想要安全性,您需要组合哈希,而不是链接它们。@BatchyX-您没有描述我要防御的攻击类型。在长度扩展攻击中,攻击者根据哈希值生成内部哈希算法状态,然后将更多数据输入哈希算法并生成新的哈希。这可能使攻击者在某些情况下向消息中添加数据或执行其他令人讨厌的操作。我并不期望新的散列更具抗冲突性。事实上,我有点担心会少一些。但它确实更能抵抗长度扩展。这几乎是只写的,不是吗?@Michael Foukarakis-是的,但这确实意味着我没有多少额外的代码可以为任何其他哈希函数编写。@Michael Foukarakis-我修复了它,因此它不再具有几乎如此强烈的“只写”属性。我需要对此进行一些测试,以将速度与我正在做的进行比较。使用
self.update
优化,我发现更新的速度和直接使用sha256的速度一样快——正如预期的那样,因为它做的是相同的事情(查找和本机函数调用)。你的比我的好一点。我想我会选择你和我的组合(基本上使用你的
更新
优化),因为我喜欢这样一个事实,即在保留准确的文档字符串的同时包装一个随机散列函数非常容易。你的时机“非常好一点”与sha256本身的速度相同,这意味着这是最好的:零开销。除此之外(更重要的是),像这样实现
\uuu getattr\uuuu
是对
sha256
这样没有公开类的对象进行“子类化”的最简单方法。我不知道你在找什么。这和格伦·梅纳德的答案基本相同。
from hashlib import sha256

class shad_256(object):
    def __init__(self, data=''):
        self._hash = sha256(data)

    def __getattr__(self, attr):
        setattr(self, attr, getattr(self._hash, attr))
        return getattr(self, attr)

    def copy(self):
        ret = shad_256()
        ret._hash = self._hash.copy()
        return ret

    def digest(self):
        return sha256(self._hash.digest()).digest()

    def hexdigest(self):
        return sha256(self._hash.digest()).hexdigest()