Практика: шифрование данных перед отправкой

В браузерных и Node.js-приложениях библиотека Jsrsasign используется для реализации криптографических операций без необходимости подключения нативных модулей. Она поддерживает RSA, ECDSA, HMAC, SHA-хеширование, а также работу с X.509 сертификатами и форматом JWS/JWT.

Подключение в браузере:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.8.6/jsrsasign-all-min.js"></script>

В Node.js:

const jsrsasign = require("jsrsasign");
const { KEYUTIL, KJUR } = jsrsasign;

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


Генерация и загрузка RSA-ключей

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

Создание ключевой пары:

const rsaKeypair = KEYUTIL.generateKeypair("RSA", 2048);

const privateKey = rsaKeypair.prvKeyObj;
const publicKey = rsaKeypair.pubKeyObj;

Экспорт ключей в PEM-формат:

const privatePEM = KEYUTIL.getPEM(privateKey, "PKCS8PRV");
const publicPEM = KEYUTIL.getPEM(publicKey);

Загрузка ключа из PEM:

const pubKey = KEYUTIL.getKey(publicPEM);
const prvKey = KEYUTIL.getKey(privatePEM);

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


Шифрование данных с помощью RSA

Базовое шифрование небольшого сообщения:

const pubKey = KEYUTIL.getKey(publicPEM);

const encrypted = pubKey.encrypt("секретное сообщение");
const encryptedBase64 = hextob64(encrypted);

Расшифрование:

const prvKey = KEYUTIL.getKey(privatePEM);

const decryptedHex = prvKey.decrypt(b64tohex(encryptedBase64));
console.log(decryptedHex);

Такой подход применяется редко в чистом виде из-за ограничений RSA и высокой вычислительной стоимости.


Гибридное шифрование: практический стандарт

Реальные системы используют комбинацию:

  • AES для шифрования данных
  • RSA для защиты AES-ключа

Генерация AES-ключа

const aesKey = KJUR.crypto.Util.getRandomHexOfNbytes(16); // 128-bit key

Шифрование данных AES

const plainText = "Конфиденциальные данные пользователя";

const cipher = new KJUR.crypto.Cipher({
  alg: "aes128",
  pass: aesKey
});

const encryptedData = cipher.encrypt(plainText);

Защита AES-ключа с помощью RSA

const pubKey = KEYUTIL.getKey(publicPEM);

const encryptedAesKey = pubKey.encrypt(aesKey);
const encryptedAesKeyB64 = hextob64(encryptedAesKey);

Теперь передаётся два объекта:

  • encryptedData — зашифрованные данные
  • encryptedAesKeyB64 — зашифрованный ключ

Дешифрование на стороне получателя

Восстановление AES-ключа

const prvKey = KEYUTIL.getKey(privatePEM);

const aesKeyHex = prvKey.decrypt(b64tohex(encryptedAesKeyB64));

Расшифрование данных

const cipher = new KJUR.crypto.Cipher({
  alg: "aes128",
  pass: aesKeyHex
});

const decrypted = cipher.decrypt(encryptedData);

Практическая структура защищённого запроса

Типичная структура отправляемого объекта:

const payload = {
  key: encryptedAesKeyB64,
  data: encryptedData,
  algorithm: "RSA + AES-128"
};

Такой формат позволяет серверу:

  1. Расшифровать AES-ключ приватным RSA-ключом
  2. Использовать AES для восстановления данных
  3. Минимизировать нагрузку на асимметричную криптографию

Добавление цифровой подписи

Шифрование скрывает данные, но не подтверждает их подлинность. Для этого используется подпись.

Создание подписи

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

sig.init(privatePEM);
sig.updateString(JSON.stringify(payload));

const signature = sig.sign();
const signatureB64 = hextob64(signature);

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

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

verifier.init(publicPEM);
verifier.updateString(JSON.stringify(payload));

const isValid = verifier.verify(b64tohex(signatureB64));

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


Использование JWT как альтернативы

Jsrsasign поддерживает работу с JSON Web Token:

Создание токена

const header = { alg: "RS256", typ: "JWT" };
const payload = { userId: 123, role: "admin" };

const jwt = KJUR.jws.JWS.sign(
  "RS256",
  JSON.stringify(header),
  JSON.stringify(payload),
  privatePEM
);

Проверка токена

const isValid = KJUR.jws.JWS.verify(jwt, publicPEM, ["RS256"]);

JWT часто заменяет ручную реализацию шифрования в прикладных системах авторизации.


Защита данных при отправке через HTTP

В реальных API-проектах схема выглядит следующим образом:

  1. Клиент генерирует AES-ключ
  2. Шифрует данные AES
  3. Шифрует AES-ключ RSA публичным ключом сервера
  4. Подписывает пакет
  5. Отправляет на сервер

Пример итогового запроса:

fetch("/api/secure", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    key: encryptedAesKeyB64,
    data: encryptedData,
    signature: signatureB64
  })
});

Типичные ошибки при использовании Jsrsasign

Использование RSA для больших данных

RSA подходит только для ключей и коротких строк.

Отсутствие подписи

Шифрование не гарантирует целостность данных.

Повторное использование AES-ключа

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

Игнорирование форматов кодирования

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


Оптимизация производительности

  • AES использовать для всех объёмных данных
  • RSA применять только для обмена ключами
  • Кэшировать публичные ключи сервера
  • Минимизировать количество криптографических операций в UI-потоке

Архитектура безопасного обмена данными

Структурно система выглядит так:

  • Клиент:

    • генерация AES
    • шифрование данных
    • RSA-обёртка ключа
    • подпись
  • Сервер:

    • проверка подписи
    • расшифровка AES
    • обработка данных

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