Salt et hachage d'un mot de passe en Python

93

Ce code est censé hacher un mot de passe avec un sel. Le sel et le mot de passe haché sont enregistrés dans la base de données. Le mot de passe lui-même ne l'est pas.

Étant donné le caractère sensible de l'opération, je voulais m'assurer que tout était casher.

import hashlib
import base64
import uuid

password = 'test_password'
salt     = base64.urlsafe_b64encode(uuid.uuid4().bytes)


t_sha = hashlib.sha512()
t_sha.update(password+salt)
hashed_password =  base64.urlsafe_b64encode(t_sha.digest())
Chris Dutrow
la source
Pourquoi encodez-vous le sel en b64? Il serait plus simple d'utiliser le sel directement, puis b64 encoder les deux ensemble t_sha.digest() + salt. Vous pouvez séparer à nouveau le sel plus tard lorsque vous avez décodé le mot de passe de hachage salé, car vous savez que le mot de passe haché décodé est exactement de 32 octets.
Duncan
1
@Duncan - J'ai encodé le sel en base64 pour pouvoir effectuer des opérations puissantes dessus sans avoir à me soucier de problèmes étranges. La version "octets" fonctionnera-t-elle comme une chaîne? Si tel est le cas, je n'ai pas non plus besoin d'encoder en base64 t_sha.digest (). Je ne sauverais probablement pas le mot de passe haché et le sel ensemble simplement parce que cela semble un peu plus compliqué et un peu moins lisible.
Chris Dutrow
Si vous utilisez Python 2.x, l'objet bytes fonctionnera parfaitement comme une chaîne. Python ne met aucune restriction sur ce que vous pouvez avoir dans une chaîne. Cependant, la même chose peut ne pas s'appliquer si vous transmettez la chaîne à un code externe tel qu'une base de données. Python 3.x distingue les types d'octets et les chaînes de sorte que dans ce cas, vous ne voudriez pas utiliser d'opérations de chaîne sur le salt.
Duncan
4
Je ne peux pas vous dire comment le faire en python, mais SHA-512 est un mauvais choix. Utilisez un hachage lent tel que PBKDF2, bcrypt ou scrypt.
CodesInChaos
Note latérale: je déconseille d'utiliser les UUID comme source d'aléa cryptographique. Oui, l'implémentation utilisée par CPython est cryptographiquement sécurisée , mais ce n'est pas dicté par la spécification Python ni par la spécification UUID , et des implémentations vulnérables existent . Si votre base de code est exécutée à l'aide d'une implémentation Python sans UUID4 sécurisés, vous aurez affaibli votre sécurité. C'est peut-être un scénario improbable, mais son utilisation à la secretsplace ne coûte rien .
Mark Amery

Réponses:

49

EDIT: Cette réponse est fausse. Une seule itération de SHA512 est rapide , ce qui le rend inapproprié pour une utilisation en tant que fonction de hachage de mot de passe. Utilisez plutôt l'une des autres réponses ici.


Ça me va. Cependant, je suis sûr que vous n'avez pas besoin de base64. Vous pouvez simplement faire ceci:

import hashlib, uuid
salt = uuid.uuid4().hex
hashed_password = hashlib.sha512(password + salt).hexdigest()

Si cela ne crée pas de difficultés, vous pouvez obtenir un stockage légèrement plus efficace dans votre base de données en stockant le salt et le mot de passe haché sous forme d'octets bruts plutôt que de chaînes hexadécimales. Pour ce faire, remplacez hexpar byteset hexdigestpar digest.

Taymon
la source
1
Oui, hex fonctionnera très bien. Je préfère la base64 car les cordes sont un peu plus courtes. Il est plus efficace de passer et d'effectuer des opérations sur des chaînes plus courtes.
Chris Dutrow
Maintenant, comment l'inverser pour récupérer le mot de passe?
nodebase
28
Vous ne l'inversez pas, vous n'inversez jamais un mot de passe. C'est pourquoi nous le hachons et nous ne le chiffrons pas. Si vous devez comparer un mot de passe d'entrée avec un mot de passe stocké, vous hachez l'entrée et comparez les hachages. Si vous cryptez un mot de passe, toute personne disposant de la clé peut le décrypter et le voir. Ce n'est pas sûr
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex est différent à chaque fois qu'il est généré. Comment allez-vous comparer un mot de passe à des fins de vérification si vous ne pouvez pas récupérer le même uuid?
LittleBobbyTables
3
@LittleBobbyTables, je pense, saltest stocké dans la base de données et le mot de passe haché salé aussi.
clemtoy
70

