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

В библиотеке Stanford JavaScript Crypto Library (SJCL) режим шифрования представляет собой слой поверх блочного шифра (обычно AES), который определяет, как именно обрабатываются данные больше одного блока, как используется вектор инициализации, и каким образом обеспечивается целостность и конфиденциальность сообщения.

Базовый принцип: режим шифрования = алгоритм управления потоками блоков + обработка IV + (опционально) аутентификация

Внутри SJCL режимы реализуются как объекты в пространстве sjcl.mode, где каждый режим обязан предоставлять единый интерфейс шифрования и расшифрования.


Контракт режима шифрования

Любой пользовательский режим должен соответствовать ожидаемому интерфейсу, который используется функциями sjcl.encrypt и sjcl.decrypt.

Минимальный набор требований:

  • функция encrypt(prf, plaintext, iv, adata)
  • функция decrypt(prf, ciphertext, iv, adata)
  • обработка блочного шифра через sjcl.cipher.aes
  • корректная работа с IV (initialization vector)

Параметры:

  • prf — псевдослучайная функция, обычно экземпляр AES (new sjcl.cipher.aes(key))
  • plaintext / ciphertext — массивы слов SJCL (sjcl.bitArray)
  • iv — вектор инициализации
  • adata — дополнительные аутентифицированные данные (если режим поддерживает AEAD)

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

Режим в SJCL обычно оформляется как функция-конструктор:

sjcl.mode.MyMode = function (cipher) {
    this.cipher = cipher;
};

И набор методов:

sjcl.mode.MyMode.prototype.encrypt = function (prf, plaintext, iv, adata) {
    ...
};

sjcl.mode.MyMode.prototype.decrypt = function (prf, ciphertext, iv, adata) {
    ...
};

После этого режим регистрируется в системе:

sjcl.mode.myMode = function (aes, plaintext, iv, adata) {
    return new sjcl.mode.MyMode(aes).encrypt(aes, plaintext, iv, adata);
};

и аналогично для decrypt.


Простейший пример: потоковый XOR-режим

Для понимания механики полезно рассмотреть упрощённый потоковый режим, который генерирует псевдослучайный поток на основе AES и XOR-ит его с данными.

Идея режима

  • IV используется как стартовое состояние счётчика
  • AES(key, counter) генерирует блок ключевого потока
  • каждый блок данных XOR-ится с этим потоком
  • счётчик увеличивается

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

sjcl.mode.XorStream = function (aes) {
    this.cipher = aes;
};

Вспомогательная функция XOR

SJCL работает с sjcl.bitArray, поэтому используется:

function xor(a, b) {
    return sjcl.bitArray.bitxor(a, b);
}

Шифрование

sjcl.mode.XorStream.prototype.encrypt = function (aes, plaintext, iv) {
    var blockSize = 4; // 128 бит = 4 слова
    var counter = iv.slice(0);
    var output = [];

    var i, keystream;

    for (i = 0; i < plaintext.length; i += blockSize) {
        keystream = aes.encrypt(counter);

        var chunk = plaintext.slice(i, i + blockSize);
        output = output.concat(xor(chunk, keystream));

        counter[3]++; // инкремент младшего слова счётчика
    }

    return output;
};

Дешифрование

Для потоковых режимов шифрование и дешифрование идентичны:

sjcl.mode.XorStream.prototype.decrypt = sjcl.mode.XorStream.prototype.encrypt;

Регистрация режима в sjcl.mode

Чтобы режим стал доступен через стандартный API SJCL, его необходимо зарегистрировать.

Подключение в пространство режимов

sjcl.mode.xorStream = {
    encrypt: function (aes, plaintext, iv, adata) {
        return new sjcl.mode.XorStream(aes).encrypt(aes, plaintext, iv, adata);
    },
    decrypt: function (aes, ciphertext, iv, adata) {
        return new sjcl.mode.XorStream(aes).decrypt(aes, ciphertext, iv, adata);
    }
};

После этого режим можно использовать через стандартные функции:

sjcl.encrypt("password", "data", { mode: "xorStream" });
sjcl.decrypt("password", json, { mode: "xorStream" });

Интеграция с sjcl.encrypt / sjcl.decrypt

Внутри SJCL выбор режима происходит по строковому идентификатору:

options.mode

При вызове:

sjcl.encrypt(password, data, { mode: "ccm" });

библиотека выполняет:

  • выбор режима из sjcl.mode[mode]
  • инициализацию AES
  • передачу IV и параметров в encrypt

Поэтому пользовательский режим должен быть доступен именно через:

sjcl.mode["имя"]

Работа с IV и ключевым материалом

IV не должен генерироваться внутри режима без необходимости. SJCL ожидает:

  • IV передаётся извне или генерируется на уровне sjcl.encrypt
  • режим использует IV как начальное состояние

Типичная ошибка при реализации:

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

Корректная практика:

var ivCopy = iv.slice(0);

Расширенный режим: добавление аутентификации

Если требуется AEAD-подобное поведение, режим должен:

  • вычислять MAC по данным
  • включать adata в хэш
  • проверять целостность при расшифровании

Простейшая схема:

var mac = sjcl.hash.sha256.hash(adata + ciphertext);

и сравнение:

if (!sjcl.bitArray.equal(mac, expectedMac)) {
    throw new sjcl.exception.corrupt("tag mismatch");
}

Подключение режима к высокоуровневому API

После регистрации режима он становится частью стандартного набора:

  • sjcl.encrypt
  • sjcl.decrypt
  • sjcl.json.encrypt
  • sjcl.json.decrypt

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

var encrypted = sjcl.encrypt("key", "secret data", {
    mode: "xorStream",
    ts: 64
});

var decrypted = sjcl.decrypt("key", encrypted, {
    mode: "xorStream"
});

Ограничения и требования безопасности

Сам факт корректной реализации интерфейса не означает криптографическую безопасность режима.

Критически важные требования:

  • отсутствие повторного использования IV
  • отсутствие линейных предсказуемых потоков без криптостойкой PRF
  • обязательная аутентификация для защищённых каналов
  • использование проверенных конструкций (CTR, GCM, CCM)

Самодельные режимы допустимы только для:

  • учебных целей
  • тестирования
  • экспериментальных прототипов

Внутренняя модель расширения SJCL

SJCL проектировался как расширяемая система:

  • sjcl.cipher — блочные шифры
  • sjcl.mode — режимы работы
  • sjcl.misc — вспомогательные функции
  • sjcl.codec — кодирование форматов

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


Пример итоговой структуры пользовательского режима

sjcl.mode.custom = {
    encrypt: function (aes, plaintext, iv, adata) {
        return new sjcl.mode.XorStream(aes).encrypt(aes, plaintext, iv);
    },

    decrypt: function (aes, ciphertext, iv, adata) {
        return new sjcl.mode.XorStream(aes).decrypt(aes, ciphertext, iv);
    }
};