Итак, как говорится в заголовке, я пытаюсь реализовать сервис на стороне клиента с 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.