NESTJS - RSA加解密
1、生成密钥(2048位)
openssl genrsa -out private_key.pem 20482、从密钥中提取公钥
openssl rsa -in private_key.pem -pubout -out public_key.pem密钥存放在服务端,公钥存在放在客户端,通过公钥进行加密,私钥进行解密
注:RSA加密有长度限制,,所以一般的做法为:
1、每次发起请求时随机生成临时32字节的密钥
2、通过RSA对32字节的密钥进行加密
3、将32字节的密钥当做AES的密钥,对请求体进行AES加密
4、将RSA加密后的32字节的密钥 和 AES加密后的请求体 发送给服务端
5、服务端拿到后先将RSA加密后的32字节的密钥 进行解密,得到AES加密的密钥,然后使用密钥对请求体进行AES解密,就可以拿到最终的请求体了
3、前端使用RSA公钥对数据进行加密 (pnpm i node-forge)
import * as forge from 'node-forge';// 使用 RSA 公钥 加密 参数为 要加密的数据以及公钥(public_key.pem的值)
export const rsaEncrypt = (data: string, publicKeyPem: string): string => {// 1. 将字符串转换为字节const bytes = forge.util.encodeUtf8(data);// 2. 解析 PEM 公钥const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);// 3. 使用 RSA-OAEP + SHA-256 加密const encryptedBytes = publicKey.encrypt(bytes, 'RSA-OAEP', {md: forge.md.sha256.create(), // OAEP 主哈希mgf1: forge.md.sha1.create(), // MGF1 哈希(必须指定)});// 4. 转为 base64 字符串(便于 JSON 传输)const encryptedBase64 = forge.util.encode64(encryptedBytes);return encryptedBase64;
};4、服务端RAS解密
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as crypto from 'crypto';
import * as fs from 'fs';
import path from "path";@Injectable()
export class RsaService {private privateKey: string;constructor(private readonly configService: ConfigService) {// 私钥的内容this.privateKey = fs.readFileSync(path.join(__dirname, "../../../", this.configService.get("rsaKeys.privateKey")!), 'utf-8');}// 返回解密数据decryptAesKey(encryptedKey: string) {const buffer = Buffer.from(encryptedKey, 'base64');const decrypted = crypto.privateDecrypt({key: this.privateKey,padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,oaepHash: 'sha256',},buffer,);return decrypted.toString('utf-8'); //返回解密数据}}5、导入服务 直接使用即可
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";
import { WinstonService } from "./winston-service";
import { ConfigService } from "@nestjs/config";
import { AesService } from "../crypto/aes-service";
import { RsaService } from "../crypto/rsa-service";// 请求日志中间件。进行解密与记录日志
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {constructor(private readonly winstonService: WinstonService,private readonly configService: ConfigService,private readonly aesService: AesService,private readonly rsaService: RsaService) {}use(req: Request, res: Response, next: NextFunction) {if (req.method == "POST") {const aesKey = this.rsaService.decrypt(req.body.id);const decryptedBody = this.aesService.decrypt(req.body.info, aesKey);req.body = JSON.parse(decryptedBody);}if (this.configService.get("env") != "development") {this.winstonService.log('winstonRoute', {url: req.originalUrl,query: req.query,body: req.body,method: req.method,ip: req.ip,token: req.headers.authorization})}next();}
}我这边是前端将 RSA加密后的32字节的密钥 作为id
AES加密后的请求体 作为info 传递过来的,所以通过this.rsaService.decrypt(req.body.id) 解密,得到AES的密钥,
然后再将info和密钥传递给this.aesService.decrypt(req.body.info, aesKey)进行取得body体
