Неверная длина хеша 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 шт):
По-моему ларчик открывается просто:
- Длина хэша 24 байта (бинарные естественно)
- 24 байта в Base64 представляют собой 32 ASCII символа
- 32 ASCII символа в UTF-8 имеет длину 32 символа
Теперь, если мы пропустим шаг №2 - то есть перекодировку 24 байт в Base64, а начнем кодировать его напрямую из бинарного хэша, то можем получить длину хэша в UTF-8 от 24 символов до 48 (смотря как карты лягут). Почему? Все очень просто - UTF-8 работает так:
- Если в байте старший бит пустой (то есть это ASCII символ - грубо говоря символ латинского алфавита), то байт записывается "as-is"
- Если старший бит не пустой, то байт представляется как 2 символа
Пример вычисления длины здесь
То есть происходит следующее:
- В варианте автора, сначала вычисляется хэш, потом хэш перекодируется в Base64, который далее в UTF-8 - что всегда дает 32 символа
- В варианте встроенной функции, вычисляется хэш, который перекодируется в UTF-8 сразу. И если в хэше есть нелатинские символы, то длина хэша в UTF-8 представлении начинает скакать и рандомно может показывать величины от 24 от 48 символов (в примере ТС это 31 символов).