Salt und Hash ein Passwort in Python

92

Dieser Code soll ein Passwort mit einem Salt hashen. Das Salt- und das Hash-Passwort werden in der Datenbank gespeichert. Das Passwort selbst ist nicht.

Angesichts der Sensibilität der Operation wollte ich sicherstellen, dass alles koscher ist.

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
quelle
Warum kodierst du b64 das Salz? Es wäre einfacher, das Salz direkt zu verwenden und dann beide zusammen mit b64 zu codieren t_sha.digest() + salt. Sie können das Salt später wieder aufteilen, wenn Sie das Salted-Hash-Passwort dekodiert haben, da Sie wissen, dass das decodierte Hash-Passwort genau 32 Byte beträgt.
Duncan
1
@ Duncan - Ich habe das Salz mit base64 codiert, damit ich starke Operationen daran durchführen kann, ohne mir Gedanken über seltsame Probleme machen zu müssen. Funktioniert die "Bytes" -Version als Zeichenfolge? Wenn dies der Fall ist, muss ich t_sha.digest () auch nicht mit base64 codieren. Ich würde das Hash-Passwort und das Salt wahrscheinlich nicht zusammen speichern, nur weil das etwas komplizierter und etwas weniger lesbar erscheint.
Chris Dutrow
Wenn Sie Python 2.x verwenden, funktioniert das Byte-Objekt perfekt als Zeichenfolge. Python legt keine Einschränkungen für das fest, was Sie in einem String haben können. Dies gilt jedoch möglicherweise nicht, wenn Sie die Zeichenfolge an einen externen Code wie eine Datenbank übergeben. Python 3.x unterscheidet Bytetypen und Zeichenfolgen. In diesem Fall möchten Sie keine Zeichenfolgenoperationen für das Salt verwenden.
Duncan
4
Ich kann Ihnen nicht sagen, wie es in Python geht, aber einfach SHA-512 ist eine schlechte Wahl. Verwenden Sie einen langsamen Hash wie PBKDF2, bcrypt oder scrypt.
CodesInChaos
Randnotiz: Ich würde davon abraten, UUIDs als Quelle für kryptografische Zufälligkeit zu verwenden. Ja, die von CPython verwendete Implementierung ist kryptografisch sicher , aber dies wird weder von Pythons Spezifikation noch von der UUID-Spezifikation vorgegeben , und es gibt anfällige Implementierungen . Wenn Ihre Codebasis mit einer Python-Implementierung ohne sichere UUID4s ausgeführt wird, haben Sie Ihre Sicherheit geschwächt. Das mag ein unwahrscheinliches Szenario sein, aber es kostet nichts, es secretsstattdessen zu verwenden .
Mark Amery

Antworten:

49

EDIT: Diese Antwort ist falsch. Eine einzelne Iteration von SHA512 ist schnell , was es für die Verwendung als Passwort-Hashing-Funktion ungeeignet macht. Verwenden Sie stattdessen eine der anderen Antworten.


Sieht für mich gut aus. Ich bin mir jedoch ziemlich sicher, dass Sie base64 nicht benötigen. Sie könnten dies einfach tun:

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

Wenn dies keine Schwierigkeiten verursacht, können Sie die Speicherung in Ihrer Datenbank etwas effizienter gestalten, indem Sie das Salt- und Hash-Kennwort als Rohbytes und nicht als Hex-Zeichenfolgen speichern. Ersetzen Sie dazu hexdurch bytesund hexdigestmit digest.

Taymon
quelle
1
Ja, Hex würde gut funktionieren. Ich bevorzuge base64, weil die Saiten etwas kürzer sind. Es ist effizienter, kürzere Saiten weiterzugeben und zu bearbeiten.
Chris Dutrow
Wie können Sie es nun umkehren, um das Passwort zurückzubekommen?
Nodebase
28
Sie kehren es nicht um, Sie kehren niemals ein Passwort um. Deshalb haben wir es hash und wir verschlüsseln es nicht. Wenn Sie ein Eingabekennwort mit einem gespeicherten Kennwort vergleichen müssen, hashen Sie die Eingabe und vergleichen die Hashes. Wenn Sie ein Passwort verschlüsseln, kann jeder mit dem Schlüssel es entschlüsseln und sehen. Es ist nicht sicher
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex ist bei jeder Generierung anders. Wie vergleichen Sie ein Passwort zu Überprüfungszwecken, wenn Sie nicht dieselbe UUID zurückerhalten können?
LittleBobbyTables
3
@LittleBobbyTables Ich denke, es saltist in der Datenbank gespeichert und das salzige Hash-Passwort auch.
Clemtoy
70

