Цифровая подпись используется для подтверждения подлинности данных и их неизменности. В контексте веб-приложений это особенно важно для токенов аутентификации, API-запросов и документов, передаваемых между клиентом и сервером. Библиотека SJCL (Stanford Javascript Crypto Library) предоставляет инструменты для реализации подписи на стороне JavaScript с использованием проверенных криптографических алгоритмов.
Основная задача подписи — гарантировать:
SJCL не предоставляет высокоуровневого API для «подписания токенов» как готовой функции, но включает базовые криптографические примитивы:
Подпись строится на комбинации:
Для работы с подписями используется асимметричная криптография. Генерация ключей выполняется следующим образом:
const keys = sjcl.ecc.ecdsa.generateKeys(256);
const privateKey = keys.sec;
const publicKey = keys.pub;
privateKey — используется для создания подписиpublicKey — используется для проверки подписиКлючи основаны на эллиптической кривой (обычно
c256).
Перед подписанием данные необходимо привести к хэшированному виду:
const message = "important data";
const hash = sjcl.hash.sha256.hash(message);
Затем выполняется подпись:
const signature = privateKey.sign(hash);
Результат — массив чисел (битовое представление подписи), который обычно кодируется:
const signatureBase64 = sjcl.codec.base64.fromBits(signature);
Проверка выполняется с использованием публичного ключа:
const isValid = publicKey.verify(hash, signature);
true — подпись корректнаfalse — подпись недействительнаВажно: проверка должна выполняться на стороне, которой доверяют (обычно сервер).
SJCL можно использовать для создания собственных токенов, аналогичных JWT:
const header = { alg: "ES256", typ: "JWT" };
const payload = { userId: 123, exp: Date.now() + 3600000 };
const encodedHeader = btoa(JSON.stringify(header));
const encodedPayload = btoa(JSON.stringify(payload));
const data = encodedHeader + "." + encodedPayload;
const hash = sjcl.hash.sha256.hash(data);
const signature = privateKey.sign(hash);
const encodedSignature = sjcl.codec.base64.fromBits(signature);
const token = data + "." + encodedSignature;
const parts = token.split(".");
const data = parts[0] + "." + parts[1];
const hash = sjcl.hash.sha256.hash(data);
const signatureBits = sjcl.codec.base64.toBits(parts[2]);
const isValid = publicKey.verify(hash, signatureBits);
Для документов (JSON, текст, бинарные данные) принцип аналогичен:
const document = JSON.stringify({
contractId: 42,
amount: 1000
});
const hash = sjcl.hash.sha256.hash(document);
const signature = privateKey.sign(hash);
Подпись хранится отдельно или вместе с документом:
{
"document": { ... },
"signature": "BASE64_SIGNATURE"
}
Ключевая проблема клиентской криптографии — защита приватного ключа:
Пример шифрования ключа:
const password = "strong_password";
const encryptedKey = sjcl.encrypt(password, JSON.stringify(privateKey));
Добавление временной метки:
payload.timestamp = Date.now();
Жёсткая проверка alg в header:
if (header.alg !== "ES256") throw new Error("Invalid algorithm");
Всегда проверяется подпись перед использованием данных.
Однако:
| Характеристика | SJCL | Web Crypto API |
|---|---|---|
| Поддержка браузеров | Широкая | Современные браузеры |
| Производительность | Ниже | Выше (нативная) |
| Простота API | Средняя | Сложнее |
| Контроль | Полный | Ограниченный |
SJCL подходит, когда требуется:
const data = field1 + "|" + field2 + "|" + field3;
const bits = sjcl.codec.arrayBuffer.toBits(buffer);
const hash = sjcl.hash.sha256.hash(bits);
Добавление версии схемы:
payload.version = 1;
Чтобы избежать проблем с различиями в форматировании:
function canonicalize(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
Обычно:
Для повышения безопасности:
SJCL активно использует преобразования:
sjcl.codec.base64sjcl.codec.hexsjcl.codec.utf8StringПример:
const bits = sjcl.codec.utf8String.toBits("data");
const base64 = sjcl.codec.base64.fromBits(bits);
Добавление поля exp:
if (Date.now() > payload.exp) {
throw new Error("Token expired");
}
Подпись может применяться к цепочке (например, блокам):
const blockHash = sha256(previousHash + data);
Это используется в:
Каждый этап критичен: ошибка в любом из них делает подпись бесполезной или уязвимой.