Sól i mieszaj hasło w Pythonie

93

Ten kod ma zhaszować hasło za pomocą soli. Sól i zaszyfrowane hasło są zapisywane w bazie danych. Samo hasło nie jest.

Biorąc pod uwagę delikatny charakter operacji, chciałem się upewnić, że wszystko jest koszerne.

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
źródło
Dlaczego kodujesz sól b64? Byłoby prostsze po prostu użycie soli bezpośrednio, a następnie kodowanie b64 razem t_sha.digest() + salt. Możesz ponownie podzielić sól później, gdy odkodujesz solone hasło mieszające, ponieważ wiesz, że zdekodowane zaszyfrowane hasło ma dokładnie 32 bajty.
Duncan
1
@Duncan - zakodowałem sól w base64, więc mogłem wykonywać na niej mocne operacje bez martwienia się o dziwne problemy. Czy wersja „bajtowa” będzie działać jako ciąg? W takim przypadku nie potrzebuję też kodowania base64 t_sha.digest (). Prawdopodobnie nie zapisałbym razem zaszyfrowanego hasła i soli tylko dlatego, że wydaje się to trochę bardziej skomplikowane i trochę mniej czytelne.
Chris Dutrow,
Jeśli używasz Pythona 2.x, obiekt bajtów będzie działał doskonale jako łańcuch. Python nie nakłada żadnych ograniczeń na zawartość łańcucha. Jednak to samo może nie mieć zastosowania, jeśli przekażesz ciąg do dowolnego kodu zewnętrznego, takiego jak baza danych. Python 3.x rozróżnia typy bajtów i łańcuchy, więc w takim przypadku nie chciałbyś używać operacji na łańcuchach na soli.
Duncan,
4
Nie mogę ci powiedzieć, jak to zrobić w Pythonie, ale zwykły SHA-512 to zły wybór. Użyj powolnego skrótu, takiego jak PBKDF2, bcrypt lub scrypt.
CodesInChaos
Uwaga dodatkowa: odradzałbym używanie identyfikatorów UUID jako źródła kryptograficznej losowości. Tak, implementacja używana przez CPython jest kryptograficznie bezpieczna , ale nie jest to podyktowane specyfikacją Pythona ani specyfikacją UUID , a implementacje podatne na ataki istnieją . Jeśli Twoja baza kodu zostanie uruchomiona przy użyciu implementacji Pythona bez bezpiecznych identyfikatorów UUID4, osłabisz swoje bezpieczeństwo. To może być mało prawdopodobny scenariusz, ale jego użycie nic nie kosztuje secrets.
Mark Amery,

Odpowiedzi:

49

EDYCJA: ta odpowiedź jest błędna. Pojedyncza iteracja SHA512 jest szybka , co sprawia, że ​​nie nadaje się do użycia jako funkcja mieszania haseł. Zamiast tego użyj jednej z pozostałych odpowiedzi.


Według mnie wygląda dobrze. Jednak jestem prawie pewien, że tak naprawdę nie potrzebujesz base64. Możesz po prostu zrobić to:

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

Jeśli nie powoduje to trudności, możesz uzyskać nieco bardziej wydajne przechowywanie w bazie danych, przechowując sól i zaszyfrowane hasło jako surowe bajty, a nie ciągi szesnastkowe. Aby to zrobić, należy wymienić hexz bytesi hexdigestz digest.

