Hash-Passwörter langsam mit bcrypt in Python

Der hier verwendete Code ist auf Github. https://github.com/matsulib/bcrypt-dictionary-attack

Inhaltsverzeichnis

Einführung

Wenn Sie die Hash-Funktion bcrypt verwenden möchten, die für die Kennwortspeicherung in Python geeignet ist, verwenden Sie das bcrypt-Modul (https://pypi.python.org/pypi/bcrypt/3.1.1).

** Installationsmethode **:

** Funktionen von bcrypt **

bcrypt ist eine kryptobasierte Blowfish-Hash-Funktion.

Der entscheidende Unterschied zwischen bcrypt- und Hash-Funktionen wie der MD5- und der SHA-Familie besteht darin, dass erstere ein schneller Hash ist, während letztere ein langsamer Hash ist.

Da schneller Hash schnell ist, ist es praktisch, einen Digest mit fester Länge aus einer großen Datei abzurufen. Wenn Sie ihn jedoch für die Kennwortverwaltung verwenden, besteht das Problem, dass der durchgesickerte Hash-Wert durch einen Offline-Angriff leicht geknackt werden kann. .. Daher gibt es eine Technik namens Dehnen (wiederholtes Hashing), die den Prozess absichtlich verlangsamt.

Bcrypt hingegen ist ein langsamer Hash, der ursprünglich für die Iteration entwickelt wurde und nicht nur langsam ist, sondern auch die schnelle Hardware-Implementierung erschwert.

This system hashes passwords using a version of Bruce Schneier's Blowfish block cipher with modifications designed to raise the cost of off-line password cracking and frustrate fast hardware implementation.

Basically, computational power can be parallelized cheaply and easily, but memory cannot. This is the cornerstone of bcrypt and scrypt. Obviously, they can still be broken by sheer brute force, and you could just use hardware with integrated memory units to circumvent the problem, but it's much harder and much, much more expensive to do so.

Um ehrlich zu sein, bin ich mir nicht sicher, ob die obige Erklärung korrekt oder aufgrund mangelnden Verständnisses noch gültig ist.

Es scheint, dass bcrypt auch in dem im Juli 2015 aufgetretenen Vorfall eines Informationslecks bei Registranten von Ashley Madison verwendet wurde. Es scheint jedoch, dass MD5 (und es war auch bekannt, dass es 26 Buchstaben des unteren Alphabets waren) tödlich war, und es scheint, dass bcrypt selbst für den Angreifer immer noch eine schmerzhafte Existenz ist.

Instead of cracking the slow bcrypt hashes directly, which is the hot topic at the moment, we took a more efficient approach and simply attacked the md5(lc($username).”::”.lc($pass)) and md5(lc($username).”::”.lc($pass).”:”.lc($email).”:73@^bhhs&#@&^@8@*$”) tokens instead. Having cracked the token, we simply then had to case correct it against its bcrypt counterpart.

** Grundlegende Verwendung von bcrypt **

--bcrypt.gensalt (Runden = 12, Präfix = b'2b ') # Salz erzeugen --bcrypt.hashpw (Passwort, Salt) # Hash Passwort --bcrypt.checkpw (Passwort, Hash-Passwort) # Passwort überprüfen

Hash das Passwort wie folgt:

python


>>> import bcrypt
>>> salt = bcrypt.gensalt(rounds=10, prefix=b'2a')
>>> salt
b'$2a$10$VpsqBArIfdhGzJY1YO/xyO'
>>> password = b'password'
>>> bcrypt.hashpw(password, salt)
b'$2a$10$VpsqBArIfdhGzJY1YO/xyOiOsCrQc9BZAaonPt3QDsL0HWzoaHXgG'

Es hat die folgende Struktur.

$(2-stellige Version)$(2-stellige Arbeit-factor)$(Salz mit 22 Zeichen)(31 Zeichen Hash)

** Über das Argument von bcrypt.gensalt () **

bcrypt.gensalt () generiert für jeden Aufruf ein anderes Salt. Sie können beim Aufruf auch zwei Argumente angeben.

  1. Runden: Ein Parameter namens Work-Faktor zum Verlangsamen der Berechnung von Hash-Werten, der von 4 bis 31 angegeben werden kann (Standard = 12). Der Vorgang wird 2 ^ Runden wiederholt.
  2. Präfix: Sie können b'2a'or b'2b 'angeben (Standard = b'2b').

Derzeit scheint "2a" der Mainstream zu sein.

Experiment 1. Wirkung des Arbeitsfaktors

Im Folgenden sehen wir uns die Berechnungszeit an, wenn der Arbeitsfaktorwert von bcrypt.gensalt () geändert wird.

** Ausführungsumgebung: **

** Code und Ergebnisse **

python


In [1]: import bcrypt
In [2]: password = b'password'
In [3]: for i in range(5, 18):
   ...:     print('rounds: {:02d}'.format(i), end=' => ')
   ...:     %timeit -r1 bcrypt.hashpw(password, bcrypt.gensalt(rounds=i))
   ...:
rounds: 05 => 100 loops, best of 1: 2.73 ms per loop
rounds: 06 => 100 loops, best of 1: 5.4 ms per loop
rounds: 07 => 100 loops, best of 1: 10.7 ms per loop
rounds: 08 => 10 loops, best of 1: 21 ms per loop
rounds: 09 => 10 loops, best of 1: 42.1 ms per loop
rounds: 10 => 10 loops, best of 1: 84.4 ms per loop
rounds: 11 => 10 loops, best of 1: 172 ms per loop
rounds: 12 => 1 loop, best of 1: 369 ms per loop
rounds: 13 => 1 loop, best of 1: 703 ms per loop
rounds: 14 => 1 loop, best of 1: 1.4 s per loop
rounds: 15 => 1 loop, best of 1: 2.71 s per loop
rounds: 16 => 1 loop, best of 1: 5.42 s per loop
rounds: 17 => 1 loop, best of 1: 10.8 s per loop

Es überrascht nicht, dass jedes Inkrement des Arbeitsfaktors die Berechnungszeit verdoppelte.

Experiment 2. Es sieht aus wie ein Wörterbuchangriff (Vergleich mit SHA256)

** Ausführungsumgebung **

** SHA256 (+ Salz) Code **

hashing_sha256.py


import uuid
import hashlib

# Reference: http://pythoncentral.io/hashing-strings-with-python/

def hash_password(password):
    # uuid is used to generate a random number
    salt = uuid.uuid4().hex
    return hashlib.sha256(salt.encode() + password.encode()).hexdigest() + ':' + salt
    
def check_password(hashed_password, user_password):
    password, salt = hashed_password.split(':')
    return password == hashlib.sha256(salt.encode() + user_password.encode()).hexdigest()

if __name__ == '__main__':
    new_pass = input('Please enter a password: ')
    hashed_password = hash_password(new_pass)
    print('The string to store in the db is: ' + hashed_password)
    old_pass = input('Now please enter the password again to check: ')
    if check_password(hashed_password, old_pass):
        print('You entered the right password')
    else:
        print('I am sorry but the password does not match')

Ausführungsergebnis

> python hashing_sha256.py
Please enter a password: piyo
The string to store in the db is: 25bcf883839511cdb493b3ba25bc0ad3fc809a3688076d198ef13128f5883a66:f0ac11a13a314336951392ff52c9c2d6
Now please enter the password again to check: piyo
You entered the right password

** Verschlüsselungscode ** Das Ausführungsergebnis entspricht dem SHA256-Code.

hashing_bcrypt.py


import bcrypt

def hash_password(password, rounds=12):
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds)).decode()

