Создание сервера с нуля или использование легковесного фреймворка наделяет огромной властью. С этой властью приходит и ответственность, в частности, ответственность за безопасное хранение паролей пользователей.
Непонимание последствий для безопасности хранения паролей может привести к разрушительным нарушениям и утечкам. Если вы создаете приложение и вам необходимо хранить учетные данные пользователя, изучите хэш-функции.
Могу ли я хранить пароли в виде простого текста?
Чтобы продемонстрировать потенциальную опасность, давайте предположим, что мы не хэшируем пароли на примере фейкового веб-сайта LoveMatchingToday. Скорее всего, когда хакер или недовольный сотрудник получает доступ к базе данных LoveMatchingToday, они загружают все имена пользователей и пароли:
- user.one@gmail.com – somePa$$wordHere
- user.two@hotmail.com – otherPlainTextPass
Теперь злоумышленник может перейти на другие сайты, и, поскольку большинство людей используют одни и те же пароли на разных сайтах, они могут взломать и другие системы.
Наилучшее решение — хеширование
Хеш-функция (или, более конкретно, в нашем случае, функция получения ключа) детерминистически создает надежный ключ из пароля. Поскольку хеши являются односторонними, злоумышленник не может воссоздать незашифрованный пароль из хеша. Теперь злоумышленник увидит что-то подобное в базе данных:
- user.one@gmail.com – cab864878af008fbc550087940ffacdb79a7f82201725e3350e25d6cfbdd4255
- user.two@hotmail.com – 42a7fd2b639d18b3aba5db8504d4530f1f1ab58ab9615414b7629d6ec5c157b8
Они не смогут использовать хеш для входа в другие системы, потому что у них нет доступа к исходному паролю.
В Python существует библиотека Bcrypt, которая является функцией получения сильных ключей, которую можно использовать в производственных системах:
import bcrypt bcrypt.hashpw('userPlainTextPassword'.encode(), bcrypt.gensalt())
Радужные таблицы и соли
В приведенном выше фрагменте кода, вы могли задаться вопросом, что делает функция gensalt(). Если бы мы хешировали пароли без солей, злоумышленник мог бы провести атаку с помощью радужной таблицы, чтобы найти исходный текст.
Соль — это случайная строка данных, хэшированная вместе с паролем, для сохранения уникальности результата хеширования. Соли следует воссоздавать каждый раз, когда новый пароль сохраняется, а соль сохраняется вместе с хешированным результатом, чтобы ее можно было использовать снова для сравнения. Такие библиотеки, как bcrypt, достаточно умны, чтобы хранить соль в результирующей строке, поэтому разработчикам не нужно выполнять дополнительную работу.
Например, допустим, что LoveMatchingToday обновился и начал хешировать пароли, но не содержал уникальных солей. Злоумышленник может иметь предварительно вычисленную таблицу хэшей:
- aab864878af008fbc550087940ffacdb79a7f82201725e3350e25d6cfbdd425f = password123
- afg3683232297323f2f0087940ffacdb79a7f8284723732350e25d6cfbdd4cccc = shadowTheHedgehog1234
Затем он может проверить каждый найденный хэш и посмотреть, совпадает ли хэш с записью в его таблице. Если это так, он может эффективно «развернуть» хэш и получить оригинальный текст.
По этой причине нам нужно «посолить» пароли. К счастью, Bcrypt обрабатывает засолку автоматически. Однако, ради обучения давайте предположим, что этого не случилось. Если бы такого не произошло, наш псевдокод выглядел бы примерно так:
# Save new password salt = creatRandomSalt() hashedPassword = hash(newPassword.concat(salt)) database.save(hashedPassword, salt) # Check password hashedPassword, salt = database.GetUserCredentals() passwordInput = userInput if hash(passwordInput.concat(salt)) == hashedPassword: login() else: failure()
Однако, поскольку Bcrypt автоматически сохраняет соль с хэшированным результатом в формате «{соль}{хэш}», мы можем просто использовать следующий код:
import bcrypt # password = userInput hashAndSalt = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) # save "hashAndSalt" in data base # To check: # password = userInput valid = bcrypt.checkpw(password.encode(), hashAndSalt)
Спасибо за прочтение!