В библиотеке CryptoJS блочные шифры разделены на три уровня: сам алгоритм (AES, DES и др.), режим работы блока (CBC, CFB, CTR и т.д.) и режим заполнения (padding). Такой подход позволяет комбинировать компоненты и создавать собственные схемы обработки данных без изменения базового криптографического ядра.
Режим шифрования отвечает за то, как обрабатываются последовательности блоков фиксированного размера. Блочные алгоритмы работают только с блоками (например, 128 бит у AES), поэтому режим определяет способ преобразования длинных сообщений.
Любой режим в CryptoJS строится на основе объекта
BlockCipherMode. Он определяет интерфейс и поведение при
шифровании и расшифровании.
Ключевые элементы:
BlockCipherMode.extend(...) — создание нового
режимаprocessBlock(words, offset) — обработка одного
блокаinit(cipher, iv) — инициализация режимаEncryptor и Decryptor — вспомогательные
классы-обёрткиСтруктура данных внутри CryptoJS представлена массивом 32-битных слов:
words: [0x00000000, 0x00000000, ...]
sigBytes: количество значимых байт
Это важно учитывать при создании собственного режима, так как работа идёт не с байтовым потоком напрямую, а с блоками слов.
Стандартные режимы (CBC, CTR, CFB) покрывают большинство задач, однако собственный режим может понадобиться в случаях:
Любой новый режим начинается с расширения
BlockCipherMode.
const MyMode = CryptoJS.lib.BlockCipherMode.extend({
processBlock: function (words, offset) {
// обработка блока
}
});
Далее создаются обёртки для шифрования и расшифрования:
MyMode.Encryptor = MyMode.extend({
processBlock: function (words, offset) {
MyMode.prototype.processBlock.call(this, words, offset);
}
});
MyMode.Decryptor = MyMode.extend({
processBlock: function (words, offset) {
MyMode.prototype.processBlock.call(this, words, offset);
}
});
Каждый блок в CryptoJS обрабатывается как набор из четырёх 32-битных слов (для AES).
offset = индекс блока * 4
block0 = words[offset]
block1 = words[offset + 1]
block2 = words[offset + 2]
block3 = words[offset + 3]
Любое преобразование должно учитывать эту структуру.
Режим XOR-цепочки может использоваться как учебная модель (не является безопасной криптографией).
const XorChain = CryptoJS.lib.BlockCipherMode.extend({
init: function (cipher, iv) {
this._cipher = cipher;
this._iv = iv.clone();
this._prevBlock = iv.clone();
},
processBlock: function (words, offset) {
const blockSize = this._cipher.blockSize;
for (let i = 0; i < blockSize; i++) {
words[offset + i] ^= this._prevBlock.words[i];
}
this._cipher.encryptBlock(words, offset);
for (let i = 0; i < blockSize; i++) {
this._prevBlock.words[i] = words[offset + i];
}
}
});
При расшифровании необходимо восстанавливать предыдущий зашифрованный блок до преобразования.
const XorChainDecrypt = CryptoJS.lib.BlockCipherMode.extend({
init: function (cipher, iv) {
this._cipher = cipher;
this._iv = iv.clone();
this._prevBlock = iv.clone();
},
processBlock: function (words, offset) {
const blockSize = this._cipher.blockSize;
const temp = [];
for (let i = 0; i < blockSize; i++) {
temp[i] = words[offset + i];
}
this._cipher.decryptBlock(words, offset);
for (let i = 0; i < blockSize; i++) {
words[offset + i] ^= this._prevBlock.words[i];
}
for (let i = 0; i < blockSize; i++) {
this._prevBlock.words[i] = temp[i];
}
}
});
Чтобы использовать режим через стандартный API CryptoJS:
const encrypted = CryptoJS.AES.encrypt("text", key, {
mode: XorChain,
iv: iv
});
И расшифрование:
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
mode: XorChainDecrypt,
iv: iv
});
IV (Initialization Vector) играет критическую роль в цепочечных режимах.
Правильное использование:
В пользовательских режимах IV часто используется как:
Наиболее частые проблемы:
words и
sigBytesCTR-режим можно реализовать как генерацию псевдослучайного блока и XOR с данными.
const CustomCTR = CryptoJS.lib.BlockCipherMode.extend({
init: function (cipher, iv) {
this._cipher = cipher;
this._counter = iv.clone();
},
processBlock: function (words, offset) {
const keystream = this._counter.clone();
this._cipher.encryptBlock(keystream.words, 0);
const blockSize = this._cipher.blockSize;
for (let i = 0; i < blockSize; i++) {
words[offset + i] ^= keystream.words[i];
}
this._incrementCounter();
},
_incrementCounter: function () {
for (let i = this._counter.words.length - 1; i >= 0; i--) {
this._counter.words[i]++;
if (this._counter.words[i] !== 0) break;
}
}
});
Режим не обязан заниматься заполнением данных, но должен корректно работать с уже дополненными блоками.
CryptoJS автоматически применяет padding (PKCS7 по умолчанию), однако при создании низкоуровневых режимов возможно отключение:
CryptoJS.AES.encrypt("data", key, {
mode: XorChain,
padding: CryptoJS.pad.NoPadding
});
Каждый режим вызывает:
cipher.encryptBlock(words, offset)cipher.decryptBlock(words, offset)Эти методы работают “in-place”, изменяя массив слов напрямую.
Создание собственного режима не гарантирует криптографическую стойкость. Ошибки проектирования могут привести к:
Любой нестандартный режим требует криптоаналитической проверки, если предполагается использование вне учебных целей.
CryptoJS позволяет создавать иерархии режимов:
const BaseMode = CryptoJS.lib.BlockCipherMode.extend({
processBlock: function (words, offset) {
// базовая логика
}
});
const ExtendedMode = BaseMode.extend({
processBlock: function (words, offset) {
BaseMode.prototype.processBlock.call(this, words, offset);
// дополнительная логика
}
});
Режим может хранить любое состояние:
Важно учитывать, что объект режима живёт в течение всего процесса шифрования и используется последовательно для каждого блока.
Типовой пользовательский режим включает:
init(cipher, iv)_prevBlock,
_counter)processBlock(words, offset)_xor,
_increment)Эта структура обеспечивает совместимость с API CryptoJS и позволяет подключать режим как стандартный параметр конфигурации.
При разработке собственного режима обычно учитываются:
Любая ошибка на уровне режима приводит к компрометации всего шифра, независимо от силы базового алгоритма AES или DES.