В Web Crypto API все криптографические операции выполняются над бинарными данными. Обычные строки JavaScript не подходят напрямую, так как представлены в виде UTF-16. Для корректной работы с хешированием требуется преобразование строки в массив байтов.
Ключевую роль здесь играет встроенный интерфейс
TextEncoder, который преобразует строку в
Uint8Array в кодировке UTF-8.
const encoder = new TextEncoder();
const data = encoder.encode("Пример строки");
Результат:
data — это Uint8Array, содержащий
последовательность байтовОсновной механизм хеширования в Web Crypto API — метод
crypto.subtle.digest. Он принимает:
ArrayBuffer или
TypedArray)const encoder = new TextEncoder();
const data = encoder.encode("Пример строки");
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
Поддерживаемые алгоритмы:
"SHA-1" (устаревший, не рекомендуется)"SHA-256""SHA-384""SHA-512"Результат digest — это ArrayBuffer. Для
удобства его обычно преобразуют в массив байтов:
const hashArray = Array.from(new Uint8Array(hashBuffer));
Далее часто требуется строковое представление, например в hex-формате:
const hashHex = hashArray
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
Теперь hashHex содержит читаемое представление хеша.
async function hashString(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
}
hashString("Hello, WebCrypto!").then(console.log);
1. Всегда UTF-8
TextEncoder не поддерживает выбор кодировки. Это
гарантирует единообразие, но требует учета при работе с внешними
системами.
2. Поддержка Unicode
Корректно обрабатывает:
encoder.encode("?");
// корректно преобразуется в UTF-8 последовательность байтов
3. Иммутабельность результата
Каждый вызов encode() возвращает новый массив. Изменения
не влияют на исходную строку.
Важно понимать, что одинаково выглядящие строки могут иметь разное бинарное представление:
"é" !== "e\u0301"
Обе строки визуально идентичны, но:
TextEncoder закодирует их по-разному, что приведет к
разным хешам.
Для предотвращения подобных проблем используется нормализация Unicode:
const normalized = str.normalize("NFC");
const data = encoder.encode(normalized);
Часто применяемые формы:
NFC — каноническая композицияNFD — каноническая декомпозицияВыбор зависит от требований системы, но важно использовать одну форму последовательно.
TextEncoder работает очень быстро и реализован
нативноОднако:
В современных версиях Node.js TextEncoder доступен
глобально:
const encoder = new TextEncoder();
А также доступен через модуль util (в старых
версиях):
const { TextEncoder } = require("util");
Web Crypto API доступен через:
const { subtle } = require("crypto").webcrypto;
Передача строки напрямую в digest
// Ошибка
crypto.subtle.digest("SHA-256", "text");
Метод ожидает бинарные данные.
Забытый await
const hash = crypto.subtle.digest("SHA-256", data);
// hash — Promise, а не результат
Неправильная интерпретация результата
ArrayBuffer нельзя напрямую вывести как строку:
console.log(hashBuffer); // нечитаемый вывод
Требуется преобразование в hex или base64.
Base64:
function bufferToBase64(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}
Uint8Array напрямую:
Используется в бинарных протоколах и криптографических операциях без преобразования.
TextEncoder часто используется вместе с:
TextDecoder — обратное преобразованиеSubtleCrypto — криптографические операцииUint8Array и ArrayBuffer — работа с
бинарными даннымиconst encoder = new TextEncoder();
const data = encoder.encode(inputString);
const hash = await crypto.subtle.digest("SHA-256", data);
Этот паттерн лежит в основе большинства операций хеширования строк в Web Crypto API.