Passwortabfrage / Hashlib

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Ich Grüße Euch :D

Ich habe ein kleines Skript geschrieben, welches einen Passwort hash erzeugt. Ist quasi aus einer Übungsaufgabe entstanden, die ich erweitert habe. Aber ich verstehe leider nicht, wie ich nun ein Passwort eines Benutzers mit dem Hash prüfen kann. Hab schon mehreres probiert, von dem nichts gekappt hat.

Die Überprüfung ist nicht mit in dem Code, das habe ich bisher nur in der interaktiven Konsole getestet. Das Problem ist, das der Hash einfach nicht mit dem User übereinstimmt. :roll:

Wäre nett, wenn mir jemand weiterhelfen kann. Wünsche Euch eine angenehme Woche.

Hier mein Code, mit dem ich den Hash erstelle:

Code: Alles auswählen

# Imports
# *********************************************************************
import os
import hashlib
import getpass
import binascii
# *********************************************************************


def password_user():
    """Password user input

    :return: The password from the user
    """

    pw = getpass.getpass()
    pw_sha = hashlib.sha512(bytes(pw, 'utf-8'))

    return pw, pw_sha


def salt_gen(n):
    """Salt generator

    :param n: The number of bytes
    To make it impossible for an attacker to create a lookup table for
    every possible salt, the salt must be long. A good rule of thumb is
    to use a salt that is the same size as the output of the hash
    function. For example, the output of SHA256 is 256 bits (32 bytes),
    so the salt should be at least 32 random bytes.
    [https://crackstation.net/hashing-security.htm - (CC BY-SA 3.0)]

    :return: The created salt
    """

    salt = os.urandom(n)

    return salt


def secure_pw_hash(hash_name, pw, salt):
    """Create a secure password hash with hashlib.pbkdf2_hmac

    :param hash_name: The hash algorithm name (e.g. sha512)
    :param pw: The password from the function -> password_user
    :param salt: The salt that generated in function -> salt_gen
    :return: The Secure password -> sec_pw
    """

    # Caution: 1_000_000 only on Python >= 3.6 . When below use: 1000000
    sec_pw = hashlib.pbkdf2_hmac(hash_name, pw.encode(), salt, 1_000_000)

    return sec_pw


def main():
    """Receive and send all relevant data"""

    # Method calls
    pw, pw_sha = password_user()
    salt = salt_gen(64)
    sec_pw = secure_pw_hash('sha512', pw, salt)

    # Bytes to hex
    hex_sec_pw = binascii.b2a_hex(sec_pw)
    hex_salt = binascii.b2a_hex(salt)

    # Decode to str
    hex_sec_pw = hex_sec_pw.decode()
    hex_salt = hex_salt.decode()
    pw_sha = pw_sha.hexdigest()

    print('Secure Password: {}\nHashed Password: {}\nSalt: {}'.format(
        hex_sec_pw, pw_sha, hex_salt))


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich verstehe deine Frage zwar nicht so ganz, aber was mir dazu einfällt: Sinn eines salts ist es, zusammen mit User und dem Hash aus Passwort und Salt gespeichert zu werden.

Nur dann kann man aus dem User das Salt nachschlagen, und dann wieder mit eingegebenen Passwort zusammen hashen, und gegen das gespeicherte Hash zu prüfen.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

__deets__ hat geschrieben:Ich verstehe deine Frage zwar nicht so ganz, aber was mir dazu einfällt: Sinn eines salts ist es, zusammen mit User und dem Hash aus Passwort und Salt gespeichert zu werden.

Nur dann kann man aus dem User das Salt nachschlagen, und dann wieder mit eingegebenen Passwort zusammen hashen, und gegen das gespeicherte Hash zu prüfen.
Ich versuche mich mal genauer auszudrücken:

Angenommen, ich habe nun mit dem Skript ein pw für User 'X' angelegt. Nun kommt User X und gibt ein Passwort ein. Ich hole das Passwort über das Modul getpass ab und möchte es vergleichen. Wie mache ich das genau? Denn sobald ich das vergleichen will, bekomme ich immer einen anderen hash.

Mir ist klar, dass ich vermutlich irgendetwas falsch interpretiere, aber ich komme nicht drauf wo.

Ohne den salt würde die abfrage beispielsweise so aussehen (hier habe ich noch Input anstatt getpass verwendet, da es so war in der Beispielaufgabe):

Code: Alles auswählen

import hashlib