Taymon
źródło
1
Tak, zaklęcie zadziałałoby dobrze. Wolę base64, ponieważ struny są trochę krótsze. Bardziej wydajne jest przekazywanie i wykonywanie operacji na krótszych strunach.
Chris Dutrow,
Jak to odwrócić, aby odzyskać hasło?
baza węzłowa
28
Nie odwracasz tego, nigdy nie odwracasz hasła. Dlatego haszujemy to i nie szyfrujemy. Jeśli chcesz porównać hasło wejściowe z zapisanym hasłem, haszujesz dane wejściowe i porównujesz skróty. Jeśli zaszyfrujesz hasło, każdy, kto ma klucz, może je odszyfrować i zobaczyć. To nie jest bezpieczne
Sebastian Gabriel Vinci
4
uuid.uuid4 (). hex jest inny za każdym razem, gdy jest generowany. Jak zamierzasz porównać hasło w celu sprawdzenia, jeśli nie możesz odzyskać tego samego identyfikatora użytkownika?
LittleBobbyTables
3
@LittleBobbyTables Myślę, że saltsą przechowywane w bazie danych, a także zaszyfrowane hasło.
clemtoy
70

Na podstawie innych odpowiedzi na to pytanie zaimplementowałem nowe podejście przy użyciu bcrypt.

Dlaczego warto korzystać z bcrypt

Jeśli dobrze rozumiem, argument użyć bcryptna SHA512to, że bcryptma być powolny. bcryptma również opcję dostosowania, jak wolno ma być generowane zaszyfrowane hasło po raz pierwszy:

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

Powolne jest pożądane, ponieważ jeśli złośliwa strona dostanie ręce na stół zawierający zaszyfrowane hasła, znacznie trudniej jest je brutalnie wymusić.

Realizacja

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)

Uwagi

Udało mi się dość łatwo zainstalować bibliotekę w systemie Linux przy użyciu:

pip install py-bcrypt

Jednak miałem więcej problemów z instalacją go w moich systemach Windows. Wydaje się, że potrzebuje poprawki. Zobacz to pytanie o przepełnienie stosu: instalacja py-bcrypt na win 7 64-bitowym pythonie

Chris Dutrow
źródło
4
12 to domyślna wartość dla gensalt
Ahmed Hegazy
2
Według pypi.python.org/pypi/bcrypt/3.1.0 , maksymalna długość hasła dla bcrypt to 72 bajty. Wszystkie postacie poza tym są ignorowane. Z tego powodu zalecają najpierw haszowanie za pomocą kryptograficznej funkcji skrótu, a następnie zakodowanie skrótu base64 (szczegółowe informacje znajdują się w linku). Uwaga dodatkowa: Wygląda na py-bcryptto, że jest to stary pakiet pypi i od tego czasu jego nazwa została zmieniona na bcrypt.
balu
48

Mądrą rzeczą nie jest samodzielne pisanie krypto, ale użycie czegoś takiego jak passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

Łatwo jest zepsuć kod kryptograficzny w bezpieczny sposób. Paskudne jest to, że w przypadku kodu innego niż kryptograficzny często natychmiast zauważasz, że nie działa, ponieważ program się zawiesza. Podczas gdy z kodem kryptograficznym często dowiadujesz się, gdy jest już za późno, a Twoje dane zostały naruszone. Dlatego uważam, że lepiej jest użyć pakietu napisanego przez kogoś, kto ma wiedzę na ten temat i który jest oparty na protokołach przetestowanych w walce.

Passlib ma również kilka fajnych funkcji, które czynią go łatwym w użyciu, a także łatwą aktualizacją do nowszego protokołu haszowania haseł, jeśli stary protokół okaże się uszkodzony.

Również tylko jedna runda sha512 jest bardziej podatna na ataki słownikowe. sha512 został zaprojektowany tak, aby był szybki i jest to faktycznie zła rzecz, gdy próbuje się bezpiecznie przechowywać hasła. Inni ludzie długo i intensywnie zastanawiali się nad wszystkimi tego rodzaju sprawami, więc lepiej to wykorzystaj.

