Хеш-функции в CryptoJS обладают важным свойством детерминированности: одинаковый вход всегда должен давать одинаковый выход. Это делает их удобным объектом для юнит-тестирования, где основная цель — гарантировать стабильность вычислений и отсутствие регрессий при изменениях кода.
Базовый принцип тестирования хеш-функций заключается в проверке соответствия известных входных значений заранее вычисленным контрольным суммам. В CryptoJS чаще всего используются алгоритмы MD5, SHA1, SHA256, SHA512.
import CryptoJS from "crypto-js";
const hash = CryptoJS.SHA256("hello").toString();
console.log(hash);
Юнит-тест фиксирует ожидаемое значение:
import CryptoJS from "crypto-js";
test("SHA256 от строки hello", () => {
const result = CryptoJS.SHA256("hello").toString();
expect(result).toBe("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824");
});
Ключевой момент — использование заранее известных векторов, которые берутся из стандартов NIST или проверенных криптографических справочников.
Для SHA256 классическим примером является строка
"abc":
test("SHA256 от abc", () => {
const result = CryptoJS.SHA256("abc").toString();
expect(result).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
});
Такие тесты выполняют роль якорей корректности реализации и защищают от случайных изменений поведения библиотеки или неверной конфигурации кодировки.
Юнит-тесты обычно покрывают сразу несколько алгоритмов:
test("MD5 хеширование", () => {
const result = CryptoJS.MD5("test").toString();
expect(result).toBe("098f6bcd4621d373cade4e832627b4f6");
});
test("SHA1 хеширование", () => {
const result = CryptoJS.SHA1("test").toString();
expect(result).toBe("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3");
});
Такой подход позволяет выявлять несоответствия в конкретных алгоритмах, особенно при обновлении версии CryptoJS или замене внутренней реализации.
Одна из частых проблем при тестировании — различие в интерпретации строк. CryptoJS работает с UTF-8 по умолчанию, однако ошибки возникают при неявных преобразованиях.
test("UTF-8 строка с кириллицей", () => {
const result = CryptoJS.SHA256("привет").toString();
expect(result).toBe("ожидаемое_значение_хеша");
});
Важно фиксировать, что вход всегда интерпретируется одинаково. В сложных случаях полезно явно задавать кодировку:
import CryptoJS from "crypto-js";
const wordArray = CryptoJS.enc.Utf8.parse("привет");
const hash = CryptoJS.SHA256(wordArray).toString();
Особое внимание уделяется краевым случаям:
test("пустая строка SHA256", () => {
const result = CryptoJS.SHA256("").toString();
expect(result).toBe("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
});
Также тестируются:
CryptoJS оперирует структурой WordArray, что требует отдельного тестирования при работе с байтовыми последовательностями:
test("хеширование бинарного массива", () => {
const words = CryptoJS.lib.WordArray.create([0x61626300]); // "abc\0"
const result = CryptoJS.SHA256(words).toString();
expect(result).toBeDefined();
});
Такие тесты важны при интеграции с файлами, сетевыми потоками и криптографическими протоколами.
Хеш-функции должны давать одинаковый результат вне зависимости от способа передачи данных в CryptoJS:
test("строка и WordArray дают одинаковый результат", () => {
const hash1 = CryptoJS.SHA256("data").toString();
const hash2 = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse("data")).toString();
expect(hash1).toBe(hash2);
});
Это защищает от ошибок при рефакторинге слоёв кодирования.
На практике чаще всего применяются Jest или Mocha. В Jest тесты хеш-функций выглядят компактно:
describe("CryptoJS hash tests", () => {
it("SHA256 consistency", () => {
const input = "consistent input";
const hash1 = CryptoJS.SHA256(input).toString();
const hash2 = CryptoJS.SHA256(input).toString();
expect(hash1).toBe(hash2);
});
});
Mocha в связке с Chai:
const { expect } = require("chai");
const CryptoJS = require("crypto-js");
describe("SHA256", () => {
it("should produce deterministic hash", () => {
const result = CryptoJS.SHA256("test").toString();
expect(result).to.equal("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
});
});
Даже минимальное изменение строки должно приводить к полностью другому хешу:
test("изменение одного символа меняет хеш", () => {
const hash1 = CryptoJS.SHA256("data1").toString();
const hash2 = CryptoJS.SHA256("data2").toString();
expect(hash1).not.toBe(hash2);
});
Это проверяет фундаментальное свойство лавинного эффекта.
Для масштабирования тестирования используется подход data-driven:
const vectors = [
{ input: "a", expected: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" },
{ input: "abc", expected: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" }
];
vectors.forEach(({ input, expected }) => {
test(`SHA256(${input})`, () => {
const result = CryptoJS.SHA256(input).toString();
expect(result).toBe(expected);
});
});
Этот подход упрощает расширение тестового покрытия без дублирования кода.
Важный аспект — соответствие результатов CryptoJS и других реализаций (OpenSSL, Node.js crypto):
import crypto from "crypto";
test("сравнение с Node crypto", () => {
const input = "comparison";
const nodeHash = crypto.createHash("sha256").update(input).digest("hex");
const cryptoJSHash = CryptoJS.SHA256(input).toString();
expect(cryptoJSHash).toBe(nodeHash);
});
Такие тесты выявляют расхождения в кодировке или обработке входных данных.
При обновлении CryptoJS тесты хеш-функций выполняют роль регрессионного барьера. Любое изменение в алгоритмах, даже незначительное, сразу проявляется в несовпадении контрольных значений, что позволяет выявлять ошибки до попадания в продакшен.
Дополнительный уровень проверки — случайные входные данные:
function randomString(length) {
return Array.from({ length }, () =>
String.fromCharCode(Math.floor(Math.random() * 256))
).join("");
}
test("фаззинг SHA256", () => {
for (let i = 0; i < 1000; i++) {
const input = randomString(50);
const hash = CryptoJS.SHA256(input).toString();
expect(hash).toHaveLength(64);
}
});
Такой подход помогает выявлять редкие ошибки обработки нестандартных байтовых последовательностей.