def main():
    # Password: 'Schwarzer Wolf'
    pw_hash = '9325752ab99c719d65b59be54117c70130db786618a52f4fdc8b19382cb37d15'

    x = hashlib.sha512(bytes(input('Password: '), 'utf-8'))

    if pw_hash == x.hexdigest():
        print('Access granted')
    else:
        print('Access denied')


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: Der Kommentar `Imports` und die *-Zeilen stören nur, weg damit. Dass es sich um Importe handelt, sieht man ja schon am Wort `import`. Warum liefert `password_user` auch den Hash des Passwort zurück, damit kann und sollte man nichts anfangen, weil die eigentliche Hash-Funktion ja in `secure_pw_hash` aufgerufen wird. Mit dem Salz kann man es auch übertreiben. Und welchen der beiden Passwort-Hashes hast Du in Deinem unteren Programm verwendet? Dann mußt Du auch die gleiche Funktion wie oben zum Erzeugen des Hashs benutzen.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Sirius3 hat geschrieben:Mit dem Salz kann man es auch übertreiben.
Bezüglich der Länge des salts wird doch von einigen gesagt, dass er so lang sein soll wie der hash.
Sirius3 hat geschrieben: Warum liefert `password_user` auch den Hash des Passwort zurück, damit kann und sollte man nichts anfangen, weil die eigentliche Hash-Funktion ja in `secure_pw_hash` aufgerufen wird.
[...]
Und welchen der beiden Passwort-Hashes hast Du in Deinem unteren Programm verwendet? Dann mußt Du auch die gleiche Funktion wie oben zum Erzeugen des Hashs benutzen.
Ich habe vergessen, das vor dem Posten raus zu machen. Diente zur Fehlersuche. Hier ohne Eingrenzung:

Code: Alles auswählen

import os
import hashlib
import getpass
import binascii


def password_user():
    """Password user input

    :return: The password from the user
    """

    pw = getpass.getpass()

    return pw


def salt_gen(n):
    """Salt generator

    :param n: The number of bytes
    To make it impossible for an attacker to create a lookup table for
    every possible salt, the salt must be long. A good rule of thumb is
    to use a salt that is the same size as the output of the hash
    function. For example, the output of SHA256 is 256 bits (32 bytes),
    so the salt should be at least 32 random bytes.
    [https://crackstation.net/hashing-security.htm - (CC BY-SA 3.0)]

    :return: The created salt
    """

    salt = os.urandom(n)

    return salt


def secure_pw_hash(hash_name, pw, salt):
    """Create a secure password hash with hashlib.pbkdf2_hmac

    :param hash_name: The hash algorithm name (e.g. sha512)
    :param pw: The password from the function -> password_user
    :param salt: The salt that generated in function -> salt_gen
    :return: The Secure password -> sec_pw
    """

    # Caution: 1_000_000 only on Python >= 3.6 . When below use: 1000000
    sec_pw = hashlib.pbkdf2_hmac(hash_name, pw.encode(), salt, 1_000_000)

    return sec_pw


def main():
    """Receive and send all relevant data"""

    # Method calls
    pw = password_user()
    salt = salt_gen(64)
    sec_pw = secure_pw_hash('sha512', pw, salt)

    # Bytes to hex
    hex_sec_pw = binascii.b2a_hex(sec_pw)

    # Decode to str
    hex_sec_pw = hex_sec_pw.decode()

    print('Secure Password: {}'.format(hex_sec_pw))


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: und jetzt mußt Du Dein neu eingegebenes Passwort exakt gleich generieren wie beim ersten Mal, sonst macht ein Vergleich ja keinen Sinn.

Ein Salt sorgt nur dafür, dass es schwieriger wird, mit vorberechneten Tabellen das Passwort zu raten. Daher nie zweimal den selben Salt für zwei verschiedene Passwörter benutzen. Warum der Salt aber so lang sein soll, wie der Hash, dazu gibt es keine plausible Erklärung. Einzig "weil's halt geht und mehr keinen Sinn macht" ist für mich kein Grund. Bei 32bit müßte man 4 Milliarden Werte speichern, da hätte man für ein Passwort (12345678) 256GB-Daten, was für das eine Passwort noch gehen würde, man müßte aber für viele solcher Passworttabellen speichern. Bei 48bit wären das schon 16 PetaByte pro Password, das tut sich niemand an.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Sirius3 hat geschrieben:@Schwarzer Wolf: und jetzt mußt Du Dein neu eingegebenes Passwort exakt gleich generieren wie beim ersten Mal, sonst macht ein Vergleich ja keinen Sinn.

Ein Salt sorgt nur dafür, dass es schwieriger wird, mit vorberechneten Tabellen das Passwort zu raten. Daher nie zweimal den selben Salt für zwei verschiedene Passwörter benutzen. Warum der Salt aber so lang sein soll, wie der Hash, dazu gibt es keine plausible Erklärung. Einzig "weil's halt geht und mehr keinen Sinn macht" ist für mich kein Grund. Bei 32bit müßte man 4 Milliarden Werte speichern, da hätte man für ein Passwort (12345678) 256GB-Daten, was für das eine Passwort noch gehen würde, man müßte aber für viele solcher Passworttabellen speichern. Bei 48bit wären das schon 16 PetaByte pro Password, das tut sich niemand an.
Danke für das beeindruckende mathematische Beispiel. Dass alles exakt gleich sein muss, ist mir klar, und genau hier liegt mein Problem. Wenn ich nun beispielsweise den gleichen Aufruf mache wie beim ersten Mal, dann den urandom salt durch den festen ersetze, der beim ersten Mal generiert wurde, dann bekomme ich trotzdem einen anderen hash bei 'Secure Password':