MD
źródło
5
Przypuszczam, że rada dotycząca używania bibliotek crypo jest dobra, ale OP już używa hashlib, biblioteki kryptograficznej, która również znajduje się w standardowej bibliotece Pythona (w przeciwieństwie do passlib). Nadal używałbym hashlib, gdybym był w sytuacji PO.
dgh
18
@dghubble hashlibsłuży do kryptograficznych funkcji skrótu. passlibsłuży do bezpiecznego przechowywania haseł. To nie to samo (chociaż wydaje się, że wiele osób tak myśli ... a następnie hasła ich użytkowników są łamane).
Brendan Long
3
W przypadku gdyby ktoś się zastanawiał: passlibgeneruje własną sól, która jest przechowywana w zwróconym ciągu hash (przynajmniej dla niektórych schematów, takich jak BCrypt + SHA256 ) - więc nie musisz się tym martwić.
z0r
22

Aby to zadziałało w Pythonie 3, musisz kodować UTF-8 na przykład:

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

W przeciwnym razie otrzymasz:

Traceback (ostatnie wywołanie ostatnie):
Plik „”, linia 1,
hashed_password = hashlib.sha512 (hasło + sól) .hexdigest ()
TypeError: Obiekty Unicode muszą być zakodowane przed haszowaniem

Wayne
źródło
7
Nie. Nie używaj funkcji sha do mieszania haseł. Użyj czegoś takiego jak bcrypt. Powód znajduje się w komentarzach do innych pytań.
josch
11

Począwszy od Pythona 3.4, hashlibmoduł w bibliotece standardowej zawiera funkcje wyprowadzania kluczy, które są „zaprojektowane do bezpiecznego mieszania haseł” .

Więc użyj jednego z nich, na przykład hashlib.pbkdf2_hmac, z solą wygenerowaną przy użyciu 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')

Zauważ, że:

  • Użycie 16-bajtowej soli i 100000 iteracji PBKDF2 odpowiada minimalnym liczbom zalecanym w dokumentacji Pythona. Dalsze zwiększanie liczby iteracji spowoduje, że obliczenia skrótu będą wolniejsze, a przez to bezpieczniejsze.
  • os.urandom zawsze używa kryptograficznie bezpiecznego źródła losowości
  • hmac.compare_digest, używany w is_correct_password, jest w zasadzie tylko ==operatorem dla łańcuchów, ale bez możliwości zwarcia, co czyni go odpornym na ataki czasowe. To prawdopodobnie nie zapewnia żadnej dodatkowej wartości bezpieczeństwa , ale też nie boli, więc poszedłem do przodu i użyłem go.

Aby poznać teorię na temat tego, co składa się na dobry skrót haseł i listę innych funkcji odpowiednich do haszowania haseł, zobacz https://security.stackexchange.com/q/211/29805 .

Mark Amery
źródło
10

passlib wydaje się być przydatne, jeśli potrzebujesz użyć skrótów przechowywanych w istniejącym systemie. Jeśli masz kontrolę nad formatem, użyj nowoczesnego skrótu, takiego jak bcrypt lub scrypt. W tej chwili bcrypt wydaje się być znacznie łatwiejszy w użyciu z Pythona.

passlib obsługuje bcrypt i zaleca zainstalowanie py-bcrypt jako zaplecza: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Możesz także użyć bezpośrednio py-bcrypt, jeśli nie chcesz instalować passlib. Plik Readme zawiera przykłady podstawowego użycia.

zobacz także: Jak używać scrypt do generowania skrótu hasła i soli w Pythonie

Terrel Shumway
źródło
0

Po pierwsze importuj: -

import hashlib, uuid

Następnie zmień kod zgodnie z tym w swojej metodzie:

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

Następnie podaj tę sól i uname w zapytaniu sql bazy danych, poniżej login jest nazwa tabeli:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
źródło
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Następnie podaj tę sól i uname w zapytaniu sql bazy danych, poniżej login to nazwa tabeli : - sql = "wstaw do wartości logowania ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1, ponieważ md5 jest bardzo szybki, co sprawia, że ​​użycie pojedynczej iteracji md5 jest kiepskim wyborem dla funkcji mieszania haseł.
Mark Amery,