Sur la base des autres réponses à cette question, j'ai implémenté une nouvelle approche utilisant bcrypt.

Pourquoi utiliser bcrypt

Si je comprends bien, l'argument d'utiliser bcryptplus SHA512que bcryptest conçu pour être lent. bcrypta également une option pour ajuster la vitesse à laquelle vous voulez qu'il soit lors de la génération du mot de passe haché pour la première fois:

# The '12' is the number that dictates the 'slowness'
bcrypt.hashpw(password, bcrypt.gensalt( 12 ))

La lenteur est souhaitable car si une partie malveillante met la main sur la table contenant des mots de passe hachés, il est beaucoup plus difficile de les forcer brutalement.

la mise en oeuvre

def get_hashed_password(plain_text_password):
    # Hash a password for the first time
    #   (Using bcrypt, the salt is saved into the hash itself)
    return bcrypt.hashpw(plain_text_password, bcrypt.gensalt())

def check_password(plain_text_password, hashed_password):
    # Check hashed password. Using bcrypt, the salt is saved into the hash itself
    return bcrypt.checkpw(plain_text_password, hashed_password)

Remarques

J'ai pu installer la bibliothèque assez facilement dans un système Linux en utilisant:

pip install py-bcrypt

Cependant, j'ai eu plus de mal à l'installer sur mes systèmes Windows. Il semble avoir besoin d'un patch. Voir cette question Stack Overflow: installation de py-bcrypt sur win 7 64 bits python

Chris Dutrow
la source
4
12 est la valeur par défaut pour gensalt
Ahmed Hegazy
2
Selon pypi.python.org/pypi/bcrypt/3.1.0 , la longueur maximale du mot de passe pour bcrypt est de 72 octets. Tous les caractères au-delà de cela sont ignorés. Pour cette raison, ils recommandent d'abord le hachage avec une fonction de hachage cryptographique, puis d'encoder en base64 le hachage (voir le lien pour plus de détails). Remarque latérale: il semble que ce py-bcryptsoit l'ancien package pypi et qu'il a depuis été renommé bcrypt.
balu
48

La chose intelligente n'est pas d'écrire la crypto vous-même mais d'utiliser quelque chose comme passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

Il est facile de gâcher l'écriture de votre code cryptographique de manière sécurisée. Ce qui est désagréable, c'est qu'avec un code non crypté, vous le remarquez souvent immédiatement lorsqu'il ne fonctionne pas depuis que votre programme se bloque. Alors qu'avec le code crypto, vous ne le découvrez souvent qu'après qu'il est trop tard et que vos données ont été compromises. Par conséquent, je pense qu'il est préférable d'utiliser un package écrit par quelqu'un d'autre qui connaît le sujet et qui est basé sur des protocoles testés au combat.

Passlib a également quelques fonctionnalités intéressantes qui le rendent facile à utiliser et aussi facile à mettre à niveau vers un protocole de hachage de mot de passe plus récent si un ancien protocole s'avère défectueux.

De plus, un seul cycle de sha512 est plus vulnérable aux attaques par dictionnaire. sha512 est conçu pour être rapide et c'est en fait une mauvaise chose lorsque vous essayez de stocker des mots de passe en toute sécurité. D'autres personnes ont longuement réfléchi à tous ces problèmes, vous feriez mieux d'en profiter.

MARYLAND
la source
5
Je suppose que le conseil d'utilisation des bibliothèques crypo est bon, mais l'OP utilise déjà hashlib, une bibliothèque cryptographique qui se trouve également dans la bibliothèque standard Python (contrairement à passlib). Je continuerais à utiliser hashlib si j'étais dans la situation des OP.
dgh
18
@dghubble hashlibest pour les fonctions de hachage cryptographique. passlibest pour stocker en toute sécurité les mots de passe. Ce n'est pas la même chose (bien que beaucoup de gens semblent le penser ... et ensuite les mots de passe de leurs utilisateurs sont déchiffrés).
Brendan Long
3
Au cas où quelqu'un se demanderait: passlibgénère son propre sel, qui est stocké dans la chaîne de hachage retournée (au moins pour certains schémas tels que BCrypt + SHA256 ) - vous n'avez donc pas à vous en soucier.
z0r
22

