Написание собственного режима шифрования

В библиотеке CryptoJS блочные шифры разделены на три уровня: сам алгоритм (AES, DES и др.), режим работы блока (CBC, CFB, CTR и т.д.) и режим заполнения (padding). Такой подход позволяет комбинировать компоненты и создавать собственные схемы обработки данных без изменения базового криптографического ядра.

Режим шифрования отвечает за то, как обрабатываются последовательности блоков фиксированного размера. Блочные алгоритмы работают только с блоками (например, 128 бит у AES), поэтому режим определяет способ преобразования длинных сообщений.


Базовые сущности режима в CryptoJS

Любой режим в CryptoJS строится на основе объекта BlockCipherMode. Он определяет интерфейс и поведение при шифровании и расшифровании.

Ключевые элементы:

  • BlockCipherMode.extend(...) — создание нового режима
  • processBlock(words, offset) — обработка одного блока
  • init(cipher, iv) — инициализация режима
  • Encryptor и Decryptor — вспомогательные классы-обёртки

Структура данных внутри CryptoJS представлена массивом 32-битных слов:

words: [0x00000000, 0x00000000, ...]
sigBytes: количество значимых байт

Это важно учитывать при создании собственного режима, так как работа идёт не с байтовым потоком напрямую, а с блоками слов.


Причины создания собственного режима

Стандартные режимы (CBC, CTR, CFB) покрывают большинство задач, однако собственный режим может понадобиться в случаях:

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

Базовая структура пользовательского режима

Любой новый режим начинается с расширения 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-цепочки

Режим XOR-цепочки может использоваться как учебная модель (не является безопасной криптографией).

Логика:

  • каждый блок XOR’ится с предыдущим зашифрованным блоком
  • первый блок XOR’ится с IV

Реализация режима

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];
        }
    }
});

Регистрация режима в алгоритме AES

Чтобы использовать режим через стандартный 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 в пользовательских режимах

IV (Initialization Vector) играет критическую роль в цепочечных режимах.

Правильное использование:

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

В пользовательских режимах IV часто используется как:

  • начальное значение цепочки
  • первый “предыдущий блок”
  • часть ключевого смешивания

Ошибки при реализации пользовательских режимов

Наиболее частые проблемы:

  • неправильная работа с words и sigBytes
  • игнорирование блоковой структуры (4 слова для AES)
  • изменение входного массива без копирования
  • отсутствие сохранения состояния между блоками
  • неправильное обновление IV или предыдущего блока

Пример потокового режима на основе счётчика

CTR-режим можно реализовать как генерацию псевдослучайного блока и 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;
        }
    }
});

Интеграция с padding

Режим не обязан заниматься заполнением данных, но должен корректно работать с уже дополненными блоками.

CryptoJS автоматически применяет padding (PKCS7 по умолчанию), однако при создании низкоуровневых режимов возможно отключение:

CryptoJS.AES.encrypt("data", key, {
    mode: XorChain,
    padding: CryptoJS.pad.NoPadding
});

Внутреннее взаимодействие с cipher

Каждый режим вызывает:

  • 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.