Написание собственного кодека

В основе работы Stanford JavaScript Crypto Library лежит единый тип данных — bitArray. Любая криптографическая операция внутри библиотеки работает не со строками и байтами напрямую, а с массивами 32-битных слов, дополненными информацией о длине значимой части последнего элемента.

Такое представление позволяет:

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

Любой внешний формат (hex, base64, JSON, бинарные строки) в sjcl реализуется через слой кодеков (sjcl.codec), который отвечает за преобразование:

bitArray ⇄ внешний формат

Архитектура codec-слоя

Все кодеки в sjcl находятся в пространстве:

sjcl.codec

Каждый кодек представляет собой объект с двумя ключевыми функциями:

  • encode(bitArray) — преобразование битового массива в строку или структуру
  • decode(string) — обратное преобразование в bitArray

Типовая сигнатура:

sjcl.codec.custom = {
    encode: function (arr) { ... },
    decode: function (str) { ... }
};

Некоторые кодеки дополнительно реализуют вспомогательные методы, но базовый контракт ограничивается этими двумя функциями.


Внутреннее устройство bitArray

Понимание структуры bitArray критично для создания корректного кодека.

Пример структуры:

[
  0x12345678,
  0x9abcdef0,
  0x13579b00, // последний элемент содержит "хвост"
  100         // длина значимых бит
]

Особенности:

  • данные хранятся в 32-битных словах;
  • последний элемент массива содержит количество валидных бит;
  • лишние биты в последнем слове игнорируются;
  • операции кодеков обязаны учитывать это значение длины.

Встроенные кодеки sjcl

Hex-кодек

Используется для человекочитаемого представления байтов:

sjcl.codec.hex
  • плотное шестнадцатеричное представление
  • отсутствие разделителей
  • фиксированная длина 4 бита на символ

Base64

sjcl.codec.base64

Используется для компактной передачи бинарных данных:

  • 6 бит на символ
  • padding через =
  • оптимизирован под URL и транспорт

UTF-8 строковый кодек

sjcl.codec.utf8String
  • преобразует строки в битовые массивы
  • использует UTF-8 кодировку
  • критичен для криптографических операций с текстом

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

Пользовательские кодеки в sjcl применяются в случаях, когда требуется:

  • нестандартное представление данных (например, base91 или custom alphabet)
  • совместимость с внешними системами хранения
  • минимизация размера при специфических ограничениях
  • сериализация в доменно-специфичный формат

Требования к собственному кодеку

Корректный codec должен соблюдать несколько инвариантов:

  1. Полная обратимость преобразования

    decode(encode(x)) ≡ x
  2. Учет битовой длины bitArray

  3. Отсутствие зависимости от внешнего состояния

  4. Предсказуемая работа с неполными байтами


Разработка кастомного кодека

Рассматривается пример кодека на основе пользовательского алфавита (base-N).

Определение алфавита

var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
var BASE = ALPHABET.length;

Преобразование bitArray → число

Для кодирования требуется преобразовать битовый массив в последовательность чисел.

Принцип:

  • извлечение 32-битных слов
  • объединение в единое большое число или поток
  • разбиение по основанию алфавита

Реализация encode

sjcl.codec.baseN = {
    encode: function (bitArray) {
        var out = "";
        var value = 0;
        var bits = 0;

        for (var i = 0; i < bitArray.length - 1; i++) {
            value = (value << 32) | bitArray[i];
            bits += 32;

            while (bits >= 6) {
                bits -= 6;
                out += ALPHABET[(value >>> bits) & 63];
            }
        }

        var last = bitArray[bitArray.length - 1];
        value = (value << last) >>> 0;

        while (bits > 0) {
            if (bits < 6) {
                out += ALPHABET[(value << (6 - bits)) & 63];
                break;
            }
            bits -= 6;
            out += ALPHABET[(value >>> bits) & 63];
        }

        return out;
    }
};

Реализация decode

Обратное преобразование требует восстановления битового массива:

sjcl.codec.baseN.decode = function (str) {
    var out = [];
    var value = 0;
    var bits = 0;

    for (var i = 0; i < str.length; i++) {
        value = (value << 6) | ALPHABET.indexOf(str[i]);
        bits += 6;

        if (bits >= 32) {
            bits -= 32;
            out.push(value >>> bits);
            value &= (1 << bits) - 1;
        }
    }

    if (bits > 0) {
        out.push(value << (32 - bits));
    }

    out.push(bits);

    return out;
};

Регистрация кодека в sjcl

Все пользовательские кодеки добавляются в пространство:

sjcl.codec.baseN = baseN;

После этого они становятся доступными как стандартные:

var encoded = sjcl.codec.baseN.encode(bits);
var decoded = sjcl.codec.baseN.decode(encoded);

Работа с неполными байтами

Ключевая сложность кодеков sjcl заключается в корректной обработке “хвоста” битового массива.

Особенности:

  • последний элемент массива содержит длину значимых бит;
  • нельзя предполагать кратность 8 или 32;
  • любые операции должны сохранять точную длину.

Типовая ошибка — потеря значимых бит при сдвигах:

value << (32 - bits)

Потоковая обработка

Некоторые кастомные кодеки расширяются до потокового режима:

  • обработка чанков входных данных;
  • минимизация использования памяти;
  • применение в WebSocket или streaming API.

В таких случаях кодек разделяется на:

  • encoder state
  • decoder state

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

  1. Игнорирование bitArray[length - 1]
  2. Потеря старших бит при сдвиге
  3. Использование строк вместо битовых операций
  4. Несогласованность алфавита encode/decode
  5. Отсутствие обработки пустых входов

Безопасность и роль кодека

Кодек не является криптографическим компонентом.

Он:

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

Ошибочная интерпретация кодека как средства защиты приводит к ложному чувству безопасности.


Расширенные варианты кастомных кодеков

Сжатые бинарные форматы

  • упаковка нескольких бит в нестандартные блоки
  • использование Huffman-подобных таблиц

Доменные кодеки

  • сериализация ключей протоколов
  • компактное хранение токенов

URL-safe кодеки

  • замена символов +, /, =
  • устранение необходимости escaping

Совместимость с другими компонентами sjcl

Кодеки используются во всех ключевых модулях:

  • sjcl.encrypt
  • sjcl.decrypt
  • sjcl.hash
  • sjcl.random

Любое изменение кодека влияет только на внешний слой представления, но не на криптографическое ядро.