Преобразование между строками, байтами, hex и base64

В Stanford JavaScript Crypto Library (SJCL) вся криптографическая обработка опирается на внутренний формат данных — bitArray. Это не строки, не байты и не привычные массивы Uint8Array, а структурированное представление битов, оптимизированное для криптографических операций.

Ключевая особенность SJCL: любые входные данные перед вычислениями приводятся к bitArray, а любые выходные — обратно в удобный формат (hex, base64, строки).


bitArray: фундамент всех преобразований

bitArray — это массив 32-битных слов с дополнительным элементом длины в битах.

Структура:

  • arr[0] ... arr[n] — 32-битные числа
  • последний элемент содержит длину в битах

Пример:

var ba = [0x61626300, 24];

Это строка "abc" в виде битов (3 байта = 24 бита).


Преобразование строки в bitArray

Для работы со строками используется UTF-8 кодек:

var sjcl = require('sjcl');

var text = "hello";
var bits = sjcl.codec.utf8String.toBits(text);

Что происходит внутри

  • строка → UTF-8 байты
  • байты → группировка в 32-битные слова
  • добавление длины в битах

Обратное преобразование: bitArray → строка

var original = sjcl.codec.utf8String.fromBits(bits);

Важно учитывать:

  • используется UTF-8 декодирование
  • некорректные байты могут привести к ошибкам декодирования

Hex-представление

Hex — один из самых популярных форматов для криптографии.

Преобразование string → hex

var bits = sjcl.codec.utf8String.toBits("hello");
var hex = sjcl.codec.hex.fromBits(bits);

console.log(hex);

Результат:

68656c6c6f

Преобразование hex → string

var bits = sjcl.codec.hex.toBits("68656c6c6f");
var text = sjcl.codec.utf8String.fromBits(bits);

console.log(text); // hello

Base64 в SJCL

Base64 часто используется для передачи бинарных данных через текстовые протоколы.

bitArray → base64

var bits = sjcl.codec.utf8String.toBits("hello");
var b64 = sjcl.codec.base64.fromBits(bits);

console.log(b64);

Результат:

aGVsbG8=

base64 → string

var bits = sjcl.codec.base64.toBits("aGVsbG8=");
var text = sjcl.codec.utf8String.fromBits(bits);

console.log(text);

Важный нюанс: паддинг base64

SJCL поддерживает два режима base64:

  • стандартный (= padding)
  • без padding

Настройка:

sjcl.codec.base64._padchar = "=";

Преобразование между hex и base64

Поскольку обе кодировки работают через bitArray, конвертация происходит через промежуточное представление:

hex → base64

var bits = sjcl.codec.hex.toBits("68656c6c6f");
var b64 = sjcl.codec.base64.fromBits(bits);

base64 → hex

var bits = sjcl.codec.base64.toBits("aGVsbG8=");
var hex = sjcl.codec.hex.fromBits(bits);

Преобразование байтов (Uint8Array) и SJCL

SJCL не использует Uint8Array напрямую, но часто требуется взаимодействие.

Uint8Array → bitArray

function bytesToBits(bytes) {
    var out = [];
    var i, tmp = 0;

    for (i = 0; i < bytes.length; i++) {
        tmp = tmp << 8 | bytes[i];
        if ((i & 3) === 3) {
            out.push(tmp);
            tmp = 0;
        }
    }

    if (i & 3) {
        tmp = tmp << (8 * (4 - (i & 3)));
        out.push(tmp);
    }

    out.push(bytes.length * 8);
    return out;
}

bitArray → Uint8Array

function bitsToBytes(arr) {
    var len = arr[arr.length - 1];
    var out = [];
    var i, j = 0;

    for (i = 0; i < len; i += 8) {
        var word = arr[i >> 5];
        out.push((word >> (24 - (i % 32))) & 0xFF);
    }

    return new Uint8Array(out);
}

Внутренние особенности SJCL кодеков

Все кодеки в SJCL следуют одному принципу:

  • вход: string | hex | base64
  • выход: bitArray
  • обратная операция — из bitArray

Общая схема

string / hex / base64
        ↓
     bitArray
        ↓
string / hex / base64

UTF-8 и проблемы совместимости

UTF-8 кодек SJCL строго следует стандарту, но важно учитывать:

  • JavaScript строки — UTF-16
  • SJCL конвертирует UTF-16 → UTF-8 вручную
  • суррогатные пары могут усложнять обработку

Пример проблемного символа:

var text = "?"; // редкий Unicode символ
var bits = sjcl.codec.utf8String.toBits(text);
var back = sjcl.codec.utf8String.fromBits(bits);

Побайтовое представление через hex

Hex часто используют для визуальной проверки данных:

var data = "Crypto";
var bits = sjcl.codec.utf8String.toBits(data);
var hex = sjcl.codec.hex.fromBits(bits);

console.log(hex);

Вывод:

43727970746f

Каждая пара символов — один байт.


Base64 как транспортный формат

Base64 в SJCL особенно удобен для:

  • передачи хешей
  • сериализации ключей
  • хранения ciphertext

Пример:

var key = sjcl.codec.utf8String.toBits("secret-key");
var encoded = sjcl.codec.base64.fromBits(key);

Ошибки при преобразованиях

1. Несовпадение кодеков

sjcl.codec.hex.toBits("hello"); // ошибка

hex принимает только корректные шестнадцатеричные строки.


2. Потеря длины bitArray

Если вручную модифицировать массив и забыть последний элемент:

arr.push(length);

SJCL не сможет корректно декодировать данные.


3. Некорректный base64

sjcl.codec.base64.toBits("!!!");

приведёт к исключению.


Практический пример: полный цикл преобразований

var sjcl = require("sjcl");

var input = "cryptographic data";

// string → bitArray
var bits = sjcl.codec.utf8String.toBits(input);

// bitArray → hex
var hex = sjcl.codec.hex.fromBits(bits);

// hex → bitArray
var bits2 = sjcl.codec.hex.toBits(hex);

// bitArray → base64
var b64 = sjcl.codec.base64.fromBits(bits2);

// base64 → string
var output = sjcl.codec.utf8String.fromBits(
    sjcl.codec.base64.toBits(b64)
);

console.log(input);
console.log(hex);
console.log(b64);
console.log(output);

Почему SJCL использует bitArray вместо Uint8Array

Причины архитектурные:

  • оптимизация под 32-битные операции (AES, SHA)
  • уменьшение числа операций доступа к памяти
  • единый формат для всех криптопримитивов
  • совместимость с JavaScript до появления TypedArray

Рекомендации при работе с кодировками

  • всегда явно контролировать входной формат
  • использовать UTF-8 кодек для строк
  • не смешивать hex/base64 без промежуточного bitArray
  • избегать ручного редактирования bitArray
  • проверять длину данных после преобразований

Типичные сценарии использования преобразований

  • хранение хешей в hex
  • передача ключей в base64
  • сериализация зашифрованных данных
  • подготовка данных для HMAC/AES
  • взаимодействие с API, ожидающими текстовые форматы