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

Библиотека CryptoJS построена вокруг модульной системы криптографических примитивов, где каждый алгоритм реализуется как расширение базового класса хеширования. Такой подход позволяет внедрять собственные алгоритмы, сохраняя совместимость с внутренними механизмами обработки данных, потокового обновления и финализации результата.

В основе лежит объектная модель CryptoJS.lib, где ключевую роль играют:

  • Base — базовый класс для всех объектов библиотеки
  • WordArray — представление бинарных данных
  • Hasher — абстракция для всех хеш-функций
  • BufferedBlockAlgorithm — обработка данных блоками

Хеш-алгоритм в CryptoJS представляет собой класс с переопределением трёх основных методов:

  • _doReset — инициализация состояния
  • _doProcessBlock — обработка блока данных
  • _doFinalize — завершение вычисления

Базовая структура пользовательского хеша

Создание собственного алгоритма начинается с расширения CryptoJS.lib.Hasher.

const MyHash = CryptoJS.lib.Hasher.extend({
    _doReset: function () {
        this._state = 0x12345678;
    },

    _doProcessBlock: function (words, offset) {
        let block = words[offset];

        this._state ^= block;
        this._state = (this._state << 5) | (this._state >>> 27);
    },

    _doFinalize: function () {
        return CryptoJS.lib.WordArray.create([this._state]);
    }
});

Такая структура уже является полноценным хеш-алгоритмом, интегрированным в систему CryptoJS.


Работа с состоянием алгоритма

Любой хеш-алгоритм опирается на внутреннее состояние. В CryptoJS оно обычно представляется числовыми регистрами или массивами 32-битных слов.

Инициализация состояния

Метод _doReset вызывается при создании экземпляра и повторной инициализации:

_doReset: function () {
    this._a = 0x67452301;
    this._b = 0xEFCDAB89;
    this._c = 0x98BADCFE;
    this._d = 0x10325476;
}

Подобный подход характерен для алгоритмов семейства MD5 и SHA-подобных конструкций.


Обработка входных данных

В CryptoJS входные данные автоматически преобразуются в массив 32-битных слов (WordArray). Каждый блок данных передаётся в _doProcessBlock.

_doProcessBlock: function (words, offset) {
    let w0 = words[offset];
    let w1 = words[offset + 1];

    this._a = this._a + (w0 ^ this._b);
    this._b = this._b + (w1 ^ this._c);

    this._a = (this._a << 7) | (this._a >>> 25);
}

Ключевая особенность модели заключается в том, что данные не обрабатываются целиком, а поступают по блокам фиксированного размера (обычно 512 бит).


Завершение вычислений

Метод _doFinalize отвечает за финальную стадию алгоритма. Здесь выполняется:

  • добавление padding
  • добавление длины сообщения
  • финальное преобразование состояния
_doFinalize: function () {
    let bitLength = this._nDataBytes * 8;

    this._data.words.push(0x80 << 24);

    while (this._data.words.length % 16 !== 14) {
        this._data.words.push(0);
    }

    this._data.words.push(0);
    this._data.words.push(bitLength);

    this._process();

    return CryptoJS.lib.WordArray.create([
        this._a, this._b, this._c, this._d
    ]);
}

Финализация тесно связана с внутренним буфером BufferedBlockAlgorithm, который управляет накоплением данных.


Регистрация пользовательского алгоритма

После определения класса алгоритм интегрируется в пространство CryptoJS.algo.

CryptoJS.algo.MyHash = MyHash;

Теперь алгоритм становится доступным через стандартный интерфейс:

const hash = CryptoJS.MyHash("data").toString();

Использование WordArray в пользовательских алгоритмах

WordArray — фундаментальная структура представления бинарных данных.

let wa = CryptoJS.lib.WordArray.create([0xDEADBEEF, 0xCAFEBABE]);

Основные операции:

  • конкатенация
  • клонирование
  • преобразование в строку

Внутри хеш-алгоритма WordArray используется для:

  • хранения входных блоков
  • накопления результата
  • передачи промежуточных данных

Побитовые операции и 32-битная арифметика

CryptoJS полностью опирается на 32-битные операции, так как JavaScript не имеет нативной поддержки низкоуровневой битовой арифметики.

Типовые операции:

(a + b) >>> 0
(a << 5) | (a >>> 27)
a ^ b

Особое внимание требуется к переполнению, поскольку оно имитирует поведение беззнаковых 32-битных регистров.


Пример простого хеш-алгоритма

Ниже приведён минимальный рабочий пример линейного хеша:

const SimpleHash = CryptoJS.lib.Hasher.extend({
    _doReset: function () {
        this._state = 0;
    },

    _doProcessBlock: function (words, offset) {
        for (let i = 0; i < 16; i++) {
            this._state += words[offset + i];
            this._state = this._state ^ (this._state << 3);
        }
    },

    _doFinalize: function () {
        return CryptoJS.lib.WordArray.create([this._state >>> 0]);
    }
});

CryptoJS.algo.SimpleHash = SimpleHash;

Интеграция с механизмом потоковой обработки

Одно из ключевых преимуществ CryptoJS — поддержка потоковой обработки данных.

Методы:

  • update(data)
  • finalize(data)
  • clone()

Пример внутреннего поведения:

let hasher = CryptoJS.algo.MyHash.create();
hasher.update("part1");
hasher.update("part2");
let result = hasher.finalize();

Каждый вызов update вызывает буферизацию и частичную обработку блоков.


Клонирование состояния алгоритма

Клонирование необходимо для параллельных вычислений:

clone: function () {
    let clone = this.constructor.create();
    clone._state = this._state;
    return clone;
}

Это позволяет сохранять промежуточное состояние и продолжать вычисления независимо.


Особенности проектирования криптографических алгоритмов в CryptoJS

При создании собственного хеша необходимо учитывать:

  • фиксированный размер блока (обычно 512 бит)
  • детерминированность результата
  • отсутствие зависимости от платформы
  • корректную обработку endianness
  • правильную реализацию padding

Структурная модель алгоритма

Типичный алгоритм в CryptoJS можно представить как конвейер:

  1. Преобразование строки в WordArray
  2. Разбиение на блоки
  3. Итеративная обработка _doProcessBlock
  4. Финализация _doFinalize
  5. Формирование выходного WordArray

Расширенные возможности модификации

CryptoJS позволяет расширять не только хеши, но и вспомогательные компоненты:

  • кастомные кодировки
  • модификация padding-логики
  • изменение блока обработки
  • создание гибридных алгоритмов

Пример изменения размера блока:

blockSize: 512/32

Поведение внутри BufferedBlockAlgorithm

BufferedBlockAlgorithm управляет буфером данных:

  • накопление входных байтов
  • преобразование в 32-битные слова
  • вызов _doProcessBlock при заполнении блока

Это скрытый слой, обеспечивающий потоковую обработку.


Взаимодействие с внешними режимами (cipher/hasher)

Хотя хеши и шифры различаются, архитектура CryptoJS унифицирована:

  • хеши используют Hasher
  • шифры используют Cipher
  • оба работают через WordArray

Это позволяет комбинировать алгоритмы в цепочки обработки данных.