Как bcrypt может иметь встроенные соли?

617

В статье Coda Hale «Как безопасно хранить пароль» утверждается, что:

В bcrypt встроены соли для предотвращения атак с радужного стола.

Он цитирует эту статью , в которой говорится, что в реализации OpenBSD bcrypt:

OpenBSD генерирует 128-битную соль bcrypt из ключевого потока arcfour (arc4random (3)), заполненного случайными данными, которые ядро ​​собирает по времени устройства.

Я не понимаю, как это может работать. В моей концепции соли:

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

Когда я использую Devise (менеджер входа в Rails) с bcrypt, в базе данных нет столбца соли, поэтому я запутался. Если соль случайная и нигде не хранится, как мы можем надежно повторить процесс хеширования?

Короче говоря, как bcrypt может иметь встроенные соли ?

Натан Лонг
источник

Ответы:

789

Это bcrypt:

Генерация случайной соли. Фактор «стоимость» был предварительно настроен. Собери пароль.

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

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

Bcrypt работает очень похоже на более традиционные схемы, основанные на таких алгоритмах, как PBKDF2. Основным отличием является использование производного ключа для шифрования известного простого текста; другие схемы (разумно) предполагают, что функция вывода ключа необратима, и сохраняют полученный ключ напрямую.


Хранящийся в базе данных bcryptхеш может выглядеть примерно так:

$ 2a $ 10 $ vI8aWBnW3fID.ZQ4 / zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

На самом деле это три поля, разделенные символом «$»:

  • 2aопределяет bcryptверсию алгоритма, которая была использована.
  • 10фактор стоимости; 2 10 10 итераций функции получения ключа (кстати, этого недостаточно. Я бы рекомендовал стоимость 12 или более).
  • vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTaявляется солью и зашифрованным текстом, объединенным и закодированным в модифицированной Base-64. Первые 22 символа декодируются до 16-байтового значения для соли. Остальные символы - это зашифрованный текст, который нужно сравнить для аутентификации.

Этот пример взят из документации по реализации Ruby в Coda Hale.

Эриксон
источник
7
Не могли бы вы подробнее рассказать, почему стоимостного коэффициента 10 будет недостаточно? В Grails я заметил, что 10 - это значение по умолчанию для коэффициента затрат / логарифмических циклов для bcrypt, поэтому, возможно, стоит обновить его, учитывая ваше предложение.
pm_labs
57
Коэффициент стоимости для bcrypt является экспоненциальным, или, скорее, коэффициент стоимости 10 означает 2 ^ 10 раундов (1024), коэффициент стоимости 16 будет означать 2 ^ 16 раундов (65536). Естественно, что это займет 5-10 секунд. Это должно занять примерно в 64 раза больше времени, чем фактор стоимости в 10 раз. Чтобы убрать другую дезинформацию, PHP-функция crypt использует библиотеку unix crypt, которая реализована в c.
Томасруттер
3
@TJChambers Это верно; если вы сможете установить пароль для учетной записи, вы сможете пройти аутентификацию. Хеширование пароля не предназначено для предотвращения этой атаки. Он предназначен для предотвращения аутентификации злоумышленника с доступом только для чтения к таблице паролей. Например, вы получаете резервную ленту с таблицей на ней.
Эриксон
8
@LobsterMan Нет, не совсем. Если бы вы могли хранить секрет, вы бы не использовали этот подход, вы бы просто сохранили пароль. Схемы аутентификации по паролю основаны на предположении, что злоумышленник обнаружил все, что вы знаете. Соль заключается в том, что каждый пароль должен быть атакован индивидуально. Вычислительные усилия, необходимые для тестирования паролей, регулируются итерациями. Если пользователи выбирают хорошие пароли, они будут в безопасности, даже если обнаружится соль. В некоторых случаях сокрытие соли может помочь кому-то с неправильным паролем, но я бы сначала поработал над качеством пароля.
Эриксон
1
@NLV Это строка, определенная в спецификации bcrypt:"OrpheanBeholderScryDoubt"
erickson
182

