Шифрование / хеширование паролей в виде простого текста в базе данных
Я унаследовал только что обнаруженное веб-приложение, которое хранит более 300000 имен пользователей и паролей в виде простого текста в базе данных SQL Server. Я понимаю, что это очень плохая вещь ™.
Зная, что мне придется обновить процессы обновления логина и пароля для шифрования / дешифрования и с минимальным воздействием на остальную часть системы, что бы вы порекомендовали как лучший способ удаления паролей в виде простого текста из базы данных?
Любая помощь приветствуется.
Изменить: Извините, если мне было неясно, я хотел спросить, какова будет ваша процедура для шифрования / хеширования паролей, а не конкретные методы шифрования / хеширования.
Должен ли я просто:
- Сделайте резервную копию БД
- Обновить логин / обновить пароль
- Через несколько часов просмотрите все записи в таблице пользователей, хэширующие пароль и заменяющие каждую.
- Тест, чтобы убедиться, что пользователи все еще могут войти / обновить пароли
Я думаю, что мое беспокойство связано с большим количеством пользователей, поэтому я хочу убедиться, что я делаю это правильно.
16 ответов
Я полагаю, вам нужно будет добавить в базу данных столбец для зашифрованного пароля, а затем запустить пакетное задание для всех записей, которое получает текущий пароль, шифрует его (поскольку другие упоминали, что хеш, такой как md5, является довольно стандартным редактированием: но не должен использовать самостоятельно - см. другие ответы для хороших обсуждений), сохраняет его в новом столбце и проверяет, что все прошло гладко.
Затем вам нужно будет обновить свой интерфейс, чтобы хэшировать введенный пользователем пароль во время входа в систему и проверять, что это против сохраненного хэша, вместо проверки Plaintext-vs-Plaintext.
Мне кажется разумным оставить на некоторое время обе колонки на месте, чтобы убедиться, что ничего сложного не произошло, прежде чем в конечном итоге полностью удалить пароли в виде открытого текста.
Не забывайте также, что при каждом обращении к паролю код должен быть изменен, например, запросы на изменение пароля / напоминание. Конечно, вы потеряете возможность отправлять забытые пароли по электронной почте, но это не так уж плохо. Вместо этого вам придется использовать систему сброса пароля.
Редактирование: В заключение я бы хотел избежать ошибки, которую я совершил при первой попытке зайти на сайт безопасного входа в систему на испытательном стенде:
При обработке пароля пользователя учитывайте, где происходит хеширование. В моем случае хеш вычислялся с помощью PHP-кода, запущенного на веб-сервере, но пароль был передан на страницу с компьютера пользователя в виде открытого текста! Это было нормально (иш) в среде, в которой я работал, так как это было внутри системы https в любом случае (уни сеть). Но в реальном мире, я думаю, вы захотите хешировать пароль до того, как он покинет пользовательскую систему, используя javascript и т. Д., А затем передать хеш на ваш сайт.
РЕДАКТИРОВАТЬ (2016): используйте Argon2, scrypt, bcrypt или PBKDF2 в указанном порядке предпочтения. Используйте настолько большой коэффициент замедления, насколько это возможно для вашей ситуации. Использовать проверенную существующую реализацию. Убедитесь, что вы используете правильную соль (хотя библиотеки, которые вы используете, должны убедиться в этом за вас).
Когда вы хэшируете пароли, не используйте PLAIN MD5.
Используйте PBKDF2, что в основном означает использование случайной соли для предотвращения атак радужных таблиц и повторение (повторное хеширование) достаточное количество раз, чтобы замедлить хеширование - не столько, что ваше приложение занимает слишком много времени, но и достаточно, чтобы злоумышленник перебил зверя. большое количество разных паролей заметит
Из документа:
- Выполните итерацию как минимум 1000 раз, предпочтительно больше - время вашей реализации, чтобы увидеть, сколько итераций для вас выполнимо.
- 8 байт (64 бита) соли достаточно, и случайное не должно быть безопасным (соль не зашифрована, мы не беспокоимся, что кто-то это догадается).
- Хороший способ применить соль при хешировании - это использовать HMAC с вашим любимым алгоритмом хеширования, используя пароль в качестве ключа HMAC и соль в качестве текста для хэширования (см. Этот раздел документа).
Пример реализации на Python с использованием SHA-256 в качестве безопасного хэша:
РЕДАКТИРОВАТЬ: как упомянул Эли Коллинз, это не реализация PBKDF2. Вы должны предпочесть реализации, которые придерживаются стандарта, такого как PassLib.
from hashlib import sha256
from hmac import HMAC
import random
def random_bytes(num_bytes):
return "".join(chr(random.randrange(256)) for i in xrange(num_bytes))
def pbkdf_sha256(password, salt, iterations):
result = password
for i in xrange(iterations):
result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt
return result
NUM_ITERATIONS = 5000
def hash_password(plain_password):
salt = random_bytes(8) # 64 bits
hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)
# return the salt and hashed password, encoded in base64 and split with ","
return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip()
def check_password(saved_password_entry, plain_password):
salt, hashed_password = saved_password_entry.split(",")
salt = salt.decode("base64")
hashed_password = hashed_password.decode("base64")
return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)
password_entry = hash_password("mysecret")
print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA=
check_password(password_entry, "mysecret") # returns True
Основная стратегия состоит в том, чтобы использовать функцию получения ключа для "хеширования" пароля с помощью соли. Соль и результат хеширования сохраняются в базе данных. Когда пользователь вводит пароль, соль и его ввод хэшируются одинаково и сравниваются с сохраненным значением. Если они совпадают, пользователь проходит проверку подлинности.
Дьявол кроется в деталях. Во-первых, многое зависит от выбранного алгоритма хеширования. Алгоритм получения ключей, такой как PBKDF2, основанный на хэш-коде аутентификации сообщений, делает "вычислительно неосуществимым" поиск входных данных (в данном случае пароля), которые будут выдавать заданный вывод (что злоумышленник обнаружил в базе данных).).
Атака предварительно вычисленного словаря использует предварительно вычисленный индекс, или словарь, от хеш-выводов до паролей. Хеширование выполняется медленно (или, как предполагается, в любом случае), поэтому злоумышленник хеширует все вероятные пароли один раз и сохраняет результат, проиндексированный таким образом, что при наличии хэша он может найти соответствующий пароль. Это классический обмен пространства на время. Поскольку списки паролей могут быть огромными, есть способы настроить компромисс (например, радужные таблицы), чтобы злоумышленник мог отказаться от небольшой скорости, чтобы сэкономить много места.
Атаки перед вычислением предотвращаются с помощью "криптографической соли". Это некоторые данные, которые хэшируются с паролем. Это не должно быть секретом, это просто должно быть непредсказуемо для данного пароля. Для каждого значения соли злоумышленнику потребуется новый словарь. Если вы используете один байт соли, злоумышленнику потребуется 256 копий их словаря, каждый из которых будет создан с использованием другой соли. Сначала он использовал соль для поиска правильного словаря, затем он использовал вывод хеша для поиска пригодного для использования пароля. Но что, если вы добавите 4 байта? Теперь ему нужно 4 миллиарда экземпляров словаря. При использовании достаточно большой соли атака по словарю исключается. На практике от 8 до 16 байтов данных из генератора случайных чисел криптографического качества являются хорошей солью.
С предварительным вычислением вне таблицы злоумышленник вычисляет хеш при каждой попытке. Время, необходимое для поиска пароля, полностью зависит от того, сколько времени потребуется для хэширования кандидата. Это время увеличивается за счет итерации хэш-функции. Число итераций обычно является параметром функции вывода ключа; сегодня многие мобильные устройства используют от 10000 до 20 000 итераций, а сервер может использовать 100 000 и более. (Алгоритм bcrypt использует термин "фактор стоимости", который является логарифмической мерой требуемого времени.)
Следуйте советам Ксан, чтобы некоторое время хранить текущий столбец паролей, поэтому, если что-то пойдет не так, вы можете откатить быстро и легко.
Что касается шифрования ваших паролей:
- использовать соль
- использовать алгоритм хеширования, предназначенный для паролей (т. е. он медленный)
См. Томас Птачек " Достаточно с радужными таблицами: что нужно знать о безопасных схемах паролей" для получения более подробной информации.
Это была моя проблема пару недель назад. Мы развертывали крупный проект MIS в 975 различных географических точках, где наше собственное хранилище учетных данных будет использоваться в качестве средства проверки подлинности для различного набора уже реализованных и используемых приложений. Мы уже предоставили сервис аутентификации на основе REST и SOAP, но заказчик настаивал на том, чтобы иметь возможность получить доступ к хранилищу учетных данных пользователя из других приложений, просто подключившись к БД и просматривая связанную таблицу или представление только для чтения. Вздох... (это очень связанное плохое дизайнерское решение - предмет другого вопроса).
Это заставило нас сесть и преобразовать нашу подсоленную и итеративно хешированную схему хранения паролей в спецификацию и предоставить несколько разных языковых реализаций для легкой интеграции.
Мы кратко назвали его "Достаточно безопасные хешированные пароли" или " FSHP". Реализовал его в Python, Ruby, PHP5 и выпустил в Public Domain. Доступно для потребления, разветвления, разжигания или плевки на GitHub на http://github.com/bdd/fshp
FSHP - это реализация хеширования пароля с итеративным хэшированием.
Принцип проектирования аналогичен спецификации PBKDF1 в RFC 2898 (он же: PKCS #5: спецификация криптографии на основе пароля, версия 2.0). FSHP позволяет выбирать длину соли, количество итераций и основную криптографическую хеш-функцию среди SHA-1 и SHA-2. (256, 384, 512). Самоопределение мета-префикса в начале каждого вывода делает его переносимым, позволяя потребителю выбирать собственный базовый уровень безопасности хранения пароля.
БЕЗОПАСНОСТЬ:
FSHP1 по умолчанию использует 8-байтовые соли с 4096 итерациями хеширования SHA-256. - 8-байтовая соль делает атаки на радужные таблицы нецелесообразными, умножая необходимое пространство на 2^64. - 4096 итераций приводят к довольно дорогим атакам методом перебора. - На момент выпуска этой версии не было никаких известных атак на SHA-256 для нахождения коллизий с вычислительным усилием менее 2^128 операций.
Реализации:
- Python: протестировано с 2.3.5 (w / hashlib), 2.5.1, 2.6.1
- Ruby: протестировано с 1.8.6
- PHP5: протестировано с 5.2.6
Каждый может создать недостающие языковые реализации или откорректировать текущие.
ОСНОВНЫЕ ОПЕРАЦИИ (с Python):
>>> fsh = fshp.crypt('OrpheanBeholderScryDoubt')
>>> print fsh
{FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g==
>>> fshp.validate('OrpheanBeholderScryDoubt', fsh)
True
НАСТРОЙКА КРИПТА:
Давайте ослабим нашу схему хеширования паролей. - Уменьшить длину соли со значения по умолчанию с 8 до 2. - Уменьшить цикл итерации со значения по умолчанию с 4096 до 10. - Выбрать FSHP0 с SHA-1 в качестве основного алгоритма хеширования.
>>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0)
>>> print fsh
{FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ==
Я думаю, что вы должны сделать следующее:
- Создайте новый столбец с именем HASHED_PASSWORD или что-то подобное.
- Измените ваш код так, чтобы он проверял оба столбца.
- Постепенно переносите пароли из нехешированной таблицы в хешированную. Например, когда пользователь входит в систему, перенесите его или ее пароль автоматически в хэшированный столбец и удалите не хэшированную версию. Все вновь зарегистрированные пользователи будут иметь хешированные пароли.
- После нескольких часов вы можете запустить скрипт, который мигрирует n пользователей за раз
- Если у вас больше не осталось нешифрованных паролей, вы можете удалить столбец старого пароля (возможно, вы не сможете этого сделать, это зависит от используемой вами базы данных). Также вы можете удалить код для обработки старых паролей.
- Вы сделали!
В целях аутентификации вы должны избегать хранения паролей с использованием обратимого шифрования, то есть вы должны только хранить хэш пароля и проверять хэш пароля, предоставленного пользователем, против сохраненного вами хэша. Однако у этого подхода есть недостаток: он уязвим к атакам радужных таблиц, если злоумышленник овладеет базой данных вашего хранилища паролей.
Что вы должны сделать, это сохранить хэши предварительно выбранной (и секретной) соли + пароль. Т.е. объединить соль и пароль, хэшировать результат и сохранить этот хеш. При аутентификации сделайте то же самое - объедините ваше солт-значение и предоставленный пользователем пароль, хеш, затем проверьте на равенство. Это делает атаки радужного стола неосуществимыми.
Конечно, если пользователь отправляет пароли по сети (например, если вы работаете над веб-приложением или клиент-серверным приложением), вам не следует отправлять пароль в виде открытого текста, поэтому вместо хранения хеша (salt + пароль) вы должны хранить и проверять хеш (соль + хеш (пароль)), и ваш клиент должен предварительно хешировать предоставленный пользователем пароль и отправлять его по сети. Это также защищает пароль вашего пользователя, если пользователь (как и многие другие) повторно использует один и тот же пароль для нескольких целей.
Как уже упоминалось, вы не хотите расшифровывать, если можете помочь. Стандартное решение заключается в шифровании с использованием одностороннего хэша, а затем, когда пользователь входит в хэш, вводит свой пароль для сравнения.
В противном случае вам придется использовать надежное шифрование для шифрования, а затем расшифровать. Я бы порекомендовал это только в том случае, если политические причины веские (например, ваши пользователи привыкли вызывать службу поддержки, чтобы получить свой пароль, и у вас есть сильное давление сверху, чтобы это не менять). В этом случае я бы начал с шифрования, а затем начал строить экономическое обоснование для перехода к хешированию.
- Зашифруйте, используя что-то вроде MD5, закодируйте его как шестнадцатеричную строку
- Тебе нужна соль; в вашем случае имя пользователя может быть использовано в качестве соли (оно должно быть уникальным, имя пользователя должно быть самым уникальным из доступных значений;-)
- используйте поле старого пароля для хранения MD5, но пометьте MD5 (например, "MD5:687A878...."), чтобы старые (обычный текст) и новые (MD5) пароли могли сосуществовать
- измените процедуру входа в систему, чтобы проверить MD5, если есть MD5, и простой пароль в противном случае
- измените функции "изменить пароль" и "новый пользователь", чтобы создавать только пароли MD5
- Теперь вы можете запустить пакетное задание преобразования, которое может занять столько времени, сколько необходимо
- после запуска преобразования удалите legacy-support
Шаг 1: Добавить зашифрованное поле в базу данных
Шаг 2: Измените код, чтобы при смене пароля обновлялись оба поля, но при входе в систему по-прежнему использовалось старое поле.
Шаг 3: Запустите скрипт, чтобы заполнить все новые поля.
Шаг 4. Измените код, чтобы при входе в систему использовалось новое поле, а при смене паролей обновление старого поля прекращалось.
Шаг 5: Удалить незашифрованные пароли из базы данных.
Это должно позволить вам выполнить переход без прерывания для конечного пользователя.
Кроме того: что-то, что я хотел бы сделать, это назвать поле новой базы данных чем-то, что совершенно не связано с паролем, например "LastSessionID", или чем-то скучным. Затем вместо удаления поля пароля просто заполните хэши случайных данных. Затем, если ваша база данных когда-либо будет взломана, они могут потратить все свое время, пытаясь расшифровать поле "пароль".
Это может на самом деле ничего не достичь, но это забавно думать о том, что кто-то сидит там и пытается выяснить бесполезную информацию
MD5 и SHA1 продемонстрировали небольшую слабость (два слова могут привести к одному и тому же хешу), поэтому рекомендуется использовать SHA256-SHA512 / итеративные хеши для хеширования пароля.
Я написал бы небольшую программу на языке, на котором написано приложение, которое генерирует случайную соль, уникальную для каждого пользователя, и хэш пароля. Причина, по которой я склонен использовать тот же язык, что и при проверке, заключается в том, что разные криптографические библиотеки могут делать вещи немного по-разному (например, заполнение), поэтому использование одной и той же библиотеки для генерации хеша и проверки его устраняет этот риск. Это приложение также может затем проверить логин после обновления таблицы, если вы хотите, так как оно все еще знает пароль в виде простого текста.
- Не используйте MD5/SHA1
- Генерация хорошей случайной соли (многие криптотеки имеют солевой генератор)
- Итеративный алгоритм хеширования, как рекомендуется orip
- Убедитесь, что пароли не передаются в виде простого текста по проводам
Как и во всех решениях по безопасности, здесь есть компромиссы. Если вы хэшируете пароль, который, вероятно, является вашим самым простым ходом, вы не сможете предложить функцию восстановления пароля, которая возвращает исходный пароль, и ваши сотрудники не смогут найти пароль пользователя, чтобы получить доступ к своей учетной записи.
Вы можете использовать симметричное шифрование, которое имеет свои недостатки безопасности. (Если ваш сервер скомпрометирован, симметричный ключ шифрования также может быть скомпрометирован).
Вы можете использовать шифрование с открытым ключом и запускать поиск пароля / обслуживание клиентов на отдельном компьютере, который хранит закрытый ключ в изоляции от веб-приложения. Это наиболее безопасный вариант, но он требует архитектуры с двумя компьютерами и, возможно, промежуточного брандмауэра.
Я хотел бы предложить одно улучшение для великолепного примера Python, опубликованного Orip. Я бы переопределил random_bytes
функция быть:
def random_bytes(num_bytes):
return os.urandom(num_bytes)
Конечно, вам придется импортировать os
модуль. os.urandom
Функция предоставляет случайную последовательность байтов, которую можно безопасно использовать в криптографических приложениях. См. Справочную справку этой функции для получения дополнительной информации.
Для хеширования пароля вы можете использовать функцию HashBytes. Возвращает varbinary, поэтому вам нужно создать новый столбец, а затем удалить старый varchar.
подобно
ALTER TABLE users ADD COLUMN hashedPassword varbinary(max);
ALTER TABLE users ADD COLUMN salt char(10);
--Generate random salts and update the column, after that
UPDATE users SET hashedPassword = HashBytes('SHA1',salt + '|' + password);
Затем вы изменяете код для проверки пароля, используя запрос типа
SELECT count(*) from users WHERE hashedPassword =
HashBytes('SHA1',salt + '|' + <password>)
где <пароль> - это значение, введенное пользователем.
Я не эксперт по безопасности, но я полагаю, что текущая рекомендация - использовать bcrypt/blowfish или вариант SHA-2, а не MD5 / SHA1.
Вероятно, вам также следует подумать о полном аудите безопасности.