La fonction de hachage en Python 3.3 renvoie des résultats différents entre les sessions

99

J'ai implémenté un BloomFilter en python 3.3 et j'ai obtenu des résultats différents à chaque session. Explorer ce comportement étrange m'a amené à la fonction interne hash () - elle renvoie différentes valeurs de hachage pour la même chaîne à chaque session.

Exemple:

>>> hash("235")
-310569535015251310

----- ouverture d'une nouvelle console python -----

>>> hash("235")
-1900164331622581997

Pourquoi cela arrive-t-il? Pourquoi est-ce utile?

redlus
la source

Réponses:

136

Python utilise une graine de hachage aléatoire pour empêcher les attaquants de piquer votre application en vous envoyant des clés conçues pour entrer en collision. Voir la divulgation de vulnérabilité d'origine . En compensant le hachage avec une graine aléatoire (définie une fois au démarrage), les attaquants ne peuvent plus prédire quelles clés entreront en collision.

Vous pouvez définir une valeur de départ fixe ou désactiver la fonctionnalité en définissant la PYTHONHASHSEEDvariable d'environnement ; la valeur par défaut est, randommais vous pouvez la définir sur une valeur entière positive fixe, en 0désactivant complètement la fonctionnalité.

Les versions 2.7 et 3.2 de Python ont la fonctionnalité désactivée par défaut (utilisez le -Rcommutateur ou défini PYTHONHASHSEED=randompour l'activer); il est activé par défaut dans Python 3.3 et plus.

Si vous vous fiez à l'ordre des clés dans un ensemble Python, alors ne le faites pas. Python utilise une table de hachage pour implémenter ces types et leur ordre dépend de l'historique d'insertion et de suppression ainsi que de la graine de hachage aléatoire. Notez que dans Python 3.5 et versions antérieures, cela s'applique également aux dictionnaires.

Consultez également la object.__hash__()documentation de la méthode spéciale :

Remarque : Par défaut, les __hash__()valeurs des objets str, bytes et datetime sont «salées» avec une valeur aléatoire imprévisible. Bien qu'ils restent constants au sein d'un processus Python individuel, ils ne sont pas prévisibles entre des invocations répétées de Python.

Ceci est destiné à fournir une protection contre un déni de service causé par des entrées soigneusement choisies qui exploitent les pires performances d'une insertion de dict, complexité O (n ^ 2). Voir http://www.ocert.org/advisories/ocert-2011-003.html pour plus de détails.

La modification des valeurs de hachage affecte l'ordre d'itération des dictionnaires, ensembles et autres mappages. Python n'a jamais donné de garanties sur cet ordre (et il varie généralement entre les versions 32 bits et 64 bits).

Voir aussi PYTHONHASHSEED.

Si vous avez besoin d'une implémentation de hachage stable, vous voudrez probablement regarder le hashlibmodule ; cela implémente des fonctions de hachage cryptographiques. Le projet pybloom utilise cette approche .

Étant donné que le décalage se compose d'un préfixe et d'un suffixe (valeur de départ et valeur XOR finale, respectivement), vous ne pouvez malheureusement pas simplement stocker le décalage. Du côté positif, cela signifie que les attaquants ne peuvent pas non plus facilement déterminer le décalage avec les attaques de synchronisation.

Martijn Pieters
la source
9
Je m'attendrais à ce que cela apparaisse dans la documentation de hash () et pas seulement dans __hash __ (). +1 pour une excellente réponse. ps Le hashlib n'est-il pas excessif pour les utilisations non cryptographiques des fonctions de hachage?
redlus
1
pybloom utilise les fonctions hashlib. Mais si vous voulez quelque chose de plus rapide, vous pouvez consulter pyhash .
Håken Lid
3
Pourquoi la documentation l'appelle-t-elle disablelors de sa mise à 0? Je ne vois pas la différence effective en le définissant sur un ancien numéro de graine stable, à moins que quelque chose me manque. Ce que je veux dire, c'est que lorsque j'utilise, PYTHONHASHSEED=12345j'obtiens le même hachage pour des chaînes égales, même entre les sessions - la même chose se produit lorsque j'utilise PYTHONHASHSEED=0- le hachage pour des chaînes égales sera le même d'une session à l'autre (bien que différent de 12345, mais c'est évident, c'est ainsi que les graines travail).
blubberdiblub
@blubberdiblub: avec 0il n'y a pas du tout de graine et les hachages pour les objets sont égaux à ceux générés dans une ancienne version de Python sans aucun support de hachage.
Martijn Pieters
1
@MartijnPieters qu'est-ce que cela signifie pour les hachages affectés de n'avoir "aucune graine du tout"? Quelle est la différence sémantique ou qualitative d'avoir une graine de, disons, 12345, mis à part le fait qu'elle crée deux ensembles distincts de sessions entre lesquels les valeurs de hachage sont différentes et indépendamment du fait que PYTHONHASHSEED = 0 est égal aux anciennes versions? Pouvez-vous me relier à un morceau particulier de code source? Je suppose que ce que je veux dire, c'est que s'il n'y a pas une telle différence, je l'appellerais une graine de 0 et les versions plus anciennes de Python ne supportant qu'une graine de 0. La documentation telle qu'elle se présente actuellement est assez déroutante pour moi.
blubberdiblub
10

La randomisation de hachage est activée par défaut dans Python 3 . Ceci est une fonction de sécurité:

La randomisation de hachage est destinée à fournir une protection contre un déni de service causé par des entrées soigneusement choisies qui exploitent les pires performances d'une construction dict

Dans les versions précédentes de la 2.6.8, vous pouviez l'activer sur la ligne de commande avec -R ou l' option d'environnement PYTHONHASHSEED .

Vous pouvez le désactiver en le mettant PYTHONHASHSEEDà zéro.

Peter Wood
la source
-9

hash () est une fonction intégrée de Python et l'utilise pour calculer une valeur de hachage pour un objet , pas pour une chaîne ou un nombre.

Vous pouvez voir le détail dans cette page: https://docs.python.org/3.3/library/functions.html#hash .

et les valeurs de hash () proviennent de la méthode __hash__ de l'objet. Le doc dit ce qui suit:

Par défaut, les valeurs de hachage () des objets str, bytes et datetime sont «salées» avec une valeur aléatoire imprévisible. Bien qu'ils restent constants au sein d'un processus Python individuel, ils ne sont pas prévisibles entre des invocations répétées de Python.

C'est pourquoi vous avez une valeur de hachage différente pour la même chaîne dans une console différente.

Ce que vous implémentez n'est pas un bon moyen.

Lorsque vous souhaitez calculer une valeur de hachage de chaîne, utilisez simplement hashlib

hash () vise à obtenir une valeur de hachage d'objet, pas une agitation.

Adam Wen
la source
6
hash()est parfaitement valable pour les valeurs de chaîne ou numériques. Vous confondez cela avec la __hash__méthode personnalisée, utilisée parhash() pour fournir une implémentation personnalisée de la valeur de hachage.
Martijn Pieters