likes
comments
collection
share

你开发的聊天软件消息加密了吗

作者站长头像
站长
· 阅读数 15

前言

最近发现一些成熟的 IM 服务消息传输都是经过加密进行传输的,所以使用 RSA 加密对此进行一次尝试

RSA 简介

RSA 是一种非对称加密算法,它由三位密码学家 Ron Rivest、Adi Shamir 和 Leonard Adleman 在 1977 年共同提出,以他们姓氏的首字母命名。RSA 算法是目前广泛使用的加密算法之一。 RSA 算法基于两个数学问题的难解性:大素数分解和求模幂运算的逆运算。它的核心思想是使用一对相关的密钥,一个公钥和一个私钥,其中公钥用于加密数据,私钥用于解密数据。公钥可以自由发布给任何人,而私钥则必须保密。

RSA 算法的安全性基于大数分解的困难性。目前没有已知的有效算法可以在合理的时间内分解大的复合数,所以 RSA 算法在实际应用中被广泛用于数据加密、数字签名和密钥交换等领域。

代码实现思路

你开发的聊天软件消息加密了吗

阅读须知

  • 本文使用 vite-vue,以及 node 的 koa 开发
  • 本文使用 RSA 对数据进行加密
  • 本文 web 端使用jsencrypt生成公钥和私钥,后端使用node-rsa生成

项目搭建

创建后端服务

  1. 初始化目录并安装依赖
    npm init
    pnpm i koa
    pnpm i ts-node-dev
  1. 创建 index.ts
  2. 编写 server 测试
const Koa = require("koa");
const app = new Koa();

app.use(async (ctx) => {
  ctx.body = "Hello World";
});

app.listen(3000);

使用 vite 初始化一个 vue 项目

    pnpm create vite
    pnpm install --save-dev @arco-design/web-vue

客户端和服务端密钥的生成

客户端

  1. 创建密钥
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 };
};
  1. 加密数据
// 将字符串按照指定长度分割成数组
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;
};
  1. 解密数据
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 || "";
};
  1. 实现效果 你开发的聊天软件消息加密了吗

服务端

  1. 创建密钥
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 };
};
  1. 加密数据

和客户端一样的逻辑

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 || "";
};
  1. 解密数据
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 "";
};
  1. 实现效果

你开发的聊天软件消息加密了吗

server 中创建 ws 服务

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 监听

// 监听到客户端连接事件后将服务的公钥发往客户端
ws.on("connection", (client) => {
  console.log("客户端已连接");
});

交换公钥

在客户端连接成功后将服务端公钥发送到客户端,后续中客户端发往服务端的数据将使用此公钥加密

client.send(
  Buffer.from(
    JSON.stringify({ type: "server", data: { server: useKeys.publicKey } })
  )
);

效果如下

你开发的聊天软件消息加密了吗

你开发的聊天软件消息加密了吗

消息交换

客户端向服务端

  1. 客户端
const msg = encryption(data, serverPublicKey.value);
client.send(msg);
  1. 服务端
const message: { type: string; data: any } = decryption(
  req.toString("utf8"),
  useKeys.privateKey
);
  1. 效果如下

你开发的聊天软件消息加密了吗

服务端向客户端

  1. 服务端
// 获取用户信息
const user = users.get(client.userId);
// 通过用户的公钥加密
const data = encryption(message, user!.publicKey);
  1. 客户端
//先将buffer解码
const decoder = new TextDecoder();
const data = decoder.decode(res);
//通过自己的私钥解密服务端发来的数据
const message = JSON.parse(decryption(data, useKeys.privateKey!));

3.效果如下

你开发的聊天软件消息加密了吗

参考文档