Принципы цифровой подписи

Криптографическая подпись в Web Crypto API строится на асимметричной модели, в которой используется пара ключей: закрытый для создания подписи и открытый для её проверки. Основное свойство такой схемы — невозможность вычислить корректную подпись без владения приватным ключом, при этом любой обладатель публичного ключа может убедиться в подлинности данных.

Цифровая подпись представляет собой результат криптографического преобразования хеш-значения сообщения с использованием закрытого ключа. В большинстве современных схем подписи не подписывается сам текст напрямую — сначала вычисляется хеш, затем он криптографически защищается.

Фундаментальные свойства цифровой подписи:

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

Web Crypto API реализует эти свойства через интерфейс SubtleCrypto.

Алгоритмы Web Crypto API

В браузерной криптографии поддерживаются несколько алгоритмов цифровой подписи:

  • RSASSA-PKCS1-v1_5 — классический RSA с фиксированным padding
  • RSA-PSS — современная схема RSA с вероятностным паддингом
  • ECDSA — эллиптические кривые, компактные ключи и подписи
  • HMAC — хотя используется для аутентификации сообщений, а не классической цифровой подписи

Наиболее распространённые в современных системах:

  • RSA-PSS — предпочтителен для RSA
  • ECDSA — эффективен по размеру и скорости

Генерация ключевых пар

Перед подписанием данных необходимо создать ключевую пару. В Web Crypto API это выполняется через crypto.subtle.generateKey.

Пример генерации ключей ECDSA:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,
  ["sign", "verify"]
);

Параметры:

  • name — алгоритм
  • namedCurve — кривая (P-256, P-384, P-521)
  • extractable — можно ли экспортировать ключ
  • keyUsages — назначение ключей

RSA-PSS пример:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-PSS",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["sign", "verify"]
);

Здесь важны параметры:

  • modulusLength — длина RSA ключа
  • publicExponent — обычно 65537
  • hash — хеш-функция, используемая внутри схемы

Подписание данных

Подписание осуществляется методом crypto.subtle.sign. Данные должны быть представлены в виде ArrayBuffer.

Пример ECDSA подписи:

const encoder = new TextEncoder();
const data = encoder.encode("Сообщение для подписи");

const signature = await crypto.subtle.sign(
  {
    name: "ECDSA",
    hash: "SHA-256"
  },
  privateKey,
  data
);

Для RSA-PSS:

const signature = await crypto.subtle.sign(
  {
    name: "RSA-PSS",
    saltLength: 32
  },
  privateKey,
  data
);

Результат — бинарная подпись в формате ArrayBuffer.

Проверка подписи

Проверка выполняется через crypto.subtle.verify.

const isValid = await crypto.subtle.verify(
  {
    name: "ECDSA",
    hash: "SHA-256"
  },
  publicKey,
  signature,
  data
);

Возвращаемое значение — булево:

  • true — подпись корректна
  • false — данные изменены или ключ не соответствует

Хеширование и предобработка

Во многих схемах подписи Web Crypto API хеширование выполняется автоматически внутри алгоритма. Однако концептуально процесс выглядит так:

  1. Преобразование данных в байтовый формат
  2. Вычисление хеша (SHA-256, SHA-384 и т.д.)
  3. Криптографическое преобразование хеша

Хеш-функция играет критическую роль:

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

Форматы ключей и представление данных

Web Crypto API работает с несколькими форматами ключей:

  • SPKI — публичные ключи
  • PKCS8 — приватные ключи
  • JWK — JSON Web Key

Экспорт публичного ключа:

const exported = await crypto.subtle.exportKey(
  "spki",
  publicKey
);

Импорт:

const key = await crypto.subtle.importKey(
  "spki",
  exported,
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,
  ["verify"]
);

JWK формат удобен для передачи через API:

const jwk = await crypto.subtle.exportKey("jwk", publicKey);

RSA-PSS и ECDSA: различия моделей

RSA-PSS

RSA-PSS основан на факторизации больших чисел. Его особенности:

  • высокая криптостойкость при больших ключах
  • вычислительно более затратен
  • использует salt для повышения безопасности
  • широко поддерживается в инфраструктуре PKI

Ключевая особенность — вероятностный характер подписи: одинаковые данные дают разные подписи.

ECDSA

ECDSA основан на эллиптических кривых:

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

Недостаток — чувствительность к качеству генерации случайных чисел. Ошибки RNG могут привести к утечке приватного ключа.

Хеширование как основа подписи

Хеш-функции, используемые в Web Crypto API:

  • SHA-256 — стандарт де-факто
  • SHA-384 — повышенная стойкость
  • SHA-512 — высокая криптостойкость

Хеширование выполняет роль “сжатия” данных до фиксированного размера перед подписью.

Внутренний принцип:

signature = Sign(privateKey, Hash(message))

Ошибки и типичные проблемы реализации

На практике при работе с Web Crypto API часто возникают системные ошибки:

Несоответствие алгоритмов

Подпись и проверка должны использовать одинаковые параметры:

  • одинаковый hash
  • одинаковый namedCurve или параметры RSA
  • одинаковый алгоритм (ECDSA, RSA-PSS)

Неверный формат данных

Web Crypto API не работает со строками напрямую:

  • необходимо использовать ArrayBuffer
  • требуется TextEncoder / TextDecoder

Потеря бинарных данных

Подпись — это бинарный массив. При преобразовании в строку без кодирования Base64 происходит повреждение данных.

Неправильный saltLength в RSA-PSS

SaltLength должен соответствовать криптографической конфигурации и часто равен длине хеша.

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

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

Изоляция приватных ключей

Закрытый ключ не должен:

  • экспортироваться без необходимости
  • покидать контекст безопасного хранения
  • передаваться через небезопасные каналы

Контроль жизненного цикла ключей

Ключи должны:

  • регулярно ротироваться
  • иметь ограниченное время использования
  • храниться в защищённом контексте (HTTPS, secure origin)

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

Перед подписанием необходимо обеспечить:

  • стабильную сериализацию данных
  • отсутствие скрытых изменений формата
  • единый порядок байтов

Выбор алгоритма

Рекомендуемые варианты:

  • ECDSA P-256 + SHA-256 — баланс производительности и безопасности
  • RSA-PSS 2048/3072 + SHA-256 — совместимость с legacy-системами

Сравнение подписей только через API

Нельзя сравнивать подписи как строки. Корректный способ — использование crypto.subtle.verify.


Цифровая подпись в Web Crypto API является низкоуровневым механизмом, в котором безопасность определяется не только криптографическим алгоритмом, но и строгим соблюдением форматов данных, параметров ключей и корректной обработкой бинарных структур.