Хэш-функции в SJCL строятся вокруг общего интерфейса
sjcl.hash, который задаёт единый контракт для всех
реализаций. Любая новая функция хэширования должна соответствовать этому
контракту, чтобы быть совместимой с механизмами библиотеки: HMAC,
PBKDF2, режимами подписи и другими криптографическими примитивами.
В основе лежит абстрактный объект:
reset() — инициализация внутреннего состоянияupdate(data) — обработка входных данныхfinalize() — завершение и выдача результатаblockSize — размер блока в 32-битных словахoutputSize — длина хэша в битахКаждая реализация наследует поведение от sjcl.hash и
переопределяет только криптографически значимые части.
Внутренне SJCL оперирует не байтами, а 32-битными словами через
sjcl.bitArray, что критично учитывать при добавлении нового
алгоритма.
Новая хэш-функция определяется как конструктор и прототип,
наследующий sjcl.hash:
sjcl.hash.MyHash = function (hashParams) {
if (!hashParams) hashParams = {};
this.reset();
};
Наследование выполняется через:
sjcl.hash.MyHash.prototype = new sjcl.hash();
sjcl.hash.MyHash.prototype.constructor = sjcl.hash.MyHash;
После этого необходимо определить обязательные поля:
sjcl.hash.MyHash.prototype.blockSize = 512;
sjcl.hash.MyHash.prototype.outputSize = 256;
Любой хэш-алгоритм требует внутреннего состояния. В SJCL оно обычно хранится в виде массива 32-битных слов:
sjcl.hash.MyHash.prototype.reset = function () {
this._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
this._count = 0;
this._buffer = [];
};
_state — текущие хэш-регистры_count — количество обработанных бит_buffer — промежуточные данные до заполнения блокаМетод update отвечает за накопление и обработку входного
потока. SJCL передаёт данные в формате bitArray, поэтому
требуется корректная работа с этим форматом.
sjcl.hash.MyHash.prototype.update = function (data) {
var i, w;
data = sjcl.bitArray.bitSlice(data, 0);
for (i = 0; i < data.length; i += 1) {
w = data[i];
this._buffer.push(w);
if (this._buffer.length === this.blockSize / 32) {
this._processBlock(this._buffer);
this._buffer = [];
}
}
this._count += sjcl.bitArray.bitLength(data);
};
Здесь ключевым моментом является согласование формата входных данных с внутренними словами алгоритма.
Сердце хэш-функции — функция обработки блока:
sjcl.hash.MyHash.prototype._processBlock = function (block) {
var a = this._state[0],
b = this._state[1],
c = this._state[2],
d = this._state[3],
i;
for (i = 0; i < block.length; i++) {
var temp = (a ^ b ^ c ^ d ^ block[i]) >>> 0;
a = d;
d = c;
c = b;
b = temp;
}
this._state[0] = (this._state[0] + a) >>> 0;
this._state[1] = (this._state[1] + b) >>> 0;
this._state[2] = (this._state[2] + c) >>> 0;
this._state[3] = (this._state[3] + d) >>> 0;
};
В реальных алгоритмах здесь находятся нелинейные функции, раунды, константы и перестановки. SJCL не накладывает ограничений на внутреннюю логику, пока соблюдается контракт блоковой обработки.
Метод finalize обязан корректно обработать padding и
вернуть результат в формате bitArray.
sjcl.hash.MyHash.prototype.finalize = function () {
var i, pad = [];
pad.push(0x80000000);
while ((this._buffer.length + pad.length) % (this.blockSize / 32) !== (this.blockSize / 32 - 2)) {
pad.push(0);
}
pad.push(0);
pad.push(this._count);
this.update(pad);
return sjcl.bitArray.clamp(this._state, this.outputSize);
};
Padding должен учитывать специфику алгоритма. В большинстве случаев используется схема, аналогичная MD-стилю: добавление 1-битного маркера, нулей и длины сообщения.
SJCL ожидает результат в формате bitArray, поэтому часто
требуется приведение:
sjcl.hash.MyHash.prototype.encrypt = function (data) {
return this.finalize(data);
};
Хотя encrypt не является обязательным, он используется
для совместимости с некоторыми слоями библиотеки.
После определения алгоритма его необходимо зарегистрировать в пространстве имён:
sjcl.hash.myhash = sjcl.hash.MyHash;
Это позволяет использовать его в HMAC и других конструкциях:
var h = new sjcl.hash.myhash();
h.update(sjcl.codec.utf8String.toBits("data"));
var out = h.finalize();
Любая новая хэш-функция автоматически становится доступной для HMAC, если реализует стандартный интерфейс:
var mac = new sjcl.misc.hmac(key, sjcl.hash.myhash);
mac.update(message);
var result = mac.digest();
Ключевой момент — корректная работа reset() после
каждого завершения вычисления.
SJCL не использует байтовые массивы напрямую. Вместо этого применяются операции над 32-битными словами:
sjcl.bitArray.bitLength(x) — длинаsjcl.bitArray.clamp(x, len) — обрезкаsjcl.bitArray.concat(a, b) — объединениеsjcl.bitArray.bitSlice(x, start, end) — срезПри реализации хэш-функции важно избегать смешивания байтовой и словной логики, иначе возникают ошибки выравнивания.
Часто встречаются следующие проблемы:
bitArray форматуreset()blockSize, из-за чего ломается HMACКорректная работа требует строгого соблюдения 32-битной арифметики:
(a + b) >>> 0
Для удобства взаимодействия часто используется кодирование:
sjcl.codec.hex.fromBits(hash)
sjcl.codec.base64.fromBits(hash)
sjcl.codec.utf8String.toBits(text)
Новая хэш-функция автоматически совместима с этими кодеками,
поскольку возвращает стандартный bitArray.
Иногда алгоритм требует параметров (например, количество раундов):
sjcl.hash.MyHash = function (params) {
params = params || {};
this.rounds = params.rounds || 10;
this.reset();
};
Это позволяет адаптировать хэш-функцию без изменения интерфейса библиотеки.
SJCL изначально оптимизирован для JavaScript-движков, но хэш-функции часто становятся узким местом. Ключевые моменты оптимизации:
>>> 0 для принудительного
unsigned intНовая хэш-функция в SJCL становится полноценной частью
криптографического стека только при соблюдении строгого интерфейса
sjcl.hash, корректной работе с bitArray и
совместимости с HMAC и кодеками.