1. User generieren:
getpass: test
Secure Password: c5e555b3897ef8d006698af14e84dacf5862d975d53e269c051fcf60w07ea20d3e9457403690f6db69c513b22137a8e512668657c459a1f20e99fddd64212872a
Salt: 4be2e9645a84fbfabff058c239a2c9e1e7051868f7167b7d15328b4ef96b6e633d6cc008ff3ebe78a0b6546989d2b9d787370e11a649d96c220462c9cd1ba8ff

2. User abfragen:
getpass: test
Secure Password: 477c98ce2b6e1704b245fcb97a8c827b7367e28532b4c7eef788b9a65eb90b97e105ce120f591481c0ce6dac1fb8745c060bac9761081c1912e5c071aaa61140
Salt: 4be2e9645a84fbfabff058c239a2c9e1e7051868f7167b7d15328b4ef96b6e633d6cc008ff3ebe78a0b6546989d2b9d787370e11a649d96c220462c9cd1ba8ff

Mein Problem wird wahrscheinlich 'iterations' bei hashlib.pbkdf2_hmac sein mit dem wert 1_000_000. Werden dadurch auch noch mal zufällige Werte erzeugt?
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: zeig doch mal den Code, den Du jetzt verwendest. Hast Du den Salt wieder von Hex nach Bytes umgewandelt?
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Sirius3 hat geschrieben:@Schwarzer Wolf: zeig doch mal den Code, den Du jetzt verwendest. Hast Du den Salt wieder von Hex nach Bytes umgewandelt?
Ich weiß nicht, woran es genau lag, aber das Problem ist gelöst. Ich habe mir einfach die Mühe gemacht, das Skript rudimentär weiter zu schreiben und eine kleine Überprüfung mit einzubauen:

Code: Alles auswählen

import os
import hashlib
import getpass
import binascii


def password_user():
    """Password user input

    :return: The password from the user
    """

    pw = getpass.getpass()

    return pw


def salt_gen(n):
    """Salt generator

    :param n: The number of bytes
    To make it impossible for an attacker to create a lookup table for
    every possible salt, the salt must be long. A good rule of thumb is
    to use a salt that is the same size as the output of the hash
    function. For example, the output of SHA256 is 256 bits (32 bytes),
    so the salt should be at least 32 random bytes.
    [https://crackstation.net/hashing-security.htm - (CC BY-SA 3.0)]

    :return: The created salt
    """

    salt = os.urandom(n)

    return salt


def secure_pw_hash(hash_name, pw, salt):
    """Create a secure password hash with hashlib.pbkdf2_hmac

    :param hash_name: The hash algorithm name (e.g. sha512)
    :param pw: The password from the function -> password_user
    :param salt: The salt that generated in function -> salt_gen
    :return: The Secure password -> sec_pw
    """

    # Caution: 1_000_000 only on Python >= 3.6 . When below use: 1000000
    sec_pw = hashlib.pbkdf2_hmac(hash_name, pw.encode(), salt, 1_000_000)

    return sec_pw


def convert(sec_pw, chk_pw):
    # Bytes to hex
    hex_sec_pw = binascii.b2a_hex(sec_pw)
    hex_chk_pw = binascii.b2a_hex(chk_pw)

    # Decode to str
    hex_sec_pw = hex_sec_pw.decode()
    hex_chk_pw = hex_chk_pw.decode()

    return hex_sec_pw, hex_chk_pw


def user_check(sec_pw, chk_pw):
    """Check if user password is correct"""

    if sec_pw == chk_pw:
        print('Access granted')

    else:
        print('Access denied')


def main():
    """Receive and send all relevant data"""

    # Method calls -> Create passwort
    pw = password_user()
    salt = salt_gen(64)
    sec_pw = secure_pw_hash('sha512', pw, salt)

    # Method call -> Check passwprd
    pw_chk = password_user()
    chk_pw = secure_pw_hash('sha512', pw_chk, salt)
    hex_sec_pw, hex_chk_pw = convert(sec_pw, chk_pw)
    user_check(hex_sec_pw, hex_chk_pw)


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: jetzt packt man alle Information, die man zum Hashen des Passworts braucht zum Passworthash dazu, (also hash_name, iterations, salt), so dass man diese Information beim Passwortprüfen kompakt hat. Die Funktion `convert` ist keine gute Funktion, weil sie zu speziell und in diesem Fall auch unnötig ist.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Sirius3 hat geschrieben:jetzt packt man alle Information, die man zum Hashen des Passworts braucht zum Passworthash dazu, (also hash_name, iterations, salt), so dass man diese Information beim Passwortprüfen kompakt hat.
Damit meinst Du Dict / Liste / db?
Sirius3 hat geschrieben:Die Funktion `convert` ist keine gute Funktion, weil sie zu speziell und in diesem Fall auch unnötig ist.
Also lass ich das Konvertieren nach hex komplett weg, und benutze die bytes?

