Может кто-нибудь объяснить, как BCrypt проверяет хэш?

Я использую C# и BCrypt.Net для хеширования своих паролей.

Например:

string salt = BCrypt.Net.BCrypt.GenerateSalt(6);
var hashedPassword = BCrypt.Net.BCrypt.HashPassword("password", salt);

//This evaluates to True. How? I'm not telling it the salt anywhere, nor
//is it a member of a BCrypt instance because there IS NO BCRYPT INSTANCE.
Console.WriteLine(BCrypt.Net.BCrypt.Verify("password", hashedPassword));
Console.WriteLine(hashedPassword);

Как BCrypt проверяет пароль с помощью хеша, если он нигде не сохраняет соль. Единственная идея, которая у меня есть, заключается в том, что это каким-то образом добавляет соль в конец хэша.

Это правильное предположение?


person Community    schedule 22.03.2011    source источник


Ответы (2)


Как BCrypt проверяет пароль с помощью хеша, если он нигде не сохраняет соль?

Ясно, что он не делает ничего подобного. Соль нужно где-то хранить.

Давайте поищем схемы шифрования паролей в Википедии. Из http://en.wikipedia.org/wiki/Crypt_%28Unix%29 :

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

Кроме того, ответ на ваш предыдущий вопрос по этому вопросу включена ссылка на исходный код. Соответствующий раздел исходного кода:

    StringBuilder rs = new StringBuilder();
    rs.Append("$2");
    if (minor >= 'a') {
        rs.Append(minor);
    }
    rs.Append('$');
    if (rounds < 10) {
        rs.Append('0');
    }
    rs.Append(rounds);
    rs.Append('$');
    rs.Append(EncodeBase64(saltBytes, saltBytes.Length));
    rs.Append(EncodeBase64(hashed,(bf_crypt_ciphertext.Length * 4) - 1));
    return rs.ToString();

Ясно, что возвращаемая строка представляет собой информацию о версии, за которой следует количество использованных циклов, за которым следует соль, закодированная как base64, а затем хеш, закодированный как base64.

person Eric Lippert    schedule 22.03.2011

Хэш BCrypt string выглядит следующим образом:

$2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
\__/\/ \____________________/\_____________________________/
 |   |        Salt                     Hash
 |  Cost
Version

Где

  • 2a: Идентификатор алгоритма (BCrypt, пароль в кодировке UTF8, завершенный нулем)
  • 10: коэффициент стоимости (210 = 1024 раунда)
  • Ro0CUfOqk6cXEKf3dyaM7O: Соль в кодировке OpenBSD-Base64 (22 символа, 16 байт)
  • hSCvnwM9s4wIX9JeLapehKK5YdLxKcm: Хэш в кодировке OpenBSD-Base64 (31 символ, 24 байта)

Изменить: я только что заметил, что эти слова точно подходят. я должен был поделиться:

$2a$10$TwentytwocharactersaltThirtyonecharacterspasswordhash
$==$==$======================-------------------------------

BCrypt действительно создает 24-байтовый двоичный хеш, используя 16-байтовую соль. Вы можете хранить двоичный хэш и соль как хотите; ничто не говорит о том, что вы должны кодировать его в base-64 в строку.

Но BCrypt был создан ребятами, которые работали над OpenBSD. OpenBSD уже определяет формат своего файла паролей:

$[HashAlgorithmIdentifier]$[AlgorithmSpecificData]

Это означает, что спецификация bcrypt неразрывно связана с форматом файла паролей OpenBSD. И всякий раз, когда кто-либо создает хэш bcrypt, он всегда преобразует его в строку формата ISO-8859-1:

$2a$[Cost]$[Base64Salt][Base64Hash]

Несколько важных моментов:

  • 2a — идентификатор алгоритма

    • 1: MD5
    • 2: ранний bcrypt, у которого была путаница в том, в какой кодировке находятся пароли (устарело)
    • 2a: текущий bcrypt, который указывает пароли в кодировке UTF-8.
  • Стоимость — фактор стоимости, используемый при вычислении хэша. Текущее значение равно 10, что означает, что настройка внутреннего ключа проходит через 1024 раунда.

    • 10: 210 = 1,024 iterations
    • 11: 211 = 2048 итераций.
    • 12: 212 = 4096 итераций.
  • алгоритм base64, используемый файлом паролей OpenBSD, не является той же кодировкой Base64, которую используют все остальные; у них свои:

      Regular Base64 Alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
          BSD Base64 Alphabet: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
    

    Таким образом, любые реализации bcrypt не могут использовать встроенную или стандартную библиотеку base64.


