你开发的聊天软件消息加密了吗
一些成熟的 IM 服务消息传输都是经过加密进行传输的,本文主要实现客户端和服务端的消息加解密实现思路
最近发现一些成熟的 IM 服务消息传输都是经过加密进行传输的,所以使用 RSA 加密对此进行一次尝试
RSA 是一种非对称加密算法,它由三位密码学家 Ron Rivest、Adi Shamir 和 Leonard Adleman 在 1977 年共同提出,以他们姓氏的首字母命名。RSA 算法是目前广泛使用的加密算法之一。 RSA 算法基于两个数学问题的难解性:大素数分解和求模幂运算的逆运算。它的核心思想是使用一对相关的密钥,一个公钥和一个私钥,其中公钥用于加密数据,私钥用于解密数据。公钥可以自由发布给任何人,而私钥则必须保密。
RSA 算法的安全性基于大数分解的困难性。目前没有已知的有效算法可以在合理的时间内分解大的复合数,所以 RSA 算法在实际应用中被广泛用于数据加密、数字签名和密钥交换等领域。
- 本文使用 vite-vue,以及 node 的 koa 开发
- 本文使用 RSA 对数据进行加密
- 本文 web 端使用
jsencrypt生成公钥和私钥,后端使用node-rsa生成
- 初始化目录并安装依赖
npm init pnpm i koa pnpm i ts-node-dev- 创建 index.ts
- 编写 server 测试
const Koa = require('koa');const app = new Koa();
app.use(async (ctx) => { ctx.body = 'Hello World';});
app.listen(3000); pnpm create vite pnpm install --save-dev @arco-design/web-vue- 创建密钥
import JSEncrypt from 'jsencrypt';
export const initConfigRSA = async () => { const crypt = new JSEncrypt(); crypt.getKey(); const privateKey = crypt.getPrivateKey(); const publicKey = crypt.getPublicKey(); return { privateKey, publicKey };};- 加密数据
// 将字符串按照指定长度分割成数组const splitString = (str: string, leng = 10) => { const list = []; let index = 0; while (index < str.length) { list.push(str.slice(index, (index += leng))); } return list;};
export const encryption = (options: Object, key: string) => { //先将发往服务端的数据转成字符串 const str = JSON.stringify(options); const encrypt = new JSEncrypt(); //设置加密使用的公钥 encrypt.setPublicKey(key); let data = ''; //由于加密的数据长度不能超过密钥的长度,所以我们要对加密的数据进行分段加密 const list = splitString(str); for (const iterator of list) { const res = encrypt.encrypt(iterator); console.log(res); if (res) { data = data + res; } } console.log(data); //将字符串数据 const encoder = new TextEncoder(); const res = encoder.encode(data); return res;};- 解密数据
export const decryption = (str: string, key: string) => { const encrypt = new JSEncrypt(); encrypt.setPrivateKey(key); let data = ''; const list = str.split('='); for (const iterator of list) { const res = encrypt.decrypt(iterator); if (res) { data = data + res; } } return data || '';};- 实现效果
- 创建密钥
import NodeRSA from 'node-rsa';
export const initConfigRSA = () => { const key = new NodeRSA({ b: 1024 }); //此处使用`pkcs1`的原因是因为jsencrypt是使用pkcs1标准生成密钥的 key.setOptions({ encryptionScheme: 'pkcs1' }); const privateKey = key.exportKey('pkcs8-private-pem'); const publicKey = key.exportKey('pkcs8-public-pem'); return { privateKey, publicKey };};- 加密数据
和客户端一样的逻辑
const splitString = (str: string, leng = 10) => { const list: string[] = []; let index = 0; while (index < str.length) { list.push(str.slice(index, (index += leng))); } return list;};
export const encryption = (options: Object, key: string) => { const str = JSON.stringify(options); const encrypt = new NodeRSA(key, 'pkcs8-public-pem'); encrypt.setOptions({ encryptionScheme: 'pkcs1' }); let data = ''; const list = splitString(str); for (const iterator of list) { const res = encrypt.encrypt(iterator, 'base64'); console.log(res); if (res) { data = data + res; } } return data || '';};- 解密数据
export const decryption = (str: string, key: string) => { const encrypt = new NodeRSA(key, 'pkcs8-private-pem'); encrypt.setOptions({ encryptionScheme: 'pkcs1' }); let data = ''; const list = str.split('='); for (const iterator of list) { const res = encrypt.decrypt(iterator + '=', 'utf8'); if (res) { data = data + res; } } if (data) { return JSON.parse(data); } return '';};- 实现效果
import http from 'http';import Koa from 'koa';import chalk from 'chalk';import WebSocket from 'ws';import { initConfigRSA } from './configRSA';const app = new Koa();const useKeys = initConfigRSA();console.log(useKeys);let users: Map<number, { username: string; publicKey: string }> = new Map();const server = http.createServer(app.callback());const ws = new WebSocket.Server({ server,});server.listen(3000);console.log('[' + chalk.green('http') + ']', 'http://127.0.0.1:3000');// 监听到客户端连接事件后将服务的公钥发往客户端ws.on('connection', (client) => { console.log('客户端已连接');});在客户端连接成功后将服务端公钥发送到客户端,后续中客户端发往服务端的数据将使用此公钥加密
client.send(Buffer.from(JSON.stringify({ type: 'server', data: { server: useKeys.publicKey } })));效果如下
- 客户端
const msg = encryption(data, serverPublicKey.value);client.send(msg);- 服务端
const message: { type: string; data: any } = decryption(req.toString('utf8'), useKeys.privateKey);- 效果如下
- 服务端
// 获取用户信息const user = users.get(client.userId);// 通过用户的公钥加密const data = encryption(message, user!.publicKey);- 客户端
//先将buffer解码const decoder = new TextDecoder();const data = decoder.decode(res);//通过自己的私钥解密服务端发来的数据const message = JSON.parse(decryption(data, useKeys.privateKey!));3.效果如下
- TextEncoder https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder#Polyfill
- jsencrypt 和 node-rsa 实现参考 https://stackoverflow.com/questions/72209096/getting-the-same-result-from-two-packages-jsencrypt-and-node-rsa https://stackoverflow.com/questions/33837617/node-rsa-errors-when-trying-to-decrypt-message-with-private-key