Генерация и управление IV и nonce

IV (initialization vector) и nonce в Web Crypto API используются как критически важные элементы при работе с симметричными алгоритмами шифрования, особенно AES-GCM, AES-CBC и AES-CTR. Их роль заключается не в сокрытии данных, а в обеспечении уникальности каждого криптографического преобразования с одним и тем же ключом.

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

  • IV (Initialization Vector) — традиционно используется в режимах блочного шифрования (CBC и др.), где требуется случайное начальное значение для первого блока.
  • Nonce (Number used once) — значение, которое гарантированно используется только один раз в рамках одного ключа.

В Web Crypto API в большинстве случаев используется термин iv, но по факту для AES-GCM это именно nonce с дополнительными требованиями к уникальности.

Алгоритмы и требования к IV/nonce

AES-GCM

AES-GCM требует строгой уникальности IV для каждого шифрования с одним ключом. Рекомендуемая длина:

  • 12 байт (96 бит) — оптимальный размер
  • Допустимы другие размеры, но с внутренней обработкой и потенциальным снижением производительности

Ключевое свойство: повтор IV с тем же ключом полностью компрометирует безопасность шифрования.

AES-CBC

Для CBC используется случайный IV длиной 16 байт (размер блока AES). Повтор IV не приводит к мгновенной компрометации ключа, но раскрывает паттерны в первых блоках сообщений.

AES-CTR

CTR использует nonce + счётчик. Обычно:

  • 16 байт nonce
  • часть байт выделяется под счётчик

Повтор nonce в CTR приводит к катастрофическим последствиям: XOR двух сообщений раскрывает их содержимое.


Генерация IV и nonce в Web Crypto API

Основной источник криптографически стойкой случайности — crypto.getRandomValues.

Генерация IV для AES-GCM (рекомендуемый способ)

const iv = crypto.getRandomValues(new Uint8Array(12));

12 байт выбраны не случайно: это оптимальная длина для GCM, при которой реализация внутри Web Crypto API не выполняет дополнительных преобразований.

Генерация IV для AES-CBC

const iv = crypto.getRandomValues(new Uint8Array(16));

Размер 16 байт соответствует блоку AES.


Хранение IV вместе с шифротекстом

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

Пример объединения IV и ciphertext

function concatBuffers(iv, ciphertext) {
    const result = new Uint8Array(iv.length + ciphertext.length);
    result.set(iv, 0);
    result.set(ciphertext, iv.length);
    return result;
}

При расшифровке структура разбивается обратно:

function splitIvAndData(data) {
    const iv = data.slice(0, 12);
    const ciphertext = data.slice(12);
    return { iv, ciphertext };
}

Шифрование с использованием IV

Пример AES-GCM:

const key = await crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"]
);

const iv = crypto.getRandomValues(new Uint8Array(12));

const encoded = new TextEncoder().encode("секретное сообщение");

const ciphertext = await crypto.subtle.encrypt(
    {
        name: "AES-GCM",
        iv: iv
    },
    key,
    encoded
);

Ошибки повторного использования IV

Повтор IV при одном и том же ключе приводит к следующим последствиям:

AES-GCM

  • утечка аутентификационных тегов
  • возможность восстановления XOR между plaintexts
  • потенциальная подделка сообщений

AES-CTR

  • прямое раскрытие XOR двух сообщений:

C_1 C_2 = (P_1 KS) (P_2 KS) = P_1 P_2

где KS — поток ключа, зависящий от nonce.


Детерминированные nonce и счётчики

В некоторых протоколах IV не генерируется случайно, а строится детерминированно:

  • счётчик сообщений
  • timestamp + random suffix
  • комбинация идентификатора сессии и счётчика

Пример конструкции nonce:

function buildNonce(sessionId, counter) {
    const nonce = new Uint8Array(12);
    nonce.set(sessionId.slice(0, 8), 0);

    const view = new DataView(nonce.buffer);
    view.setUint32(8, counter);

    return nonce;
}

Требования к уникальности

Ключевое правило:

  • IV/nonce должен быть уникален для каждого шифрования с одним ключом
  • криптографическая случайность не обязательна, если гарантирована уникальность

Это означает:

  • случайный IV подходит при достаточной энтропии
  • счётчик подходит при строгом контроле состояния
  • timestamp подходит только с дополнительной случайной компонентой

Буферные представления и кодировки

Web Crypto API работает с ArrayBuffer и TypedArray, но при хранении или передаче IV часто кодируется.

Base64 представление

function toBase64(buffer) {
    return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}

Обратное преобразование

function fromBase64(base64) {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);

    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }

    return bytes.buffer;
}

Централизованное управление IV

В реальных системах генерация IV часто выносится в отдельный слой:

class IVManager {
    constructor() {
        this.counter = 0;
    }

    next() {
        const iv = new Uint8Array(12);
        crypto.getRandomValues(iv);

        const view = new DataView(iv.buffer);
        view.setUint32(8, this.counter++);

        return iv;
    }
}

Такой подход комбинирует случайность и контроль уникальности.


Сравнение подходов генерации IV

  • Полностью случайный IV

    • простота
    • высокая криптостойкость
    • невозможность воспроизведения
  • Счётчик

    • строгая уникальность
    • риск при рассинхронизации состояния
  • Гибридный подход

    • случайная часть + детерминированная часть
    • баланс безопасности и контроля

Критические требования безопасности

  • повтор IV с AES-GCM недопустим
  • IV не должен зависеть от ключа напрямую
  • хранение IV открыто, но обязательное для восстановления
  • длина IV должна соответствовать рекомендациям алгоритма

Использование в потоках данных

При шифровании последовательных сообщений:

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

Ошибки реализации, встречающиеся на практике

  • использование фиксированного IV (new Uint8Array(12))
  • генерация IV один раз на сессию вместо каждого сообщения
  • отсутствие сохранения IV вместе с ciphertext
  • использование Math.random() вместо crypto.getRandomValues
  • повтор IV при ретраях отправки сообщения

Связь IV и аутентификации в GCM

В AES-GCM IV влияет не только на шифрование, но и на вычисление authentication tag. Изменение IV при одинаковом ciphertext приводит к полностью другому тегу, что делает невозможной подделку без ключа, но повтор IV разрушает это свойство.