Вооружившись этими знаниями, теперь вы можете проверить пароль correctbatteryhorsestapler по сохраненному хешу:

$2a$12$mACnM5lzNigHMaf7O1py1O3vlf6.BA8k8x3IoJ.Tq3IB/2e7g61Km

Варианты BCrypt

Существует много путаницы вокруг версий bcrypt.

$2$

BCrypt был разработан людьми OpenBSD. Он был разработан для хеширования паролей для хранения в файле паролей OpenBSD. Хешированные пароли хранятся с префиксом для идентификации используемого алгоритма. BCrypt получил префикс $2$.

Это было в отличие от других префиксов алгоритма:

  • $1$: MD5
  • $5$: SHA-256
  • $6$: SHA-512

$2$

Первоначальная спецификация BCrypt не определяла, как обрабатывать символы, отличные от ASCII, или как обрабатывать нулевой терминатор. Спецификация была пересмотрена, чтобы указать, что при хешировании строк:

  • строка должна быть в кодировке UTF-8
  • нулевой терминатор должен быть включен

$2x$, $2y$ (июнь 2011 г.)

В crypt_blowfish????, PHP-реализация BCrypt. Это была неправильная обработка символов с установленным 8-м битом.

Они предложили системным администраторам обновить свою существующую базу паролей, заменив $2a$ на $2x$, чтобы указать, что эти хэши неверны (и нужно использовать старый сломанный алгоритм). Они также предложили, чтобы crypt_blowfish выдавал $2y$ для хэшей, сгенерированных фиксированным алгоритмом. Никто другой, включая каноническую OpenBSD, не принял идею 2x/2y. Этот маркер версии был ограничен crypt_blowfish????.

Версии $2x$ и $2y$ не лучше и не сильнее, чем $2a$. Это остатки одной конкретной реализации BCrypt с ошибками.

2 млрд долларов США (февраль 2014 г.)

В реализации BCrypt для OpenBSD обнаружена ошибка. Они написали свою реализацию на языке, который не поддерживает строки, поэтому они подделывали ее с префиксом длины, указателем на символ, а затем индексировали этот указатель с помощью []. К сожалению, они сохраняли длину своих строк в файле unsigned char. Если пароль был длиннее 255 символов, он переполнялся и переносился на 255 символов. BCrypt был создан для OpenBSD. Когда у них возникла ошибка в их библиотеке, они решили, что можно обновить версию. Это означает, что все остальные должны последовать их примеру, если вы хотите соответствовать их спецификациям.


Нет никакой разницы между 2a, 2x, 2y и 2b. Если вы правильно написали свою реализацию, все они выведут один и тот же результат.

  • Если вы с самого начала поступали правильно (сохраняли строки в utf8, а также хэшировали нулевой терминатор), то: нет никакой разницы между 2, 2a, 2x, 2y и 2b. Если вы правильно написали свою реализацию, все они выведут один и тот же результат.
  • Версия $2b$ не лучше и не сильнее, чем $2a$. Это остаток одной конкретной реализации BCrypt с ошибками. Но поскольку BCrypt канонически принадлежит OpenBSD, они могут изменить маркер версии на любой, какой захотят.
  • Версии $2x$ и $2y$ ничем не лучше и даже не предпочтительнее. Это остатки глючной реализации, о которых следует забыть.

О 2x и 2y должны заботиться только те, кто использовал crypt_blowfish еще в 2011 году. А о 2b должны заботиться только те, кто возможно, работал под управлением OpenBSD.

Все остальные правильные реализации идентичны и правильны.

person Ian Boyd    schedule 07.06.2012
comment
Cheater: TwentyTwoCharacter vs ThirtyOneCharacters (множественное число). К счастью, Salt начинается с s, так что можно утверждать, что оно разделено между двумя словами. Хотя все равно классно... - person TTT; 15.04.2016
comment
Хороший ответ @Ian Boyd, один вопрос, стоимость означает, сколько раз вы хотите повторять генерацию случайных значений? Я имею в виду, что если стоимость равна 10, будет выполнено 1024 итерации, пока не будет сгенерирован hast? - person Skizo-ozᴉʞS; 27.03.2019
comment
@Skizo-ozᴉʞS Технически он тратит 2 ^ n итераций на создание ключа шифрования. Затем этот ключ используется для шифрования текста OrpheanBeholderScryDoubt 64 раза. Но да, вы можете подумать о том, сколько итераций ему нужно выполнить, прежде чем он сгенерирует хэш. - person Ian Boyd; 27.03.2019