IV (initialization vector) и nonce в Web Crypto API используются как критически важные элементы при работе с симметричными алгоритмами шифрования, особенно AES-GCM, AES-CBC и AES-CTR. Их роль заключается не в сокрытии данных, а в обеспечении уникальности каждого криптографического преобразования с одним и тем же ключом.
Термины IV и nonce часто используются взаимозаменяемо, однако их смысл различается:
В Web Crypto API в большинстве случаев используется термин
iv, но по факту для AES-GCM это именно nonce с
дополнительными требованиями к уникальности.
AES-GCM требует строгой уникальности IV для каждого шифрования с одним ключом. Рекомендуемая длина:
Ключевое свойство: повтор IV с тем же ключом полностью компрометирует безопасность шифрования.
Для CBC используется случайный IV длиной 16 байт (размер блока AES). Повтор IV не приводит к мгновенной компрометации ключа, но раскрывает паттерны в первых блоках сообщений.
CTR использует nonce + счётчик. Обычно:
Повтор nonce в CTR приводит к катастрофическим последствиям: XOR двух сообщений раскрывает их содержимое.
Основной источник криптографически стойкой случайности —
crypto.getRandomValues.
const iv = crypto.getRandomValues(new Uint8Array(12));
12 байт выбраны не случайно: это оптимальная длина для GCM, при которой реализация внутри Web Crypto API не выполняет дополнительных преобразований.
const iv = crypto.getRandomValues(new Uint8Array(16));
Размер 16 байт соответствует блоку AES.
IV не является секретом, но обязателен для расшифровки. Поэтому он почти всегда хранится вместе с зашифрованными данными.
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 };
}
Пример 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 при одном и том же ключе приводит к следующим последствиям:
C_1 C_2 = (P_1 KS) (P_2 KS) = P_1 P_2
где KS — поток ключа, зависящий от nonce.
В некоторых протоколах IV не генерируется случайно, а строится детерминированно:
Пример конструкции 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;
}
Ключевое правило:
Это означает:
Web Crypto API работает с ArrayBuffer и
TypedArray, но при хранении или передаче IV часто
кодируется.
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 часто выносится в отдельный слой:
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
Счётчик
Гибридный подход
При шифровании последовательных сообщений:
new Uint8Array(12))Math.random() вместо
crypto.getRandomValuesВ AES-GCM IV влияет не только на шифрование, но и на вычисление authentication tag. Изменение IV при одинаковом ciphertext приводит к полностью другому тегу, что делает невозможной подделку без ключа, но повтор IV разрушает это свойство.