Неверная длина хеша BCrypt

В 1 чудесный день я захотел реализовать алгоритм BCrypt на C#, и тут понеслась…

У меня есть чистый хеш, который мы получаем в результате 64 итераций в коне́чной функции.

Чистый хеш (в качестве ключа использовал строчку "hello")

$2y$12$oy0YCRJ7YJ7Topl/8FT/E..3hithmF9mZkNhoGUpfVEK4aQdXSXEW
└┬────┘└─────────────────┬──┘╘══════════════╤══════════════╛
 └ Информация о хеше     └ Соль             └ Чистый хеш
                                              ‾‾‾‾‾‾‾‾‾‾

Но проблема не в нём, а в его рекодировании с Base64 в UTF-8. Дело в том, что он имеет размер в 24 байта, а его "символьное" представление на ⅓ больше, т. е. 24 ⋅ 4 ∶ 3 = 32 символа. Но если мы сгенерируем хеш, используя уже готовый генератор, то обнаружим, что размер нашего хеша в UTF-8 равен 31 символу. Куда подевался ещё 1 символ?


Ответы (1 шт):

Автор решения: Barmaley

По-моему ларчик открывается просто:

  1. Длина хэша 24 байта (бинарные естественно)
  2. 24 байта в Base64 представляют собой 32 ASCII символа
  3. 32 ASCII символа в UTF-8 имеет длину 32 символа

Теперь, если мы пропустим шаг №2 - то есть перекодировку 24 байт в Base64, а начнем кодировать его напрямую из бинарного хэша, то можем получить длину хэша в UTF-8 от 24 символов до 48 (смотря как карты лягут). Почему? Все очень просто - UTF-8 работает так:

  • Если в байте старший бит пустой (то есть это ASCII символ - грубо говоря символ латинского алфавита), то байт записывается "as-is"
  • Если старший бит не пустой, то байт представляется как 2 символа

Пример вычисления длины здесь

То есть происходит следующее:

  1. В варианте автора, сначала вычисляется хэш, потом хэш перекодируется в Base64, который далее в UTF-8 - что всегда дает 32 символа
  2. В варианте встроенной функции, вычисляется хэш, который перекодируется в UTF-8 сразу. И если в хэше есть нелатинские символы, то длина хэша в UTF-8 представлении начинает скакать и рандомно может показывать величины от 24 от 48 символов (в примере ТС это 31 символов).
→ Ссылка