Сіль та хеш пароля в 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 закодував сіль, щоб я міг робити з нею сильні операції, не турбуючись про дивні проблеми. Чи буде версія байтів працювати як рядок? Якщо це так, то мені не потрібно також base64 кодувати t_sha.digest (). Можливо, я б не врятував хешований пароль і сіль разом лише тому, що це здається трохи складнішим і трохи менш читабельним.
Кріс Датроу,

Якщо ви використовуєте Python 2.x, тоді байт-об'єкт буде чудово працювати як рядок. Python не встановлює жодних обмежень щодо того, що ви можете мати у рядку. Однак те саме може не застосовуватися, якщо ви передаєте рядок до будь-якого зовнішнього коду, такого як база даних. Python 3.x розрізняє типи байтів і рядки, тому в такому випадку ви не хочете використовувати рядкові операції з сіллю.
Дункан

4
Я не можу сказати вам, як це зробити на python, але звичайний SHA-512 - поганий вибір. Використовуйте повільний хеш, такий як PBKDF2, bcrypt або scrypt.
CodesInChaos

Побічна примітка: Я б не радив використовувати UUID як джерело криптографічної випадковості. Так, реалізація, що використовується CPython, є криптографічно безпечною , але це не продиктовано специфікацією Python, ані специфікацією UUID , і вразливі реалізації існують . Якщо ваша кодова база запускається за допомогою реалізації Python без захищених UUID4, ви послабите свою безпеку. Це може бути малоймовірним сценарієм, але використовувати secretsнатомість нічого не варто .
Mark Amery

Відповіді:


49

EDIT: Ця відповідь неправильна. Одна ітерація 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він зберігається в базі даних і солоний хешований пароль теж.
clemtoy

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 - значення за замовчуванням для генсальту
Ахмед Хеґазі

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 створений швидко, і це насправді погано при спробі надійно зберігати паролі. Інші люди довго і довго думали над усіма подібними проблемами, тож краще скористайтеся цим.


5
Я вважаю, що поради щодо використання бібліотек crypo є хорошими, але OP вже використовує hashlib, криптотеку, яка також є у стандартній бібліотеці Python (на відміну від passlib). Я б продовжував використовувати hashlib, якби потрапив у ситуацію з ОП.
dgh

18
@dghubble hashlib- для криптографічних хеш-функцій. passlibпризначений для надійного зберігання паролів. Вони не одне і те ж (хоча багато людей, здається, так думають .. і тоді їх паролі користувачів зламаються).
Brendan Long

3
Якщо хтось задається питанням: passlibгенерує власну сіль, яка зберігається у повернутому хеш-рядку (принаймні для певних схем, таких як BCrypt + SHA256 ), - тому вам не потрібно про це турбуватися.
z0r

22

Щоб це працювало в Python 3, вам потрібно буде кодувати UTF-8, наприклад:

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

Інакше ви отримаєте:

Відстеження (останній останній дзвінок):
Файл "", рядок 1, у
hashed_password = hashlib.sha512 (пароль + сіль) .hexdigest ()
TypeError: Об'єкти Unicode потрібно кодувати перед хешуванням


7
Ні. Не використовуйте жодну функцію sha hash для хешування паролів. Використовуйте щось на зразок bcrypt. Дивіться коментарі до інших запитань з причини.
josch

12

Станом на 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()+"')"

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 поганим вибором для функції хешування паролів.
Mark Amery
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.