Basierend auf den anderen Antworten auf diese Frage habe ich einen neuen Ansatz mit bcrypt implementiert.

Warum bcrypt verwenden?

Wenn ich das richtig verstehe, ist das Argument, das verwendet bcryptwerden muss, SHA512dass bcryptes langsam ausgelegt ist. bcryptAußerdem können Sie anpassen, wie langsam es beim ersten Generieren des Hash-Passworts sein soll:

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

Langsam ist wünschenswert, denn wenn eine böswillige Partei den Tisch mit den gehashten Passwörtern in die Hände bekommt, ist es viel schwieriger, sie brutal zu erzwingen.

Implementierung

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)

Anmerkungen

Ich konnte die Bibliothek ziemlich einfach in einem Linux-System installieren, indem ich:

pip install py-bcrypt

Ich hatte jedoch mehr Probleme bei der Installation auf meinen Windows-Systemen. Es scheint einen Patch zu brauchen. Siehe diese Frage zum Stapelüberlauf : py-bcrypt wird unter Win 7 64-Bit-Python installiert

Chris Dutrow
quelle
4
12 ist der Standardwert für Gensalt
Ahmed Hegazy
2
Laut pypi.python.org/pypi/bcrypt/3.1.0 beträgt die maximale Kennwortlänge für bcrypt 72 Byte. Alle darüber hinausgehenden Zeichen werden ignoriert. Aus diesem Grund empfehlen sie, zuerst mit einer kryptografischen Hash-Funktion zu hashen und dann den Hash mit base64 zu codieren (siehe Link für Details). Nebenbemerkung: Es scheint py-bcryptdas alte Pypi-Paket zu sein und wurde seitdem in umbenannt bcrypt.
Balu
47

Das Schlaue ist nicht, die Krypto selbst zu schreiben, sondern etwas wie passlib zu verwenden: https://bitbucket.org/ecollins/passlib/wiki/Home

Es ist leicht, das Schreiben Ihres Kryptocodes auf sichere Weise durcheinander zu bringen. Das Schlimme ist, dass Sie es bei Nicht-Krypto-Code oft sofort bemerken, wenn es nicht funktioniert, da Ihr Programm abstürzt. Während Sie mit Kryptocode arbeiten, erfahren Sie dies oft erst, wenn es zu spät ist und Ihre Daten kompromittiert wurden. Daher denke ich, dass es besser ist, ein Paket zu verwenden, das von jemand anderem geschrieben wurde, der sich mit dem Thema auskennt und auf kampferprobten Protokollen basiert.

Passlib hat auch einige nette Funktionen, die die Verwendung vereinfachen und das Upgrade auf ein neueres Passwort-Hashing-Protokoll erleichtern, wenn sich herausstellt, dass ein altes Protokoll fehlerhaft ist.

Auch nur eine einzige Runde von sha512 ist anfälliger für Wörterbuchangriffe. sha512 ist so konzipiert, dass es schnell ist, und dies ist eine schlechte Sache, wenn Sie versuchen, Passwörter sicher zu speichern. Andere Leute haben lange und gründlich über all diese Probleme nachgedacht, also nutzen Sie dies besser aus.

