Цифровые подписи: принципы работы

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

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

  • Закрытый ключ — применяется для создания подписи и должен оставаться конфиденциальным.
  • Открытый ключ — используется для проверки подписи и может свободно распространяться.

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

H(m)

Хеш-функция преобразует сообщение m в фиксированное значение H(m). Затем это значение используется в алгоритме подписи.

Общая схема формирования подписи

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

  1. Вычисляется хеш сообщения.
  2. Хеш шифруется закрытым ключом.
  3. Полученный результат становится цифровой подписью.

При проверке выполняется обратная операция:

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

Если они совпадают, подпись считается действительной.

JWS как формат цифровых подписей

В экосистеме JavaScript стандартом представления подписанных данных выступает JSON Web Signature (JWS). Он определяет структуру, в которой хранятся:

  • заголовок (header)
  • полезная нагрузка (payload)
  • подпись (signature)

Формат позволяет передавать подписанные данные в компактном виде, часто в виде строки Base64URL.

Заголовок обычно содержит информацию об алгоритме:

{
  "alg": "RS256",
  "typ": "JWT"
}

Payload содержит данные, которые подписываются. Signature создаётся на основе header и payload.

Алгоритмы цифровой подписи

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

  • RS256 — RSA с SHA-256
  • ES256 — ECDSA на кривой P-256
  • EdDSA — подписи на основе Ed25519

Каждый алгоритм отличается характеристиками:

  • RSA обеспечивает широкую совместимость
  • ECDSA уменьшает размер ключей при сопоставимой стойкости
  • EdDSA ориентирован на высокую скорость и устойчивость к ошибкам реализации

Формирование подписи с использованием jose

Библиотека jose предоставляет низкоуровневые и высокоуровневые API для работы с JWS.

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

import { SignJWT, generateKeyPair } from 'jose'

const { privateKey } = await generateKeyPair('RS256')

const jwt = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: 'RS256' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .sign(privateKey)

В этом процессе:

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

Проверка цифровой подписи

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

import { jwtVerify } from 'jose'

const { payload } = await jwtVerify(token, publicKey)

console.log(payload)

Если подпись изменена или сообщение было модифицировано, проверка завершится ошибкой.

Структура JWS Compact Serialization

Компактное представление JWS состоит из трёх частей:

header.payload.signature

Каждая часть кодируется в Base64URL. Пример:

eyJhbGciOiJSUzI1NiJ9.eyJ1c2VySWQiOjEyM30.Kl3...abc

Изменение любой части приводит к недействительности подписи.

Математическая модель подписи

С точки зрения криптографии процесс можно формализовать:

= (H(m), private key)

и проверка:

(H(m), signature, public key)

Свойства цифровых подписей

Криптографическая подпись обеспечивает три ключевых свойства:

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

Эти свойства достигаются за счёт математической связи между закрытым и открытым ключами.

Работа с ключами в jose

Генерация ключевой пары:

import { generateKeyPair } from 'jose'

const { publicKey, privateKey } = await generateKeyPair('ES256')

Экспорт и импорт ключей:

import { exportJWK, importJWK } from 'jose'

const jwk = await exportJWK(publicKey)
const key = await importJWK(jwk, 'ES256')

Использование формата JWK упрощает хранение и передачу ключей в распределённых системах.

Уязвимости и ошибки реализации

На практике цифровые подписи часто нарушаются не из-за криптографии, а из-за неправильной реализации:

  • использование слабых алгоритмов (например, SHA-1)
  • повторное использование nonce в ECDSA
  • отсутствие проверки алгоритма (alg confusion attack)
  • хранение закрытого ключа в открытом виде
  • некорректная валидация payload

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

Принцип неизменяемости подписи

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

H(m) H(m + )

Эта особенность делает цифровые подписи устойчивыми к подделке без доступа к закрытому ключу.

Роль хеш-функций в подписи

Хеш-функция выступает промежуточным слоем между данными и криптографическим алгоритмом. Её свойства:

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

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

Применение в JWT

JWT (JSON Web Token) использует JWS для защиты данных. Подпись гарантирует, что:

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

В связке с jose JWT формируется и проверяется через единый криптографический механизм, основанный на цифровых подписях.