def check_password(hashed_password, user_password):
    return bcrypt.checkpw(user_password.encode(), hashed_password.encode())

** Wörterbuch-Angriffscode **

dictionary_attack.py


import time

import hashing_sha256 as sha256
import hashing_bcrypt as bcrypt

def attack(leaked_hashed_password, hashing):
    # https://www.teamsid.com/worst-passwords-2015/
    dictionary = ['123456', 'password', '12345678', 'qwerty', '12345', 
                '123456789', 'football', '1234', '1234567', 'baseball', 
                'welcome', '1234567890', 'abc123', '111111', '1qaz2wsx', 
                'dragon', 'master', 'monkey', 'letmein', 'login', 'princess', 
                'qwertyuiop', 'solo', 'passw0rd', 'starwars']
            
    for p in dictionary:
        if hashing.check_password(leaked_hashed_password, p):
            return 'Schlagen! Das Passwort ist{}ist.'.format(p)
    else:
        return 'aus(´ ・ ω ・ `)'


passwords = ['complex.password'] * 9 + ['passw0rd']

print('sha256')
leaked_sha256 = [sha256.hash_password(p) for p in passwords]
st = time.time()
for i, v in enumerate(leaked_sha256):
    result = attack(v, sha256)
    print('user{:02d}: {}'.format(i, result))
print('Total time: {:.3f} s'.format(time.time()-st))

print('bcrypt-5')
leaked_bcrypt = [bcrypt.hash_password(p, 5) for p in passwords]
st = time.time()
for i, v in enumerate(leaked_bcrypt):
    result = attack(v, bcrypt)
    #print('user{:02d}: {}'.format(i, result))
print('Total time: {:.3f} s'.format(time.time()-st))

print('bcrypt-12')
leaked_bcrypt = [bcrypt.hash_password(p) for p in passwords]
st = time.time()
for i, v in enumerate(leaked_bcrypt):
    result = attack(v, bcrypt)
    #print('user{:02d}: {}'.format(i, result))
print('Total time: {:.3f} s'.format(time.time()-st))

Ergebnis

