Соль и хеш-пароль в Python

93

Этот код должен хешировать пароль с солью. Соль и хешированный пароль сохраняются в базе данных. Самого пароля нет.

Учитывая деликатный характер операции, я хотел убедиться, что все было кошерно.

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())
Крис Датроу
источник
Зачем ты соль b64 кодируешь? Было бы проще просто использовать соль напрямую, а затем b64 кодировать их вместе t_sha.digest() + salt. Позже, когда вы расшифруете соленый хэш-пароль, вы можете снова разделить соль, поскольку вы знаете, что декодированный хешированный пароль составляет ровно 32 байта.
Дункан
1
@Duncan - я закодировал соль в base64, чтобы я мог выполнять над ней серьезные операции, не беспокоясь о странных проблемах. Будет ли "байтовая" версия работать как строка? Если это так, то мне также не нужно кодировать t_sha.digest () base64. Я бы, вероятно, не стал бы хранить хешированный пароль и соль вместе только потому, что это кажется немного более сложным и немного менее читаемым.
Крис Датроу,
Если вы используете Python 2.x, объект bytes будет отлично работать как строка. Python не накладывает никаких ограничений на то, что вы можете иметь в строке. Однако то же самое может не применяться, если вы передадите строку любому внешнему коду, например базе данных. Python 3.x различает байтовые типы и строки, поэтому в этом случае вы не захотите использовать строковые операции с солью.
Дункан
4
Я не могу сказать вам, как это сделать на Python, но простой SHA-512 - плохой выбор. Используйте медленный хэш, например PBKDF2, bcrypt или scrypt.
CodesInChaos
Боковое примечание: я бы не советовал использовать UUID в качестве источника криптографической случайности. Да, реализация, используемая CPython , криптографически безопасна , но это не продиктовано спецификацией Python или спецификацией UUID , и уязвимые реализации существуют . Если ваша кодовая база запускается с использованием реализации Python без безопасных UUID4, вы ослабите свою безопасность. Это может быть маловероятным сценарием, но secretsвместо этого он ничего не стоит .
Марк Эмери

Ответы:

49

РЕДАКТИРОВАТЬ: этот ответ неверен. Одна итерация SHA512 выполняется быстро , что делает ее непригодной для использования в качестве функции хеширования паролей. Вместо этого используйте один из других ответов.


Мне нравится. Однако я почти уверен, что вам действительно не нужен base64. Вы могли просто сделать это:

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

Если это не создает трудностей, вы можете получить немного более эффективное хранилище в своей базе данных, сохранив соль и хешированный пароль в виде необработанных байтов, а не шестнадцатеричных строк. Для этого замените hexна bytesи hexdigestна digest.

Таймон
источник
1
Да, шестнадцатеричный будет работать нормально. Я предпочитаю base64, потому что строки немного короче. Более эффективно передавать и выполнять операции с более короткими строками.
Крис Датроу
Теперь, как это сделать, чтобы вернуть пароль?
nodebase
28
Вы не измените его, вы никогда не измените пароль. Вот почему мы его хэшируем, а не шифруем. Если вам нужно сравнить входной пароль с сохраненным паролем, вы хешируете вход и сравниваете хеши. Если вы зашифруете пароль, любой, у кого есть ключ, сможет его расшифровать и увидеть. Это небезопасно
Себастьян Габриэль Винчи
4
uuid.uuid4 (). hex меняется каждый раз, когда он создается. Как вы собираетесь сравнивать пароль для проверки, если вы не можете вернуть тот же uuid?
LittleBobbyTables
3
@LittleBobbyTables, я думаю salt, хранится в базе данных и соленый хешированный пароль тоже.
клемтой
70

Основываясь на других ответах на этот вопрос, я реализовал новый подход, используя bcrypt.

Зачем использовать bcrypt

Если я правильно понимаю, аргумент использовать bcryptболее SHA512, что bcryptпредназначено быть медленным. bcryptтакже есть возможность настроить, насколько медленным вы хотите, чтобы он был при генерации хешированного пароля в первый раз:

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

Медленная работа желательна, потому что если злоумышленник получит в свои руки таблицу, содержащую хешированные пароли, то их будет намного сложнее подобрать.

Реализация

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)

Примечания

Мне удалось довольно легко установить библиотеку в системе Linux, используя:

pip install py-bcrypt

Однако у меня было больше проблем с установкой его в моих системах Windows. Похоже, нужен патч. См. Этот вопрос о переполнении стека: установка py-bcrypt на win 7 64bit python

Крис Датроу
источник
4
12 - значение по умолчанию для gensalt
Ахмед Хегази 01
2
Согласно pypi.python.org/pypi/bcrypt/3.1.0 , максимальная длина пароля для bcrypt составляет 72 байта. Любые другие символы игнорируются. По этой причине они рекомендуют сначала хешировать с помощью криптографической хеш-функции, а затем кодировать хеш с помощью base64 (подробности см. По ссылке). Боковое замечание: похоже, py-bcryptэто старый пакет pypi, который с тех пор был переименован в bcrypt.
балу
48

Разумно не писать криптовалюту самостоятельно, а использовать что-то вроде passlib: https://bitbucket.org/ecollins/passlib/wiki/Home

