Подпись файлов и бинарных данных

Работа с бинарными данными в JavaScript-окружении

В криптографических операциях основой является работа с точными байтовыми последовательностями. В JavaScript такие данные чаще всего представлены в виде ArrayBuffer, Uint8Array или строк в формате Base64/Hex.

Библиотека Jsrsasign оперирует в основном строковыми представлениями, поэтому перед подписью бинарных данных требуется корректное преобразование.

Ключевые форматы представления:

  • Hex (шестнадцатеричное представление)
  • Base64
  • Binary string (строка, где каждый символ соответствует байту)

Наиболее универсальным вариантом считается Base64, так как он стабильно работает в браузерах и Node.js и не зависит от кодировок.


Подготовка данных к подписи

Перед формированием цифровой подписи бинарные данные должны быть преобразованы в строку, пригодную для криптографического хеширования.

Типичный сценарий включает:

  1. Получение ArrayBuffer
  2. Преобразование в Uint8Array
  3. Кодирование в Base64
  4. Передача в алгоритм подписи

Пример преобразования:

function arrayBufferToBase64(buffer) {
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const len = bytes.byteLength;

  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  return btoa(binary);
}

Подпись бинарных данных с использованием RSA

Jsrsasign предоставляет высокоуровневый API через KJUR.crypto.Signature.

Алгоритм подписи зависит от выбранной хеш-функции:

  • SHA256withRSA (наиболее распространённый вариант)
  • SHA1withRSA (устаревший, но поддерживается)
  • SHA512withRSA (для повышенной криптостойкости)

Пример создания подписи:

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withRSA"
});

sig.init(privateKeyPem);

const base64Data = arrayBufferToBase64(fileArrayBuffer);

sig.updateString(base64Data);

const signature = sig.sign();

Особенности работы с файлами

При работе с файлами важно учитывать, что подпись должна отражать точное содержимое, а не его визуальное представление.

Типичный процесс в браузере:

const fileInput = document.querySelector("input[type=file]");

fileInput.addEventListener("change", async (event) => {
  const file = event.target.files[0];

  const arrayBuffer = await file.arrayBuffer();
  const base64Data = arrayBufferToBase64(arrayBuffer);

  const sig = new KJUR.crypto.Signature({
    alg: "SHA256withRSA"
  });

  sig.init(privateKeyPem);
  sig.updateString(base64Data);

  const signature = sig.sign();
});

При этом важно, что:

  • используется строго бинарное содержимое файла
  • метаданные (имя, дата, MIME) не участвуют в подписи
  • любые изменения в байтах полностью меняют подпись

Подпись через SHA-256 хеширование

В ряде сценариев применяется предварительное хеширование данных перед подписью.

Это снижает нагрузку на алгоритм RSA и позволяет работать с большими файлами.

Пример:

const md = new KJUR.crypto.MessageDigest({ alg: "sha256", prov: "cryptojs" });
md.updateHex(KJUR.lang.String.fromCharCode(...new Uint8Array(arrayBuffer)));

const hash = md.digest();

Далее подпись формируется уже от хеша:

const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateHex(hash);

const signature = sig.sign();

Подпись бинарных данных в формате Hex

Hex-представление часто используется при работе с низкоуровневыми криптографическими системами.

Преобразование:

function bufferToHex(buffer) {
  return Array.from(new Uint8Array(buffer))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

Использование:

const hexData = bufferToHex(arrayBuffer);

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withRSA"
});

sig.init(privateKeyPem);
sig.updateHex(hexData);

const signature = sig.sign();

Hex позволяет избежать проблем с бинарными символами и кодировками.


Проверка подписи бинарных данных

Проверка выполняется с использованием публичного ключа.

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withRSA"
});

sig.init(publicKeyPem);

sig.updateString(base64Data);

const isValid = sig.verify(signature);

Ключевые условия корректной проверки:

  • идентичный алгоритм подписи и проверки
  • полное совпадение входных данных
  • отсутствие преобразований между подписью и проверкой

Работа с большими файлами

При обработке больших бинарных данных возникают ограничения памяти, особенно в браузере.

Практика обработки включает:

  • потоковое чтение (streaming)
  • разбиение данных на чанки
  • постепенное обновление хеша

Пример концепции чанков:

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withRSA"
});

sig.init(privateKeyPem);

for (const chunk of chunks) {
  sig.updateString(arrayBufferToBase64(chunk));
}

const signature = sig.sign();

Совместимость форматов подписи

Jsrsasign поддерживает несколько стандартов криптографических форматов:

  • PKCS#1 v1.5 (по умолчанию для RSA)
  • PKCS#8 (для ключей)
  • X.509 (для сертификатов)

Подписи, сформированные библиотекой, совместимы с:

  • OpenSSL
  • Java Security API
  • Node.js crypto
  • большинство JWT-библиотек

Типичные ошибки при подписи бинарных данных

Часто возникающие проблемы связаны не с алгоритмом, а с преобразованием данных:

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

Корректная схема всегда опирается на неизменяемое представление массива байтов.


Использование подписи для контроля целостности файлов

Цифровая подпись позволяет фиксировать состояние файла в момент подписания.

Любое изменение:

  • добавление байта
  • удаление байта
  • изменение кодировки

приводит к полной невалидности подписи.

Поэтому структура проверки всегда включает:

  1. получение файла
  2. преобразование в байтовый массив
  3. повторное вычисление подписи
  4. сравнение с эталонной

Применение в прикладных системах

Подпись бинарных данных используется в следующих сценариях:

  • проверка загрузок файлов
  • цифровая подпись документов
  • защита API-запросов с вложениями
  • валидация прошивок и обновлений
  • криптографическая фиксация состояния данных

Jsrsasign позволяет реализовать эти механизмы без зависимости от внешних криптосервисов, используя чистый JavaScript-стек.