Я считаю, что эту фразу следовало бы сформулировать следующим образом:

bcrypt содержит соли, встроенные в сгенерированные хэши, чтобы предотвратить атаки радужных таблиц.

Сама bcryptутилита не поддерживает список солей. Скорее, соли генерируются случайным образом и добавляются к выходным данным функции, чтобы их потом запомнили (в соответствии с реализацией Javabcrypt ). Иными словами, «хеш», сгенерированный bcryptне просто хешем. Скорее, это хеш и соль объединены.

Адам Пейнтер
источник
20
ОК, поэтому я регистрируюсь на сайте и выбираю пароль "foo". Bcryptдобавляет случайную соль "akd2! *", в результате чего получается "fooakd2! *", которая хэшируется и сохраняется. Позже я пытаюсь войти с паролем "бар". Чтобы убедиться, что я прав, нужно хешировать "barakd2! *". Если соль была сгенерирована случайным образом для начала, откуда она знает, как добавить ее обратно в «bar» перед хэшированием и сравнением?
Натан Лонг
46
@ Натан: bcryptзнает, как извлечь соль обратно из сгенерированного вывода (который хранится в базе данных). Когда приходит время для аутентификации, bcryptразделяет исходный вывод на его хеш и солт-компоненты. Солевой компонент применяется к входящему паролю, введенному пользователем.
Адам Пейнтер
22
Чтобы ответить на комментарий Натана Лонга, хороший способ думать об этом состоит в том, что соли не должны быть секретными. Вот почему соль включена в вывод функции bcrypt, как один из ответов, указанных выше. Соль существует для предотвращения радужных таблиц, которые представляют собой списки общих паролей, или просто перебор, и т. Д. ... различных паролей, но хэшированные. Без соли хеш для пароля в базе данных A был бы таким же, как хеш для пароля в базе данных B. Salt просто изменяет значения хеш-функции, затрудняя тем, кто украл базу данных, расшифровывать (unhash) пароли.
Джозеф Астрахан
11
@ Натан, но может ли злоумышленник просто удалить известные соли во всех паролях, а затем создать таблицу с ними?
Оскар
3
Вот как я это понимаю: идея в том, что каждый пароль имеет уникальную соль. Соль включена в хэш пароля, так что хакер должен будет создать радужную таблицу для каждого пароля. Это займет огромное количество времени для умеренной базы данных. Все дело в том, чтобы замедлить атакующего и сделать бессмысленным грубое принуждение.
PVermeer
0

Чтобы сделать вещи еще яснее,

Регистрация / Направление входа ->

Пароль + соль шифруется ключом, сгенерированным из: стоимости, соли и пароля. мы называем это зашифрованное значение cipher text. затем мы добавляем соль к этому значению и кодируем ее с помощью base64. добавив стоимость к нему, и это полученная строка из bcrypt:

$2a$COST$BASE64

Это значение сохраняется в конце концов.

Что нужно сделать злоумышленнику, чтобы найти пароль? (другое направление <-)

В случае, если злоумышленник получит контроль над БД, он легко расшифрует значение base64 и сможет увидеть соль. соль не секрет. хотя это случайно. Тогда ему нужно будет расшифровать cipher text.

Что еще более важно: в этом процессе нет хэширования, скорее дорогостоящее шифрование процессора - дешифрование. поэтому радужные таблицы здесь менее актуальны.

jony89
источник
-2

Это из документации по интерфейсу PasswordEncoder от Spring Security,

 * @param rawPassword the raw password to encode and match
 * @param encodedPassword the encoded password from storage to compare with
 * @return true if the raw password, after encoding, matches the encoded password from
 * storage
 */
boolean matches(CharSequence rawPassword, String encodedPassword);

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

Встретить шаха
источник
Это не отвечает на вопрос вообще ... Это ничего не говорит о том, как bcrypt может иметь встроенные соли
spencer.sm