В криптографических примитивах NaCl и его JavaScript-реализации TweetNaCl.js (а также nacl.js как обёртках) nonce является критически важным элементом режима шифрования, обеспечивающим уникальность потока шифрования для каждого сообщения при использовании одного и того же ключа.
В конструкциях nacl.secretbox и nacl.box
используется потоковое шифрование на базе XSalsa20 и аутентификация
Poly1305. Это означает, что для каждого сообщения генерируется
уникальный ключевой поток, зависящий от ключа и nonce. Любое повторное
использование nonce с тем же ключом приводит к повторному использованию
одного и того же ключевого потока.
В TweetNaCl.js nonce — это 24-байтовый массив
(Uint8Array(24)), который передаётся вместе с
сообщением:
nacl.secretbox(message, nonce, key)nacl.box(message, nonce, publicKeyPair)Nonce не обязан быть секретным, но обязан быть уникальным в пределах одного ключа.
Ключевая идея: nonce не должен повторяться при шифровании разных сообщений одним ключом
Шифрование в secretbox фактически строится как:
keystream = StreamCipher(key, nonce)ciphertext = plaintext ⊕ keystreamЕсли nonce повторяется:
keystream₁ == keystream₂Тогда для двух сообщений:
C1 = P1 ⊕ K
C2 = P2 ⊕ K
XOR двух шифртекстов даёт:
C1 ⊕ C2 = (P1 ⊕ K) ⊕ (P2 ⊕ K)
= P1 ⊕ P2
Это полностью уничтожает конфиденциальность обоих сообщений.
Даже без знания ключа возможно:
Если известно одно из сообщений (known-plaintext attack), второе восстанавливается полностью:
P2 = C1 ⊕ C2 ⊕ P1
В nacl.secretbox используется Poly1305 MAC, ключ
которого производен из того же потока.
Повтор nonce означает:
Это критично: нарушается не только конфиденциальность, но и целостность.
В реальных протоколах повтор nonce приводит к:
const nonce = new Uint8Array(24); // всегда нули
Каждое новое сообщение повторяет keystream → катастрофическая уязвимость.
const nonce = new Uint8Array(24);
for (let i = 0; i < 24; i++) {
nonce[i] = Math.floor(Math.random() * 256);
}
Проблемы:
Math.random() не криптографически стойкийЧастая ошибка:
nonce.set(new Uint8Array(new BigUint64Array([Date.now()])));
Недостатки:
В TweetNaCl.js:
secretbox — 24 байтаbox — также 24 байтаЭто принципиальное свойство:
безопасность полностью ложится на разработчика
Использование crypto.getRandomValues:
function createNonce() {
const nonce = new Uint8Array(24);
crypto.getRandomValues(nonce);
return nonce;
}
Свойства:
Используется в протоколах с состоянием:
let counter = 0n;
function createNonce() {
const nonce = new Uint8Array(24);
const view = new DataView(nonce.buffer);
view.setBigUint64(16, counter, true);
counter += 1n;
return nonce;
}
Особенности:
const prefix = crypto.getRandomValues(new Uint8Array(16));
let counter = 0n;
function createNonce() {
const nonce = new Uint8Array(24);
nonce.set(prefix, 0);
const view = new DataView(nonce.buffer);
view.setBigUint64(16, counter, true);
counter += 1n;
return nonce;
}
Свойства:
Главное требование:
Это принципиальное различие:
При повторе:
Если nonce был повторён:
В экосистеме NaCl:
Для счётчиков:
Повтор nonce в TweetNaCl.js:
Безопасность конструкции nacl.secretbox и
nacl.box полностью опирается на строгую дисциплину
уникальности nonce, при которой любое отклонение превращает
криптосистему в уязвимую потоковую схему с утечкой данных через простые
алгебраические операции.