Легко испортить безопасный код написания криптокода. Противно то, что с некриптографическим кодом вы часто сразу же замечаете, что он не работает, так как ваша программа вылетает. В то время как с криптокодом вы часто узнаете об этом только тогда, когда уже слишком поздно, и ваши данные были скомпрометированы. Поэтому я думаю, что лучше использовать пакет, написанный кем-то другим, кто разбирается в предмете, и который основан на протоколах, протестированных в бою.

Также в passlib есть несколько приятных функций, которые упрощают использование, а также упрощают переход на новый протокол хеширования паролей, если старый протокол оказывается неработоспособным.

Также всего один раунд sha512 более уязвим для атак по словарю. sha512 разработан, чтобы быть быстрым, и на самом деле это плохо при попытке безопасно хранить пароли. Другие люди долго и усердно думали обо всех проблемах такого рода, так что вам лучше воспользоваться этим.

MD
источник
5
Я полагаю, что совет использовать библиотеки crypo хорош, но OP уже использует hashlib, криптографическую библиотеку, которая также входит в стандартную библиотеку Python (в отличие от passlib). Я бы продолжал использовать hashlib, если бы я был в ситуации с OP.
dgh 08
18
@dghubble hashlibпредназначен для криптографических хеш-функций. passlibпредназначен для безопасного хранения паролей. Это не одно и то же (хотя многие люди, кажется, так думают ... а потом пароли их пользователей взламываются).
Брендан Лонг
3
На случай, если кому-то интересно: passlibгенерирует свою собственную соль, которая хранится в возвращаемой хэш-строке (по крайней мере, для некоторых схем, таких как BCrypt + SHA256 ), поэтому вам не нужно об этом беспокоиться.
z0r
22

Чтобы это работало в Python 3, вам необходимо, например, кодировать UTF-8:

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

В противном случае вы получите:

Traceback (последний вызов последним):
файл "", строка 1, в
hashed_password = hashlib.sha512 (пароль + соль) .hexdigest ()
TypeError: объекты Unicode должны быть закодированы перед хешированием

Уэйн
источник
7
Нет. Не используйте функцию хеширования sha для хеширования паролей. Используйте что-то вроде bcrypt. Причину смотрите в комментариях к другим вопросам.
josch
11

Начиная с Python 3.4, hashlibмодуль в стандартной библиотеке содержит функции вывода ключей, которые «предназначены для безопасного хеширования паролей». .

Поэтому используйте один из них, например hashlib.pbkdf2_hmac, с солью, созданной с использованием 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')

Обратите внимание, что:

  • Использование 16-байтовой соли и 100000 итераций PBKDF2 соответствуют минимальным числам, рекомендованным в документации Python. Дальнейшее увеличение количества итераций замедлит вычисление хэшей и, следовательно, сделает их более безопасными.
  • os.urandom всегда использует криптографически безопасный источник случайности
  • hmac.compare_digest, используемый в is_correct_password, в основном просто ==оператор для строк, но без возможности короткого замыкания, что делает его невосприимчивым к атакам по времени. Это, вероятно, на самом деле не дает никакого дополнительного значения безопасности , но это тоже не повредит, поэтому я пошел дальше и использовал его.

Теорию о том, что делает хороший хэш пароля, и список других функций, подходящих для хеширования паролей, см. На https://security.stackexchange.com/q/211/29805 .

Марк Эмери
источник
10

passlib кажется полезным, если вам нужно использовать хэши, хранящиеся в существующей системе. Если у вас есть контроль над форматом, используйте современный хеш, например bcrypt или scrypt. В настоящее время кажется, что bcrypt намного проще использовать из python.

passlib поддерживает bcrypt и рекомендует установить py-bcrypt в качестве бэкэнда: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

Вы также можете использовать py-bcrypt напрямую, если не хотите устанавливать passlib. В файле readme есть примеры базового использования.

см. также: Как использовать scrypt для генерации хэша для пароля и соли в Python

Террел Шамуэй
источник
6

Я не хочу восстанавливать старую ветку, но ... любой, кто хочет использовать современное безопасное решение, используйте argon2.

https://pypi.python.org/pypi/argon2_cffi

Он выиграл соревнование по хешированию паролей. ( https://password-hashing.net/ ) Его проще использовать, чем bcrypt, и он более безопасен, чем bcrypt.

нагыльзы
источник
0

Сначала импорт: -

import hashlib, uuid

Затем измените свой код в соответствии с этим в своем методе:

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

Затем передайте эту соль и uname в запрос sql базы данных, ниже логин - имя таблицы:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"
Sheetal Jha
источник
uname = request.form ["uname"] pwd = request.form ["pwd"] salt = hashlib.md5 (pwd.encode ()) Затем передайте эту соль и uname в запрос sql вашей базы данных, ниже логин - имя таблицы : - sql = "вставить в значения входа ('" + uname + "', '" + email + "', '" + salt.hexdigest () + "')"
Sheetal Jha
-1, потому что md5 очень быстр, что делает использование одной итерации md5 плохим выбором для функции хеширования паролей.
Марк Эмери