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

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

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

Примечание: я использую безопасную для URL версию b64encode по привычке.

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())

11 ответов

Решение

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


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

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

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

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

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

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

# The '12' is the number the 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 hased password. Useing bcrypt, the salt is saved into the hash itself
    return bcrypt.checkpw(plain_text_password, hashed_password)

Заметки

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

pip install py-bcrypt

Тем не менее, у меня было больше проблем при установке его на мои системы Windows. Похоже, нужен патч. Посмотрите на этот вопрос Stackru: установка py-bcrypt на win 7 64bit Python

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

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

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

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

Начиная с 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.

Чтобы это работало в 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-объекты должны быть закодированы перед хэшированием

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

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

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

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

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

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

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

Лично я бы использовал модуль Swiftcrypt.

      pip install swiftcrypt

Он имеет множество функций по соображениям безопасности.

      import swiftCrypt

password= "coolPassword here"

salt = swiftCrypt.Salts().generate_salt(14)
hashedPass = swiftCrypt.Hash().hash_password(password,salt,"sha256")

print(hashedPass)

Вы можете сгенерировать соль произвольной длины. Если вам нужна случайная длина, просто оставьте ее пустой.

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

Затем вы можете проверить пароль следующим образом:

      verifiedPass = swiftCrypt.Checker().verify_password(password, hashedPass, salt,"sha256")
if verifiedPass == True:
    print("Password is correct!")
else:
    print("Password is incorrect!")                             

The verify_passwordМетод принимает пароль, введенный пользователем, сохраненный хешированный пароль, соль, использованную для создания пароля, и используемый алгоритм.

Я делал то же самое в NodeJs раньше:

       echo "console.log(require('crypto').createHmac('sha256', 'salt').update('password').digest('hex'))" | node

это эквивалентно в python:

      python3 -c 'import hashlib;import base64;import hmac;print(hmac.new(b"salt", "password".encode(), hashlib.sha256).hexdigest())'

И эквивалентная команда оболочки:

      echo -n "password" | openssl sha256 -hmac "salt"

Вот мое предложение:

      for i in range(len(rserver.keys())):
    salt = uuid.uuid4().hex
    print(salt)
    mdp_hash = rserver.get(rserver.keys()[i])
    rserver.set(rserver.keys()[i], hashlib.sha256(salt.encode() + mdp_hash.encode()).hexdigest() + salt)
    rsalt.set(rserver.keys()[i], salt)

Во-первых, импорт:-

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()+"')"
Другие вопросы по тегам