Использование соли и её генерация

Соль (salt) в криптографии представляет собой случайную последовательность байт, которая добавляется к исходным данным (чаще всего паролю) перед применением хеш-функции или алгоритма выработки ключа. Основная задача соли — исключить предсказуемость результата и защитить от предварительно вычисленных атак, таких как rainbow tables.

В Stanford JavaScript Crypto Library (SJCL) соль используется в механизмах симметричного шифрования на основе пароля и в функции PBKDF2 (Password-Based Key Derivation Function 2), которая применяется для получения криптографического ключа из пароля.


При преобразовании пароля в ключ нельзя использовать сам пароль напрямую. Вместо этого применяется функция многократного хеширования с добавлением соли:

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

K = (P, S, c, dkLen)

где:

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

В SJCL этот процесс используется внутри функций sjcl.encrypt и sjcl.misc.pbkdf2.


Генерация соли в SJCL

В SJCL генерация случайных значений основана на криптографически стойком генераторе случайных чисел sjcl.random.

Базовый способ получения соли:

const salt = sjcl.random.randomWords(8, 0); 

Здесь:

  • 8 — количество 32-битных слов (32 байта соли)
  • 0 — параноидальный уровень энтропии (минимальный контроль блокировки генерации)

Соль в SJCL обычно хранится как массив 32-битных чисел, а не строка.


Источник энтропии

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

  • движения мыши
  • нажатия клавиш
  • события таймеров
  • системные источники (если доступны)

Перед использованием криптографических функций важно обеспечить достаточную энтропию:

sjcl.random.addEntropy([new Date().getTime()], 32, "time");

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


Использование соли в PBKDF2

Функция PBKDF2 в SJCL позволяет явно задавать соль:

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

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

Параметры:

  • password — исходный пароль
  • salt — случайная соль
  • 10000 — число итераций (усиление вычислений)
  • 256 — длина ключа в битах

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


Соль в sjcl.encrypt

При использовании высокоуровневого API шифрования SJCL соль создаётся автоматически:

const encrypted = sjcl.encrypt("password", "secret data");

Результат представляет собой JSON-строку:

{
  "iv": "...",
  "v": 1,
  "iter": 10000,
  "ks": 256,
  "ts": 64,
  "mode": "ccm",
  "adata": "",
  "cipher": "aes",
  "salt": "....",
  "ct": "...."
}

Поле salt генерируется автоматически и используется внутри PBKDF2 для получения ключа шифрования.

Повторное шифрование одинаковых данных с тем же паролем даёт разные результаты именно из-за новой соли.


Криптографические свойства соли

Соль не является секретом. Её свойства:

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

Ключевая ошибка — попытка использовать одну и ту же соль повторно. Это приводит к:

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

Практика хранения соли

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

При самостоятельном использовании PBKDF2 необходимо хранить соль отдельно:

const record = {
  salt: sjcl.codec.hex.fromBits(salt),
  key: sjcl.codec.hex.fromBits(derivedKey)
};

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


Ошибки при работе со солью

Распространённые проблемы:

Использование статической соли

const salt = sjcl.codec.utf8String.toBits("static");

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


Недостаточная энтропия

Генерация без sjcl.random:

const salt = [Math.random(), Math.random()];

Math.random() не является криптографически безопасным источником.


Потеря соли

Без сохранённой соли восстановление ключа невозможно:

  • расшифрование становится недоступным
  • данные фактически теряются

Взаимодействие соли и IV

В SJCL часто путают соль и IV (Initialization Vector):

  • соль используется для деривации ключа
  • IV используется для режима шифрования (например, AES-CCM)

Они выполняют разные функции и не заменяют друг друга. Даже при одинаковом пароле:

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

Безопасные параметры генерации соли

Рекомендуемая конфигурация:

  • длина: 128–256 бит
  • генерация: sjcl.random.randomWords(4–8, 0)
  • источник: только SJCL RNG
  • хранение: вместе с шифротекстом

Роль соли в устойчивости системы

При корректном использовании SJCL:

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

Соль не усиливает пароль напрямую, но создаёт уникальный контекст для каждого случая шифрования, что является критическим элементом всей схемы PBKDF2 и симметричного шифрования на основе пароля в SJCL.