MD
quelle
5
Ich nehme an, der Rat zur Verwendung von Crypo-Bibliotheken ist gut, aber das OP verwendet bereits Hashlib, eine Kryptobibliothek, die sich ebenfalls in der Python-Standardbibliothek befindet (im Gegensatz zu Passlib). Ich würde weiterhin Hashlib verwenden, wenn ich in der OP-Situation wäre.
dgh
18
@dghubble hashlibist für kryptografische Hash-Funktionen. passlibdient zum sicheren Speichern von Passwörtern. Sie sind nicht dasselbe (obwohl viele Leute so zu denken scheinen ... und dann werden die Passwörter ihrer Benutzer geknackt).
Brendan Long
3
Falls sich jemand wundert: passlibErzeugt sein eigenes Salt, das in der zurückgegebenen Hash-Zeichenfolge gespeichert ist (zumindest für bestimmte Schemata wie BCrypt + SHA256 ) - Sie müssen sich also keine Sorgen machen.
z0r
22

Damit dies in Python 3 funktioniert, müssen Sie beispielsweise UTF-8 codieren:

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

Andernfalls erhalten Sie:

Traceback (letzter Aufruf zuletzt):
Datei "", Zeile 1, in
hashed_password = hashlib.sha512 (Passwort + Salt) .hexdigest ()
TypeError: Unicode-Objekte müssen vor dem Hashing codiert werden

Wayne
quelle
7
Nein. Verwenden Sie keine sha-Hash-Funktion zum Hashing von Passwörtern. Verwenden Sie so etwas wie bcrypt. Siehe die Kommentare zu anderen Fragen für den Grund.
Josch
11

Ab Python 3.4 hashlibenthält das Modul in der Standardbibliothek Funktionen zur Schlüsselableitung , die "für sicheres Passwort-Hashing ausgelegt sind" .

Verwenden Sie also eines davon, z. B. hashlib.pbkdf2_hmacmit einem Salz, das erzeugt wird aus 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')

Beachten Sie, dass:

  • Die Verwendung eines 16-Byte-Salt und 100000 Iterationen von PBKDF2 entsprechen den in den Python-Dokumenten empfohlenen Mindestzahlen. Wenn Sie die Anzahl der Iterationen weiter erhöhen, werden Ihre Hashes langsamer berechnet und sind daher sicherer.
  • os.urandom verwendet immer eine kryptografisch sichere Zufallsquelle
  • hmac.compare_digest, verwendet in is_correct_password, ist im Grunde nur der ==Operator für Strings, aber ohne die Fähigkeit zum Kurzschließen, was es immun gegen Timing-Angriffe macht. Das bietet wahrscheinlich keinen zusätzlichen Sicherheitswert , aber es tut auch nicht weh, also habe ich es verwendet.

Eine Theorie darüber, was einen guten Passwort-Hash ausmacht, und eine Liste anderer Funktionen, die zum Hashing von Passwörtern geeignet sind, finden Sie unter https://security.stackexchange.com/q/211/29805 .

Mark Amery
quelle
10

passlib scheint nützlich zu sein, wenn Sie Hashes verwenden müssen, die von einem vorhandenen System gespeichert wurden. Wenn Sie die Kontrolle über das Format haben, verwenden Sie einen modernen Hash wie bcrypt oder scrypt. Zu diesem Zeitpunkt scheint bcrypt von Python aus viel einfacher zu verwenden zu sein.

passlib unterstützt bcrypt und empfiehlt die Installation von py-bcrypt als Backend: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Sie können py-bcrypt auch direkt verwenden, wenn Sie passlib nicht installieren möchten. Die Readme-Datei enthält Beispiele für die grundlegende Verwendung.

Siehe auch: Verwendung von scrypt zum Generieren von Hash für Kennwort und Salt in Python

Terrel Shumway
quelle
0

Erstens importieren: -

import hashlib, uuid

Ändern Sie dann Ihren Code entsprechend in Ihrer Methode:

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

Übergeben Sie dann dieses Salt und entfernen Sie den Namen in Ihrer SQL-Datenbankabfrage. Unter der Anmeldung befindet sich ein Tabellenname:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
quelle
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Übergeben Sie dieses Salt und uname in Ihrer Datenbank-SQL-Abfrage. Unter login befindet sich ein Tabellenname : - sql = "In Anmeldewerte einfügen ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1, weil md5 sehr schnell ist, was die Verwendung einer einzelnen Iteration von md5 zu einer schlechten Wahl für eine Passwort-Hashing-Funktion macht.
Mark Amery