2010-10-27 3 views
3

Une tentative naïve échoue lamentablement:Comment dériver de hashlib.sha256 en Python?

import hashlib 

class fred(hashlib.sha256): 
    pass 

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

Eh bien, il se trouve que hashlib.sha256 est appelable, pas une classe. Essayer quelque chose d'un peu plus créatif ne fonctionne pas non plus:

import hashlib 

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

f = fred 

-> TypeError: cannot create 'fred' instances 

Hmmm ...

Alors, comment puis-je le faire?

Voici ce que je veux réellement atteindre:

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() 

Fondamentalement, je veux que tout passer, sauf quand quelqu'un appelle à un résultat que je veux insérer une étape supplémentaire de mon propre. Y at-il une manière intelligente que je peux accomplir ceci avec __new__ ou la magie de métaclasse de quelque sorte?

J'ai une solution Je suis largement satisfait de ce que j'ai posté comme réponse, mais je suis vraiment intéressé de voir si quelqu'un peut penser à quelque chose de mieux. Soit beaucoup moins verbeux avec un coût très faible en lisibilité ou beaucoup plus rapide (en particulier lorsque vous appelez update) tout en restant quelque peu lisible.

Mise à jour: j'ai couru quelques tests:

# 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 
+0

Qu'était -1 à propos de ma question? – Omnifarious

+0

(p i n g) –

+0

Eh bien, appliquer SHA256 deux fois ne renforcera pas du tout la sécurité. Si vous avez une collision sur sha256 seul, alors les digests sont identiques, donc le second sha256 va générer le même hachage. Si vous voulez de la sécurité, vous devez combiner des hachages, pas les enchaîner. – BatchyX

Répondre

5

Il suffit d'utiliser __getattr__ pour causer tous les attributs que vous ne vous définissez pas retomber sur l'objet sous-jacent:

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() 

Cela permet d'éliminer la plupart du temps les frais généraux pour update appels, mais pas complètement.Si vous voulez éliminer complètement, ajouter à __init__ (et en conséquence dans copy):

self.update = self.sha256.update 

qui éliminera le __getattr__ supplémentaire appel lors de la recherche update.

Tout cela tire parti de l'une des propriétés les plus utiles et souvent négligées des fonctions membres Python: la liaison de fonction. Rappellerez que vous pouvez faire ceci:

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

parce que prendre une référence à une fonction membre ne retourne pas la fonction d'origine, mais une liaison de cette fonction à son objet. C'est pourquoi, lorsque __getattr__ ci-dessus renvoie self.sha256.update, la fonction renvoyée fonctionne correctement sur self.sha256, et non self.

+0

J'ai besoin de tester un peu pour comparer la vitesse avec ce que je fais. – Omnifarious

+0

Avec l'optimisation 'self.update', je trouve que les mises à jour sont exactement aussi rapides que l'utilisation de' sha256' directement - comme prévu, puisqu'elle fait la même chose (une recherche '__dict__' et un appel de fonction natif). –

+0

J'ai ajouté quelques informations de synchronisation. Le vôtre est très légèrement meilleur que le mien. – Omnifarious

7

Faire une nouvelle classe, dérive de l'objet, créez un membre de hashlib.sha256 var dans à init, puis définissez les méthodes attendues d'une classe de hachage et proxy aux mêmes méthodes de la variable membre.

Quelque chose comme:

import hashlib 

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

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

Et ainsi de suite pour les autres méthodes.

+0

Eh bien, cela fonctionnerait. J'espérais faire quelque chose de plus intelligent qui impliquerait moins de dactylographie, parce que, je le sais, je préfèrerais taper tout un tas de texte dans StackOverflow puis un tas de déclarations de méthodes répétitives. :-) – Omnifarious

+1

Vous pouvez substituer __getattr__ ou __getattribute__ et remplacer tous les appels par la variable self._hasher, je suppose que ce serait un peu plus intelligent. –

+1

@Adam Vandenberg, plus intelligent mais il serait en réalité beaucoup plus bavard, car gettattr devrait appliquer conditionnellement les wrappers de fonction. – mikerobi

2

Donc, voici la réponse que je suis venu avec qui est basé sur la réponse de Glen, qui est celui que je lui a remis la prime pour:

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') 

C'est un peu bavard, mais les résultats dans une classe fonctionne très bien d'un point de vue de la documentation et a une mise en œuvre relativement claire. Avec l'optimisation de Glen, update est aussi rapide que possible.

Il y a un inconvénient, à savoir que la fonction update apparaît en tant que membre de données et ne possède pas de docstring. Je pense que c'est un compromis entre lisibilité et efficacité acceptable.

+0

C'est à peu près en écriture seulement, n'est-ce pas? –

+0

@Michael Foukarakis - Oui, mais cela signifie que j'ai très peu de code supplémentaire à écrire pour une autre fonction de hachage. – Omnifarious

+0

@Michael Foukarakis - Je l'ai corrigé pour ne plus avoir la propriété 'write-only'. – Omnifarious

0
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() 

Tous les attributs qui ne figurent pas sur une instance sont liés paresseusement __getattr__. copy() doit être traité spécialement bien sûr.

+0

Ceci est fondamentalement identique à la réponse de Glenn Maynard. – Omnifarious

Questions connexes