Внутренняя структура хеша: разбор по частям

Хэш пароля, формируемый библиотеками уровня Password-hash, представляет собой не просто результат криптографической функции, а структурированную строку, в которой закодированы параметры алгоритма, соль и итоговое значение. Такая композиция обеспечивает воспроизводимость проверки и устойчивость к подбору при условии корректной конфигурации параметров.


Общий формат представления

Большинство реализаций используют строковую сериализацию следующего логического вида:

algorithm$parameters$salt$hash

или более компактные варианты:

algorithm$iterations$salt$hash
algorithm$cost$salt$hash

Разделителем почти всегда выступает символ $, поскольку он редко встречается в base64- или hex-кодировке и упрощает парсинг без дополнительной экранизации.


Идентификатор алгоритма

Первая часть строки отвечает за выбор криптографического примитива. Она позволяет системе однозначно определить, каким способом был получен хэш.

Типичные варианты:

  • sha1
  • sha256
  • bcrypt
  • pbkdf2
  • argon2 (в более современных реализациях)

Пример:

sha256$...

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


Параметры вычисления

Следующий сегмент содержит настройки сложности вычисления хэша. Их структура зависит от конкретного алгоритма.

Итерации (iterations)

Используются в PBKDF2 и схожих функциях:

pbkdf2$10000$...

Число итераций напрямую влияет на стоимость вычисления: чем выше значение, тем медленнее подбор пароля.

Фактор сложности (cost)

В алгоритмах типа bcrypt применяется экспоненциальный параметр:

bcrypt$12$...

Значение 12 означает 2^12 раундов преобразований.


Соль (salt)

Соль — случайная строка, добавляемая к паролю перед хэшированием. Она предотвращает использование радужных таблиц и делает одинаковые пароли различными на уровне результата.

Формат соли зависит от кодировки:

  • base64 (чаще всего)
  • hex (реже)
  • бинарное представление, закодированное в строку

Пример:

sha256$10000$k9d2Hf8kLqP1$...

Соль должна быть уникальной для каждого пароля и сохраняется внутри итоговой строки, поскольку требуется при проверке.


Криптографический хэш

Последняя часть — итоговое значение после применения алгоритма.

Пример:

sha256$10000$k9d2Hf8kLqP1$e4b7c9a1f0d8...

Хэш обычно кодируется в base64 или hex. Его длина фиксирована и зависит от используемой функции:

  • SHA-256 → 32 байта (64 hex-символа)
  • SHA-512 → 64 байта
  • bcrypt → 184 символа (внутренняя структура base64)

Внутренняя последовательность формирования

Логика построения итоговой строки включает несколько этапов:

  1. Генерация соли
  2. Преобразование пароля в байтовую последовательность
  3. Объединение пароля и соли
  4. Применение итеративного или адаптивного алгоритма
  5. Кодирование результата в строку
  6. Сборка финального формата с разделителями

Псевдологика:

salt = random()
hash = KDF(password + salt, params)
result = algorithm + "$" + params + "$" + salt + "$" + encode(hash)

Кодирование данных

Каждый компонент строки проходит нормализацию перед объединением.

Base64

Используется для компактного представления бинарных данных. Внутри могут присутствовать символы:

  • A–Z
  • a–z
  • 0–9
  • + /

Иногда заменяются на URL-safe вариант:

  • - _

Hex

Представляет байты в виде шестнадцатеричных пар:

e4b7c9a1f0d8...

Версионность формата

Некоторые реализации добавляют версию алгоритма в начало строки:

$2b$12$salt$hash

или:

v1$sha256$10000$salt$hash

Это позволяет:

  • поддерживать обратную совместимость
  • изменять параметры без разрушения старых записей
  • определять необходимость миграции хэшей

Разделители и их роль

Символ $ используется как структурный маркер. Он разделяет логические блоки:

  • алгоритм
  • параметры
  • соль
  • хэш

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


Особенности хранения соли и хэша вместе

Объединение соли и результата в одну строку даёт несколько технических преимуществ:

  • исключается рассинхронизация данных
  • упрощается проверка пароля
  • упрощается миграция алгоритмов
  • уменьшается количество полей в базе данных

При проверке пароля строка разбирается обратно на компоненты, после чего выполняется повторное вычисление хэша с извлечённой солью.


Разбор строки при верификации

Алгоритм проверки включает обратный процесс:

  1. Разделение строки по $
  2. Извлечение алгоритма и параметров
  3. Извлечение соли
  4. Повторное вычисление хэша входного пароля
  5. Сравнение результатов в константное время

Псевдоструктура:

[algorithm, params, salt, storedHash] = parse(hashString)
computedHash = KDF(inputPassword, salt, params)
compare(storedHash, computedHash)

Константное сравнение

Финальный этап сравнения выполняется с защитой от timing-атак. Обычное сравнение строк недопустимо, поскольку может раскрыть информацию о совпадающих префиксах.

Используется побайтовое сравнение с фиксированным временем выполнения независимо от результата.


Закодированная самодостаточность хэша

Строка хэша в формате Password-hash является самодостаточной структурой:

  • содержит алгоритм
  • содержит параметры вычисления
  • содержит соль
  • содержит итоговый результат

Это позволяет системе не хранить дополнительные метаданные и при этом корректно восстанавливать процесс проверки даже спустя годы после генерации.