前后端通信加解密(Web Crypto API )
前端使用 ts,后端使用 node,要求 node版本大于18,本人用的 22
前端 encrypt.ts
/*** 使用 Web Crypto API 进行 AES-GCM 加密和解密* 注意:密钥应该从环境变量或配置中获取,不要硬编码*/// 加密密钥(应该是32字节,256位)
// 在实际项目中,这个密钥应该从环境变量或安全配置中获取
const ENCRYPTION_KEY = import.meta.env.VITE_ENCRYPTION_KEY
/*** 将字符串密钥转换为 CryptoKey*/
async function getKey(keyString: string): Promise<CryptoKey> {const encoder = new TextEncoder()const keyData = encoder.encode(keyString)// 确保密钥是32字节(256位)const keyArray = new Uint8Array(32)const sourceKey = new Uint8Array(keyData.slice(0, 32))keyArray.set(sourceKey)return crypto.subtle.importKey('raw',keyArray,{name: 'AES-GCM',length: 256,},false,['encrypt', 'decrypt'],)
}/*** 加密数据* @param data - 要加密的数据(可以是对象或字符串)* @returns 加密后的字符串(base64格式,包含iv)*/
export async function encrypt(data: string | object): Promise<string> {try {const cryptoKey = await getKey(ENCRYPTION_KEY)// 将数据转换为字符串const dataString = typeof data === 'string' ? data : JSON.stringify(data)const encoder = new TextEncoder()const dataBuffer = encoder.encode(dataString)// 生成随机 IV(初始化向量)const iv = crypto.getRandomValues(new Uint8Array(12))// 加密数据const encryptedData = await crypto.subtle.encrypt({name: 'AES-GCM',iv: iv,},cryptoKey,dataBuffer,)// 将 IV 和加密数据合并const combined = new Uint8Array(iv.length + encryptedData.byteLength)combined.set(iv, 0)combined.set(new Uint8Array(encryptedData), iv.length)// 转换为 base64 字符串(使用更安全的方式处理大数组)const binaryString = Array.from(combined, (byte) => String.fromCharCode(byte)).join('')return btoa(binaryString)} catch (error) {console.error('加密失败:', error)throw new Error('数据加密失败')}
}/*** 解密数据* @param encryptedData - 加密后的数据(base64格式)* @returns 解密后的原始数据(如果是JSON字符串,需要手动解析)*/
export async function decrypt(encryptedData: string): Promise<string> {try {const cryptoKey = await getKey(ENCRYPTION_KEY)// 从 base64 解码const binaryString = atob(encryptedData)const combined = new Uint8Array(binaryString.length)for (let i = 0; i < binaryString.length; i++) {combined[i] = binaryString.charCodeAt(i)}// 提取 IV 和加密数据const iv = combined.slice(0, 12)const data = combined.slice(12)// 解密数据const decryptedData = await crypto.subtle.decrypt({name: 'AES-GCM',iv: iv,},cryptoKey,data,)// 转换为字符串const decoder = new TextDecoder()return decoder.decode(decryptedData)} catch (error) {console.error('解密失败:', error)throw new Error('数据解密失败')}
}/*** 加密请求参数(自动处理对象)* @param data - 请求参数对象* @returns 加密后的字符串*/
export async function encryptRequestData(data: any): Promise<string> {return encrypt(data)
}/*** 解密响应数据(自动解析JSON)* @param encryptedData - 加密的响应数据* @returns 解密后的对象*/
export async function decryptResponseData(encryptedData: string): Promise<any> {const decrypted = await decrypt(encryptedData)try {return JSON.parse(decrypted)} catch {return decrypted}
}
前端加解密eg:
请求加密
await encryptRequestData(config.data)
响应解密
await decryptResponseData(response.data.data)
后端 encrypt.ts
require("dotenv").config();
const { webcrypto } = require("crypto");// 在 Node.js < 19.0.0 中,TextEncoder 和 TextDecoder 是全局可用的
// 但为了代码的明确性和兼容性,可以从 'util' 模块导入
const { TextEncoder, TextDecoder } = require("util");const ENCRYPTION_KEY = process.env.VITE_ENCRYPTION_KEY;/*** 将字符串密钥转换为 CryptoKey*/
async function getKey(keyString) {if (!keyString) {throw new Error("加密密钥未配置");}const encoder = new TextEncoder();const keyData = encoder.encode(keyString);// 确保密钥是32字节(256位)const keyArray = new Uint8Array(32);const sourceKey = new Uint8Array(keyData.slice(0, 32));keyArray.set(sourceKey);return webcrypto.subtle.importKey("raw",keyArray,{name: "AES-GCM",length: 256,},false,["encrypt", "decrypt"]);
}/*** 加密数据* @param data - 要加密的数据(可以是对象或字符串)* @returns 加密后的字符串(base64格式,包含iv)*/
async function encrypt(data) {try {const cryptoKey = await getKey(ENCRYPTION_KEY);// 将数据转换为字符串const dataString = typeof data === "string" ? data : JSON.stringify(data);const encoder = new TextEncoder();const dataBuffer = encoder.encode(dataString);// 生成随机 IV(初始化向量)const iv = webcrypto.getRandomValues(new Uint8Array(12));// 加密数据const encryptedData = await webcrypto.subtle.encrypt({name: "AES-GCM",iv: iv,},cryptoKey,dataBuffer);// 将 IV 和加密数据合并const combined = new Uint8Array(iv.length + encryptedData.byteLength);combined.set(iv, 0);combined.set(new Uint8Array(encryptedData), iv.length);// 转换为 base64 字符串(使用更安全的方式处理大数组)const binaryString = Array.from(combined, (byte) =>String.fromCharCode(byte)).join("");return btoa(binaryString);} catch (error) {console.error("加密失败:", error);throw new Error("数据加密失败");}
}/*** 解密数据* @param encryptedData - 加密后的数据(base64格式)* @returns 解密后的原始数据(如果是JSON字符串,需要手动解析)*/
async function decrypt(encryptedData) {try {const cryptoKey = await getKey(ENCRYPTION_KEY);// 从 base64 解码const combined = Buffer.from(encryptedData, "base64");// 提取 IV 和加密数据const iv = combined.slice(0, 12);const data = combined.slice(12);// 解密数据const decryptedData = await webcrypto.subtle.decrypt({name: "AES-GCM",iv: iv,},cryptoKey,data);// 转换为字符串const decoder = new TextDecoder();return decoder.decode(decryptedData);} catch (error) {console.error("解密失败:", error);throw new Error("数据解密失败");}
}/*** 解密请求参数(自动解析JSON)* @param encryptedData - 加密的请求数据* @returns 解密后的对象*/
async function decryptRequestData(encryptedData) {const decrypted = await decrypt(encryptedData);try {return JSON.parse(decrypted);} catch {return decrypted;}
}/*** 加密响应数据(自动处理对象)* @param data - 响应数据对象* @returns 加密后的字符串*/
async function encryptResponseData(data) {return encrypt(data);
}// 导出函数
module.exports = {encrypt,decrypt,decryptRequestData,encryptResponseData,
};
实例:
后端请求解密:
await decryptRequestData(data);
后端响应加密:
const rpd = await encryptResponseData({...response.data,});
// 返回第三方接口的响应res.json(rpd);