Pour que cela fonctionne dans Python 3, vous devrez encoder UTF-8 par exemple:

hashed_password = hashlib.sha512(password.encode('utf-8') + salt.encode('utf-8')).hexdigest()

Sinon, vous obtiendrez:

Traceback (dernier appel en dernier):
Fichier "", ligne 1, dans
hashed_password = hashlib.sha512 (mot de passe + sel) .hexdigest ()
TypeError: les objets Unicode doivent être encodés avant le hachage

Wayne
la source
7
N'utilisez aucune fonction de hachage sha pour hacher les mots de passe. Utilisez quelque chose comme bcrypt. Voir les commentaires sur d'autres questions pour la raison.
josch
11

Depuis Python 3.4, le hashlibmodule de la bibliothèque standard contient des fonctions de dérivation de clé qui sont «conçues pour un hachage de mot de passe sécurisé» .

Utilisez donc l'un de ceux-ci, comme hashlib.pbkdf2_hmac, avec un sel généré en utilisant os.urandom:

from typing import Tuple
import os
import hashlib
import hmac

def hash_new_password(password: str) -> Tuple[bytes, bytes]:
    """
    Hash the provided password with a randomly-generated salt and return the
    salt and hash to store in the database.
    """
    salt = os.urandom(16)
    pw_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    return salt, pw_hash

def is_correct_password(salt: bytes, pw_hash: bytes, password: str) -> bool:
    """
    Given a previously-stored salt and hash, and a password provided by a user
    trying to log in, check whether the password is correct.
    """
    return hmac.compare_digest(
        pw_hash,
        hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    )

# Example usage:
salt, pw_hash = hash_new_password('correct horse battery staple')
assert is_correct_password(salt, pw_hash, 'correct horse battery staple')
assert not is_correct_password(salt, pw_hash, 'Tr0ub4dor&3')
assert not is_correct_password(salt, pw_hash, 'rosebud')

Notez que:

  • L'utilisation d'un sel de 16 octets et 100000 itérations de PBKDF2 correspondent aux nombres minimums recommandés dans la documentation Python. Augmenter davantage le nombre d'itérations rendra vos hachages plus lents à calculer, et donc plus sécurisés.
  • os.urandom utilise toujours une source aléatoire sécurisée par cryptographie
  • hmac.compare_digest, utilisé dans is_correct_password, est fondamentalement juste l' ==opérateur pour les chaînes, mais sans la capacité de court-circuit, ce qui le rend immunisé contre les attaques de synchronisation. Cela ne fournit probablement pas vraiment de valeur de sécurité supplémentaire , mais cela ne fait pas de mal non plus, alors je suis allé de l'avant et je l'ai utilisé.

Pour la théorie sur ce qui fait un bon hachage de mot de passe et une liste d'autres fonctions appropriées pour le hachage des mots de passe avec, voir https://security.stackexchange.com/q/211/29805 .

Mark Amery
la source
10

passlib semble être utile si vous devez utiliser des hachages stockés par un système existant. Si vous avez le contrôle du format, utilisez un hachage moderne comme bcrypt ou scrypt. Pour le moment, bcrypt semble être beaucoup plus facile à utiliser à partir de python.

passlib prend en charge bcrypt et recommande d'installer py-bcrypt en tant que backend: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Vous pouvez également utiliser py-bcrypt directement si vous ne souhaitez pas installer passlib. Le readme contient des exemples d'utilisation de base.

voir aussi: Comment utiliser scrypt pour générer un hachage pour le mot de passe et le sel en Python

Terrel Shumway
la source
0

Importez d'abord: -

import hashlib, uuid

Puis changez votre code en fonction de ceci dans votre méthode:

uname = request.form["uname"]
pwd=request.form["pwd"]
salt = hashlib.md5(pwd.encode())

Ensuite, passez ce salt et uname dans votre requête SQL de base de données, sous login se trouve un nom de table:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
la source
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Ensuite, passez ce salt et uname dans votre requête sql de base de données, sous login se trouve un nom de table : - sql = "insérer dans les valeurs de connexion ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1 car md5 est très rapide, ce qui rend l'utilisation d'une seule itération de md5 un mauvais choix pour une fonction de hachage de mot de passe.
Mark Amery