Как реализовать шифрование RSA на фронтенде

Вопросы и ответы

Итак, как говорится в заголовке, я пытаюсь реализовать сервис на стороне клиента с SvelteKit, который шифрует данные пользователя, отправляемые на сервер, созданный с помощью Express.

Я пытаюсь достичь этого с помощью шифрования RSA, которое я нашел на нескольких форумах.

Дело в том, что на сервере это работает совершенно прекрасно:

Шифрование:

import { constants, publicEncrypt } from 'node:crypto';
import { readFileSync } from 'node:fs';

export const encryptData = (data: string) => {
  const publicKey = readFileSync('public.pem');
  const encryptedData = publicEncrypt(
    {
      key: publicKey,
      padding: constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: 'sha256',
    },
    Buffer.from(data),
  );
  return encryptedData.toString('base64');
};

Дешифрование:

import { constants, privateDecrypt } from 'node:crypto';
import { readFileSync } from 'node:fs';

export const decryptData = (data: string) => {
  const privateKey = readFileSync('private.pem');
  const decryptedData = privateDecrypt(
    {
      key: privateKey,
      padding: constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: 'sha256',
    },
    Buffer.from(data, 'base64'),
  );
  return decryptedData.toString();
};

Но на фронтенде я не могу найти способ заставить сервер принимать данные и парсить их в JSON. Вот последний код, который я пытался реализовать:

Шифрование:

import axios from 'axios';

async function retrieveRsaKey() {
  const BACKEND = import.meta.env.VITE_SERVER_URL;
  const PUBLIC_RSA_KEY = await axios.get(BACKEND + '/public_rsa').then((res) => res.data);
  return PUBLIC_RSA_KEY;
}

export async function encryptData (data: {[key:string]: unknown}) {
  const parsedData = JSON.stringify(data)
  const crypto = new Crypto;
  crypto.subtle.encrypt({name: "RSA-OAEP"}, await retrieveRsaKey(), parsedData)
}

Знаете ли вы, как я могу это реализовать? Я искал эту тему около двух дней и не смог найти ничего работающего только на JS или TS. Некоторые даже полагаются на PHP или Python для выполнения шифрования.

Что я пытался сделать?

  • Использовать “web crypto api”
  • Искать пакеты, которые имитируют функции в “node:crypto”

Что я ожидаю?

  • Сервис, который принимает JSON с информацией о пользователе и создает зашифрованное сообщение для сервера с помощью ПУБЛИЧНОГО RSA КЛЮЧА.

Ответ

Для реализации RSA шифрования на стороне клиента с использованием SvelteKit и Express, вам нужно выполнить несколько шагов. Важно понимать, что открытый ключ RSA нужно будет получить от сервера, а затем использовать его для шифрования данных перед отправкой. Я подробно объясню, как это сделать, включая все необходимые шаги.

1. Получение открытого ключа RSA с сервера

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

// server.js (или другой файл вашего сервера)
import express from 'express';
import { readFileSync } from 'fs';

const app = express();
const PORT = process.env.PORT || 3000;

// Эндпоинт для получения открытого ключа
app.get('/public_rsa', (req, res) => {
  const publicKey = readFileSync('public.pem', 'utf-8');
  res.send(publicKey);
});

app.listen(PORT, () => {
  console.log(`Сервер запущен на порту ${PORT}`);
});

2. Шифрование данных на стороне клиента

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

Вот пример, как это можно реализовать:

import axios from 'axios';

async function retrieveRsaKey() {
    const BACKEND = import.meta.env.VITE_SERVER_URL; // Убедитесь, что эта переменная настроена.
    const PUBLIC_RSA_KEY = await axios.get(BACKEND + '/public_rsa').then(res => res.data);

    // Импортируем открытый ключ в формат для Crypto API
    const key = await window.crypto.subtle.importKey(
        'spki', 
        base64ToArrayBuffer(PUBLIC_RSA_KEY), 
        {
            name: 'RSA-OAEP',
            hash: { name: 'SHA-256' }
        },
        false, 
        ['encrypt']
    );

    return key;
}

function base64ToArrayBuffer(base64) {
    const binaryString = window.atob(base64);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
}

export async function encryptData(data) {
    const parsedData = new TextEncoder().encode(JSON.stringify(data));
    const rsaKey = await retrieveRsaKey();

    const encryptedData = await window.crypto.subtle.encrypt(
        {
            name: 'RSA-OAEP'
        },
        rsaKey,
        parsedData
    );

    return arrayBufferToBase64(encryptedData);
}

function arrayBufferToBase64(buffer) {
    let binary = '';
    const bytes = new Uint8Array(buffer);
    const len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
}

3. Отправка зашифрованных данных на сервер

Теперь, когда у вас есть функция encryptData, которая принимает объекты и возвращает зашифрованные строки, вы можете использовать axios или fetch для отправки зашифрованных данных на сервер.

Пример отправки данных:

// Пример вызова функции
const userData = { username: 'example', password: 'securePassword' };
const encryptedData = await encryptData(userData);

await axios.post(import.meta.env.VITE_SERVER_URL + '/your-endpoint', {
    data: encryptedData
});

4. Обработка зашифрованных данных на сервере

На сервере вам нужно будет использовать функцию decryptData, чтобы расшифровать данные, поступающие от клиента.

// app.js (или другой файл вашего сервера)
import express from 'express';
import bodyParser from 'body-parser';
// Импортируйте вашу функцию decryptData

const app = express();
app.use(bodyParser.json({ limit: '1mb' })); // Убедитесь, что размер тела достаточно большой

app.post('/your-endpoint', (req, res) => {
    const encryptedData = req.body.data;
    const decryptedData = decryptData(encryptedData);
    console.log(decryptedData); // Теперь у вас есть расшифрованные данные
    res.send({ success: true });
});

Заключение

Теперь у вас есть полное решение для шифрования данных на стороне клиента с использованием RSA и отправки этих данных на сервер. Не забудьте протестировать ваше приложение и убедиться, что все работает корректно. Если будут возникать ошибки, проверьте форматы ключей и данных, а также корректность используемых URL.

Оцените материал
Добавить комментарий

Капча загружается...