Подписание сообщения

Криптографическая подпись в SJCL строится на алгоритме ECDSA (Elliptic Curve Digital Signature Algorithm), который используется для подтверждения целостности и подлинности сообщения без раскрытия приватного ключа. В библиотеке Stanford JS Crypto Library подписи реализуются через модуль sjcl.ecc, работающий с эллиптическими кривыми и объектами ключей.

Криптографическая подпись решает две задачи:

  • подтверждение, что сообщение создано владельцем приватного ключа;
  • гарантия, что сообщение не было изменено после подписания.

В SJCL подпись формируется не напрямую от строки, а от криптографического хеша сообщения. Это критически важно: ECDSA работает с фиксированной длиной входа.

Типичный поток выглядит так:

  1. Сообщение → хеш (SHA-256)
  2. Хеш → подпись приватным ключом
  3. Подпись → проверка публичным ключом

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

SJCL предоставляет встроенные хеш-функции, например SHA-256:

var msg = "Сообщение для подписи";

var hashBits = sjcl.hash.sha256.hash(msg);

Внутренний формат SJCL — это массив 32-битных слов (bitArray). Именно он используется всеми криптографическими примитивами библиотеки.


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

Для работы с подписями используется модуль sjcl.ecc.ecdsa.

var curve = sjcl.ecc.curves.k256;

var keys = sjcl.ecc.ecdsa.generateKeys(curve, 0);

Структура результата:

  • keys.sec — приватный ключ
  • keys.pub — публичный ключ

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


Подписание сообщения

Подпись формируется на основе хеша:

var signature = keys.sec.sign(hashBits);

signature — это объект, содержащий координаты подписи (обычно r и s), закодированные в формате bitArray.

Для сериализации в строку часто используется кодек Base64:

var sigString = sjcl.codec.base64.fromBits(signature);

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

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

var isValid = keys.pub.verify(hashBits, signature);

Результат:

  • true — подпись корректна
  • false — сообщение или подпись изменены

Полный пример процесса подписи

var msg = "Данные, требующие подписи";

// 1. Хеширование
var hash = sjcl.hash.sha256.hash(msg);

// 2. Генерация ключей
var keys = sjcl.ecc.ecdsa.generateKeys(sjcl.ecc.curves.k256, 0);

// 3. Подпись
var sig = keys.sec.sign(hash);

// 4. Проверка
var valid = keys.pub.verify(hash, sig);

Сериализация и хранение подписи

SJCL использует внутренний формат bitArray, который неудобен для передачи по сети. Обычно применяется кодирование:

Base64

var encodedSig = sjcl.codec.base64.fromBits(sig);
var decodedSig = sjcl.codec.base64.toBits(encodedSig);

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

Для хранения часто разделяют компоненты подписи:

var sigObj = {
  r: sjcl.codec.base64.fromBits(sig[0]),
  s: sjcl.codec.base64.fromBits(sig[1])
};

И восстановление:

var sig = [
  sjcl.codec.base64.toBits(sigObj.r),
  sjcl.codec.base64.toBits(sigObj.s)
];

Использование фиксированного сообщения

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

sjcl.hash.sha256.hash("A") !== sjcl.hash.sha256.hash("a");

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

  • фиксированный порядок полей JSON
  • отсутствие лишних пробелов
  • единая кодировка UTF-8

Подпись структурированных данных

При работе с объектами JavaScript данные сначала сериализуются:

var data = {
  user: "alice",
  role: "admin"
};

var serialized = JSON.stringify(data);
var hash = sjcl.hash.sha256.hash(serialized);

var sig = keys.sec.sign(hash);

Без детерминированной сериализации возможны несовпадения подписи при повторном вычислении.


Проверка подписи на стороне получателя

function verifyMessage(msg, sig, pubKey) {
  var hash = sjcl.hash.sha256.hash(msg);
  return pubKey.verify(hash, sig);
}

Если сообщение изменено хотя бы на один символ, проверка провалится.


Ошибки при работе с подписями

1. Подпись не от хеша

Неправильный подход:

keys.sec.sign(msg); // ошибка логики

Правильный:

keys.sec.sign(sjcl.hash.sha256.hash(msg));

2. Несовпадение сериализации

JSON без строгого порядка полей приводит к разным хешам:

JSON.stringify({a:1, b:2}) !== JSON.stringify({b:2, a:1})

3. Повторное использование nonce (критическая ошибка ECDSA)

Хотя SJCL управляет этим внутри реализации, в ручных схемах повтор nonce приводит к утечке приватного ключа.


Кривая secp256k1 и совместимость

SJCL поддерживает кривую k256, которая эквивалентна secp256k1 (используется в Bitcoin и многих криптосистемах):

sjcl.ecc.curves.k256

Это делает подписи совместимыми с внешними системами при правильной упаковке форматов.


Практическая схема подписи в протоколах

Типичный протокол выглядит так:

  1. Формирование сообщения
  2. Сериализация в каноническую строку
  3. Хеширование SHA-256
  4. Подпись приватным ключом
  5. Передача: { message, signature }
  6. Проверка на стороне получателя

Восстановление публичного ключа и проверка доверия

SJCL позволяет работать с экспортируемыми ключами:

var pubBits = keys.pub.serialize();
var pubKey = sjcl.ecc.deserialize(pubBits);

После чего:

pubKey.verify(hash, signature);

Формат внутреннего представления подписи

ECDSA в SJCL хранит подпись как пару значений:

  • r — первая компонента
  • s — вторая компонента

Обе величины являются числами в поле эллиптической кривой и кодируются в bitArray.


Криптографическая устойчивость схемы

ECDSA в SJCL обеспечивает:

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

Критическая безопасность зависит не только от алгоритма, но и от:

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

Подпись как часть более сложных систем

В реальных приложениях подпись используется вместе с:

  • HMAC для быстрых проверок целостности
  • AES для шифрования данных
  • ECC для обмена ключами

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