sha256
user00:aus(´ ・ ω ・ `)
user01:aus(´ ・ ω ・ `)
user02:aus(´ ・ ω ・ `)
user03:aus(´ ・ ω ・ `)
user04:aus(´ ・ ω ・ `)
user05:aus(´ ・ ω ・ `)
user06:aus(´ ・ ω ・ `)
user07:aus(´ ・ ω ・ `)
user08:aus(´ ・ ω ・ `)
user09:Schlagen! Das Passwort lautet passw0rd.
Total time: 0.005 s
bcrypt-5
Total time: 0.715 s
bcrypt-12
Total time: 90.040 s

Sie können sehen, dass Wörterbuchangriffe auf bcrypt (und natürlich Brute Force-Angriffe) viel länger dauern als SHA256. Die tatsächliche Anzahl von Wörterbüchern und Benutzern ist größer, daher sollte es effektiv sein, Angreifer zu belästigen.

Zusammenfassung

** 1. Wenn Sie bcrypt aus Python verwenden, verwenden Sie das bcrypt-Modul **

** 2. Wählen Sie den richtigen Arbeitsfaktor **

Durch Festlegen eines großen Werts für den Arbeitsfaktor kann Zeit für den Offline-Angriff eines Angreifers gespart werden. Dies wirkt sich jedoch auch auf die Leistung während des normalen Betriebs aus. Es ist ein Kompromiss zwischen Sicherheit und Komfort.

Bestimmen Sie die Anzahl der Iterationen, bei denen der Server das Kennwort innerhalb der gewünschten Zeit (10, 200 ms usw.) überprüft und verwendet.

I don’t believe that there is a “correct” work factor; it depends on how strong you want your hashes to be and how much computational power you want to reserve for the hashing process.

Ende.

Recommended Posts

Hash-Passwörter langsam mit bcrypt in Python
Übersetzt mit Googletrans in Python
Verwenden des Python-Modus in der Verarbeitung
GUI-Programmierung in Python mit Appjar
Versuchen Sie es mit LevelDB mit Python (plyvel)
Verwendung globaler Variablen in Python-Funktionen
Mal sehen, wie man Eingaben in Python verwendet
Gesamtleistung in Python (mit Funktools)
Hash-Methode (Open-Address-Methode) in Python
Handschriftliche Zeichenerkennung mit KNN in Python
Versuchen Sie es mit LeapMotion mit Python
Suche nach Tiefenpriorität mit Stack in Python
Bei Verwendung regulärer Ausdrücke in Python
GUI-Erstellung in Python mit tkinter 2
Mausbedienung mit Windows-API in Python
Hinweise zur Verwendung von cChardet und python3-chardet in Python 3.3.1.
GUI-Erstellung in Python mit tkinter Teil 1
Übung, dies in Python zu verwenden (schlecht)
Versuchen Sie, die Kraken-API mit Python zu verwenden
Verwenden von venv in der Windows + Docker-Umgebung [Python]
[FX] Hit oanda-API mit Python mit Docker
Tweet mit der Twitter-API in Python
[Python] [Windows] Serielle Kommunikation in Python über DLL
Ich habe versucht, die Bayes'sche Optimierung von Python zu verwenden
Melden Sie sich mit Anforderungen in Python bei Slack an
Holen Sie sich Youtube-Daten in Python mithilfe der Youtube-Daten-API
Hash in Perl ist ein Wörterbuch in Python
Verwenden physikalischer Konstanten in Python scipy.constants ~ Konstante e ~
Scraping von Websites mit JavaScript in Python
Entwicklung eines Slack Bot mit Python mit chat.postMessage
Schreiben Sie mit f2py ein Python-Modul in fortran
Zeichnen Sie mit graphviz eine Baumstruktur in Python 3
Hinweise zur Verwendung von Python (Pydev) mit Eclipse
Krankheitsklassifizierung durch Random Forest mit Python
Laden Sie Dateien in jedem Format mit Python herunter
Parallele Taskausführung mit concurrent.futures in Python
Quadtree in Python --2
Python in der Optimierung
CURL in Python
Geokodierung in Python
SendKeys in Python
Erstellen Sie eine GIF-Datei mit Pillow in Python
Metaanalyse in Python
E-Mail-Anhänge über Ihr Google Mail-Konto mit Python.
Unittest in Python
Erstellen eines Nummerierungsprozesses mit Python im lokalen DynamoDB-Nummerierungsprozess
Versuchen Sie es mit der BitFlyer Ligntning API in Python
[Python] Wörterbuch (Hash)
Epoche in Python
Zwietracht in Python
Holen Sie sich die Bild-URL mithilfe der Flickr-API in Python
Starten Sie Python
Deutsch in Python
DCI in Python
Hinweise zur Verwendung von dict mit Python [Competition Pro]
Quicksort in Python
nCr in Python
Lassen Sie uns Emotionen mithilfe der Emotions-API in Python beurteilen