Деривация нескольких ключей из одного пароля

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

SJCL оперирует бинарными данными в формате bitArray. Это не строка и не массив байтов в классическом смысле, а специализированная структура, оптимизированная для побитовых операций. Любая деривация ключа в конечном итоге возвращает bitArray фиксированной длины, пригодный для AES, HMAC или других криптопримитивов.

Пароль в чистом виде сначала преобразуется в битовое представление, после чего проходит через функцию растяжения ключа (key stretching). Основная используемая функция — PBKDF2, реализованная в sjcl.misc.pbkdf2.

PBKDF2 как базовый механизм деривации

PBKDF2 в SJCL используется как основной инструмент увеличения энтропийной стойкости пароля. Его задача — сделать перебор пароля вычислительно дорогим за счёт многократного применения HMAC.

(P, S, c, dkLen) = (P, S)^{c}

Где:

  • P — пароль
  • S — соль (salt)
  • c — количество итераций
  • dkLen — длина выходного ключа
  • PRF — псевдослучайная функция на основе HMAC

В SJCL вызов выглядит следующим образом:

const password = "secret-password";
const salt = sjcl.random.randomWords(8, 0);

const derivedBits = sjcl.misc.pbkdf2(password, salt, 10000, 256);

Полученный результат — 256-битный ключ, пригодный для AES-256 или дальнейшего разделения на несколько ключей.

Роль соли и её влияние на множественную деривацию

Соль в PBKDF2 играет роль уникализатора ключевого пространства. При одинаковом пароле разные соли приводят к разным ключам. В контексте генерации нескольких ключей из одного пароля соль становится инструментом разделения ключевого материала на независимые ветви.

Типовой подход в SJCL — фиксировать базовый пароль и использовать разные соли для разных целей:

  • соль для ключа шифрования
  • соль для ключа аутентификации
  • соль для ключа инициализации

Пример:

const encSalt = sjcl.codec.hex.toBits("a1b2c3d4e5f6");
const macSalt = sjcl.codec.hex.toBits("0f1e2d3c4b5a");

const encKey = sjcl.misc.pbkdf2(password, encSalt, 12000, 256);
const macKey = sjcl.misc.pbkdf2(password, macSalt, 12000, 256);

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

Разделение ключевого материала из одного PBKDF2-вывода

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

const masterKey = sjcl.misc.pbkdf2(password, salt, 15000, 512);

const encKey = masterKey.slice(0, 8);   // 256 бит
const macKey = masterKey.slice(8, 16);  // 256 бит

SJCL работает с массивами 32-битных слов, поэтому slicing происходит по словам, а не байтам. Это важно учитывать при проектировании схемы деривации.

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

HKDF-подобная схема в SJCL

Хотя SJCL не предоставляет полноценную стандартизированную реализацию HKDF в виде отдельного API, его можно эмулировать через комбинацию HMAC и bitArray операций.

В библиотеке присутствуют примитивы HMAC через sjcl.misc.hmac, которые позволяют строить конструкцию извлечения и расширения ключа.

Схема HKDF включает два этапа:

  1. Extract — получение псевдослучайного ключа из пароля
  2. Expand — генерация нескольких ключей из одного PRK

PRK = (, )

OKM = (PRK, | 0) | (PRK, | 1) |

Реализация на SJCL:

const hmac = new sjcl.misc.hmac(salt, sjcl.hash.sha256);
const prk = hmac.encrypt(password);

const info1 = sjcl.codec.utf8String.toBits("encryption");
const info2 = sjcl.codec.utf8String.toBits("authentication");

const hmac1 = new sjcl.misc.hmac(prk, sjcl.hash.sha256);
const encKey = hmac1.encrypt(info1);

const hmac2 = new sjcl.misc.hmac(prk, sjcl.hash.sha256);
const macKey = hmac2.encrypt(info2);

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

Использование sjcl.key derivation pipeline

В типичных сценариях SJCL применяется полный pipeline:

  1. Пароль → PBKDF2 → master key
  2. master key → разделение или HKDF-расширение
  3. ключи → AES / HMAC / IV derivation

Пример полной схемы:

const password = "user-password";

const masterSalt = sjcl.random.randomWords(8);
const masterKey = sjcl.misc.pbkdf2(password, masterSalt, 20000, 512);

const encKey = masterKey.slice(0, 8);
const authKey = masterKey.slice(8, 16);
const ivSeed = masterKey.slice(16, 20);

IV часто не хранится напрямую как ключ, а выводится из отдельного сегмента мастер-ключа через хеширование или HMAC:

const ivHmac = new sjcl.misc.hmac(ivSeed, sjcl.hash.sha256);
const iv = ivHmac.encrypt("iv-generation");

Изоляция ключей и криптографическая независимость

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

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

  • нельзя использовать один и тот же salt для разных ключей при PBKDF2
  • нельзя пересекать диапазоны bitArray при slicing
  • нельзя использовать один HMAC контекст для разных назначений без domain separation

Domain separation достигается через добавление контекстных строк:

const encInfo = sjcl.codec.utf8String.toBits("enc");
const macInfo = sjcl.codec.utf8String.toBits("mac");

Производительность и выбор числа итераций

Количество итераций PBKDF2 напрямую влияет на устойчивость к brute force. В SJCL нет фиксированного значения, и выбор зависит от целевой среды.

  • 10 000 итераций — минимальный уровень для совместимости
  • 50 000+ — для современных клиентских устройств
  • 100 000+ — для высокозащищённых сценариев

Рост числа итераций линейно увеличивает время вычисления:

T c

где T — время вычисления, c — количество итераций PBKDF2.

Типовые ошибки при деривации нескольких ключей

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

  • повторное использование соли между разными ключами
  • отсутствие domain separation при HMAC-деривации
  • одинаковая длина и пересекающиеся сегменты master key
  • использование недостаточного числа итераций PBKDF2
  • хранение master key без разделения на контексты

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

Композиция ключей в реальных схемах

В реальных криптосистемах на базе SJCL часто используется трёхуровневая модель:

  • master password
  • key derivation layer (PBKDF2 / HMAC)
  • application keys (enc / mac / session / iv)

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

Комбинация PBKDF2 и HKDF-подобных конструкций позволяет одновременно обеспечить:

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