В браузерных API работа с криптографией и сетевыми протоколами почти всегда опирается на бинарные данные: ArrayBuffer и типизированные представления вроде Uint8Array. Однако большинство данных в веб-приложениях изначально существует в виде строк. Для корректного преобразования между строками и байтовыми последовательностями используются TextEncoder и TextDecoder — базовые инструменты кодирования текста в UTF-8 и обратно.
Строка в JavaScript — это последовательность символов в формате
UTF-16. Криптографические алгоритмы Web Crypto API не работают со
строками напрямую. Например, методы crypto.subtle.digest,
encrypt, sign принимают данные только в виде
ArrayBuffer или ArrayBufferView.
Это приводит к необходимости преобразования:
Именно эту задачу решают TextEncoder и TextDecoder.
TextEncoder кодирует строку в UTF-8 и возвращает Uint8Array.
Основной сценарий использования — подготовка данных для хеширования, подписи или шифрования.
const encoder = new TextEncoder();
const text = "Пример строки";
const bytes = encoder.encode(text);
console.log(bytes);
// Uint8Array([...])
Важно учитывать, что UTF-8 является стандартом де-факто для Web
Crypto API, поэтому TextEncoder идеально согласуется с
crypto.subtle.
Типичный пример — вычисление SHA-256 хеша:
const encoder = new TextEncoder();
const data = encoder.encode("секретное сообщение");
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
console.log(new Uint8Array(hashBuffer));
Здесь критически важно, что digest принимает именно
бинарные данные. Передача строки невозможна.
TextDecoder выполняет обратную операцию — преобразует бинарные данные в строку.
const decoder = new TextDecoder();
const bytes = new Uint8Array([208, 159, 209, 128, 208, 184, 208, 188, 208, 181, 209, 128]);
const text = decoder.decode(bytes);
console.log(text);
// "Пример"
Хотя по умолчанию используется UTF-8, TextDecoder поддерживает и другие кодировки:
const decoder = new TextDecoder("utf-8");
Дополнительно возможны:
Однако для Web Crypto API практически всегда используется UTF-8.
TextDecoder умеет работать с частичными данными, что важно при обработке потоков.
const decoder = new TextDecoder("utf-8", { stream: true });
let chunk1 = decoder.decode(new Uint8Array([208, 159, 208]));
let chunk2 = decoder.decode(new Uint8Array([184, 208, 188, 0]), { stream: false });
console.log(chunk1 + chunk2);
Это полезно при:
TextEncoder и TextDecoder образуют симметричную пару, но работают с разными уровнями представления данных:
| Операция | Вход | Выход |
|---|---|---|
| encode | string | Uint8Array |
| decode | Uint8Array / ArrayBuffer | string |
Ключевая особенность — отсутствие потерь при корректной кодировке UTF-8.
Web Crypto API использует ArrayBuffer как основной контейнер данных. Поэтому часто требуется преобразование Uint8Array → ArrayBuffer:
const encoder = new TextEncoder();
const bytes = encoder.encode("данные");
const buffer = bytes.buffer;
Однако важно учитывать, что .buffer может включать
лишний диапазон памяти, если Uint8Array — это view над частью
буфера.
Более безопасный способ:
const buffer = encoder.encode("данные").slice().buffer;
const decoder = new TextDecoder();
function bufferToString(buffer) {
return decoder.decode(new Uint8Array(buffer));
}
Это особенно часто используется при:
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const message = "сообщение для хеширования";
const data = encoder.encode(message);
const hash = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hash));
const hex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
console.log(hex);
Здесь происходит полный цикл:
Распространённые проблемы:
Происходит при неправильной кодировке или попытке интерпретировать UTF-8 как ASCII.
Передача строки вместо Uint8Array приводит к исключению в
crypto.subtle.
UTF-8 кодирует символы кириллицы и emoji в несколько байт, что важно при расчётах длины данных.
TextEncoder и TextDecoder реализованы на уровне движка браузера и работают значительно быстрее ручного преобразования через циклы JavaScript.
Оптимизации:
В реальных сценариях Web Crypto API почти всегда комбинируется с TextEncoder:
Пример подготовки ключевого материала:
const encoder = new TextEncoder();
const password = encoder.encode("пароль пользователя");
const key = await crypto.subtle.importKey(
"raw",
password,
"PBKDF2",
false,
["deriveKey"]
);
UTF-8 корректно кодирует emoji и сложные символы:
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const text = "ключ ?";
const encoded = encoder.encode(text);
const decoded = decoder.decode(encoded);
console.log(decoded);
Каждый emoji занимает несколько байт, что важно учитывать при криптографических операциях, где длина данных критична.
До появления TextEncoder/TextDecoder разработчики использовали:
Эти подходы:
Современный стандарт полностью заменяет их в браузере.
При работе с fetch API данные часто приходят в бинарном виде:
const response = await fetch("/data");
const buffer = await response.arrayBuffer();
const text = new TextDecoder().decode(buffer);
Или наоборот:
const encoder = new TextEncoder();
await fetch("/api", {
method: "POST",
body: encoder.encode("payload")
});
TextEncoder и TextDecoder являются связующим слоем между:
Без этих инструментов большинство операций Web Crypto API было бы невозможно в чистом виде, так как API строго типизирован под бинарные входные данные.