Ist das mit 'zu speziell' eine allgemeine Regel bei Funktionen / Methoden? Ich meine damit, sollen sie immer etwas allgemeiner und breiter gefächert sein?

Wie lange würdest Du persönlich den salt machen in bytes?

Eine letzte Frage, die ich nicht wirklich aus der 'libary reference' herauslesen kann. Was genau ist die Aufgabe der Iteration? Sie Wiederholen, das ist mir klar, aber was?

Ich danke Dir für die Hilfe. :D
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Methode convert ist zu speziell weil sie das gleiche zweimal tut. Du gewinnst eigentlich nix. Denn wenn du etwas an dem Vorgehen änderst, musst du es zweimal ändern. Sie sollte nur ein Argument nehmen und zurück liefern, und dann rufst du sie halt zweimal auf.

Und was die Informationen angeht: natürlich DB oder andere Persistenz-Schicht. Ohne so etwas macht das ganze ja keinen Sinn.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Schwarzer Wolf hat geschrieben:
Sirius3 hat geschrieben:jetzt packt man alle Information, die man zum Hashen des Passworts braucht zum Passworthash dazu, (also hash_name, iterations, salt), so dass man diese Information beim Passwortprüfen kompakt hat.
Damit meinst Du Dict / Liste / db?
In der Praxis packt man alles in einen String. Auf die Weise kann man es problemlos in einer Datenbank speichern.

Du solltest Bedenken dass man in der Praxis Parameter und auch den Algorithmus selbst möglicherweise ändern muss, falls eine Sicherheitslücke entdeckt wird. Es gibt mehrere Möglichkeiten ein "Upgrade" von einem Password Hash zu machen, erfordert aber dass man sich um das Format etwas Gedanken macht. Erst recht wenn man dies machen möchte ohne dass Passwort zu kennen.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

DasIch hat geschrieben: Du solltest Bedenken dass man in der Praxis Parameter und auch den Algorithmus selbst möglicherweise ändern muss, falls eine Sicherheitslücke entdeckt wird. Es gibt mehrere Möglichkeiten ein "Upgrade" von einem Password Hash zu machen, erfordert aber dass man sich um das Format etwas Gedanken macht. Erst recht wenn man dies machen möchte ohne dass Passwort zu kennen.
Hast du dafür ein Beispiel? Klingt herausfordernd. Und nach etwas, das man leicht verkackt ohne echt Ahnung von crypto zu haben.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: wenn z.B. sha512 geknackt ist, dann fordert man einfach jeden beim nächsten mal Einloggen auf, ein neues Passwort zu vergeben und hashed das neue Passwort mit dem neuen Algorithmus. Da der Algorithmus mit beim Passworthash steht, gehen solche Änderungen ganz transparent.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Sirius3 wenn das alles ist - dann ist es ja simpel & kann bei bedarf nachgepflegt werden.

Aber es klang so, als ob da noch mehr hinter wäre.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

__deets__ hat geschrieben:Hast du dafür ein Beispiel? Klingt herausfordernd. Und nach etwas, das man leicht verkackt ohne echt Ahnung von crypto zu haben.
Du hast im wesentlichen zwei Möglichkeiten:

1. Passwort beim nächsten Login ändern. Hierbei überprüft du ob dass Passwort stimmt wie sonst auch und falls korrekt erstellst und speicherst du neuen Hash.

Das ist sehr einfach zu implementieren und es kann eigentlich nichts falsch gehen. Nachteil ist natürlich du hast ein Hash der "unsicher" ist bis sich der User wieder einloggt. Wenn man eine Seite hat die nicht regelmäßig genutzt wird also täglich oder zumindest wöchentlich, vielleicht ein Problem. Das kann man dann umgehen indem man den Account irgendwie deaktiviert und einen Aktivierungslink per E-Mail schickt aber ist natürlich nicht sonderlich elegant.

2. Passwort Hash hashen. Hierbei hasht du den existierenden Hash. Um den zu validieren muss man dann NewHash(OldHash(Password)) == NewPasswordHash berechnen, danach machst du Schritt 1. Erfordert dass du dir natürlich alle verwendeten Algorithmen und dazugehörige Parameter mit speicherst.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ah. Das macht Sinn.
Antworten