Генерация ключей из паролей: зачем нужен KDF

Пароли сами по себе не подходят для использования в качестве криптографических ключей, поскольку обладают низкой энтропией, предсказуемой структурой и часто повторяются в различных системах. Прямое применение строки пароля в алгоритмах шифрования приводит к уязвимостям, связанным с перебором, словарными атаками и утечками через радужные таблицы. Для устранения этих проблем применяется класс алгоритмов, называемых KDF (Key Derivation Function) — функций вывода ключа.

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

Ключевые свойства корректно реализованного KDF:

  • высокая вычислительная стоимость одной итерации
  • возможность настройки сложности через параметр итераций
  • использование соли (salt) для защиты от радужных таблиц
  • детерминированность результата при одинаковых входных данных
  • устойчивость к параллельным атакам на GPU и ASIC

Почему пароль нельзя использовать напрямую

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

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

Криптографический ключ должен быть случайным по свойствам и иметь равномерное распределение битов, чего обычные пароли не обеспечивают.

PBKDF2 как основной механизм в Crypto-js

В библиотеке Crypto-js чаще всего используется PBKDF2 (Password-Based Key Derivation Function 2). Алгоритм основан на многократном применении HMAC с заданной хэш-функцией.

Основная идея заключается в итеративном усложнении вычисления ключа:

  • вход: пароль, соль, количество итераций
  • процесс: многократное применение HMAC
  • выход: производный ключ заданной длины

Формально PBKDF2 строится на следующем принципе:

= (P, S, c, dkLen)

где:

  • P — пароль
  • S — соль
  • c — число итераций
  • dkLen — длина ключа

Роль соли в генерации ключей

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

Без соли одинаковые пароли дают одинаковые ключи, что создаёт уязвимость при массовом анализе данных. Соль делает каждый результат уникальным даже при совпадающих паролях.

Свойства корректной соли:

  • уникальность для каждой операции
  • достаточная длина (обычно 8–16 байт и более)
  • случайное распределение значений
  • сохранение вместе с зашифрованными данными

Итерации как механизм замедления атак

Количество итераций напрямую влияет на вычислительную сложность алгоритма. Каждая итерация увеличивает время генерации ключа как для легитимного пользователя, так и для атакующего.

Модель роста сложности:

T c

где:

  • T — время вычисления
  • c — число итераций

Рост итераций линейно увеличивает стоимость перебора, что делает массовые атаки экономически нецелесообразными.

Реализация PBKDF2 в Crypto-js

Библиотека Crypto-js предоставляет встроенную реализацию PBKDF2, использующую различные хэш-функции, включая SHA1, SHA256 и SHA512.

Пример использования:

import CryptoJS from "crypto-js";

const password = "strong_password";
const salt = CryptoJS.lib.WordArray.random(16);

const key = CryptoJS.PBKDF2(password, salt, {
  keySize: 256 / 32,
  iterations: 100000,
  hasher: CryptoJS.algo.SHA256
});

console.log(key.toString());

В данном процессе:

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

Формирование ключа фиксированной длины

Результат PBKDF2 всегда нормализуется до заданной длины. Это необходимо для согласованности с алгоритмами шифрования, которые требуют строго определённого размера ключа (AES-128, AES-256 и т.д.).

Формирование ключа можно представить как усечение или расширение результата хэш-функции через итеративное накопление блоков.

Применение в симметричном шифровании

Сгенерированный ключ используется в алгоритмах шифрования, например AES. В связке Crypto-js процесс выглядит следующим образом:

const iv = CryptoJS.lib.WordArray.random(16);

const encrypted = CryptoJS.AES.encrypt("secret data", key, {
  iv: iv
});

Использование KDF обеспечивает, что даже слабый пароль превращается в криптографически приемлемый ключ.

Устойчивость к атакам перебора

Основная ценность KDF заключается в усложнении офлайн-атаки. При наличии зашифрованного текста атакующий вынужден проверять каждый пароль через полный процесс PBKDF2, включая все итерации.

Сложность атаки определяется как:

C N c

где:

  • N — количество проверяемых паролей
  • c — число итераций PBKDF2

Таким образом, увеличение итераций напрямую увеличивает стоимость атаки.

Выбор параметров KDF

Практическая настройка PBKDF2 требует баланса между безопасностью и производительностью.

Основные параметры:

  • соль: минимум 16 байт случайных данных
  • итерации: от 100000 и выше в современных системах
  • хэш-функция: SHA-256 или выше
  • длина ключа: в зависимости от алгоритма шифрования

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

Типичные ошибки при использовании KDF

На практике встречаются критические ошибки:

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

Каждая из этих ошибок существенно снижает уровень защиты всей системы.

Связь KDF с архитектурой безопасности

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

Без этого слоя вся криптографическая система наследует слабость человеческого фактора, что делает её уязвимой независимо от качества используемого алгоритма шифрования.