前后端通信加解密(Web Crypto API )

前端使用 ts,后端使用 node,要求 node版本大于18,本人用的 22

前端 encrypt.ts

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:

ts 复制代码
请求加密
await encryptRequestData(config.data)
响应解密
await decryptResponseData(response.data.data)

后端 encrypt.ts

javascript 复制代码
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,
};

实例:

javascript 复制代码
后端请求解密:
await decryptRequestData(data);
后端响应加密:
const rpd = await encryptResponseData({
  ...response.data,
 });
// 返回第三方接口的响应
 res.json(rpd);
相关推荐
AskHarries1 分钟前
收到第一封推广邮件:我的 App 正在被看见
前端·后端·产品
蚂蚁集团数据体验技术3 分钟前
AI 文字信息图表的技术选型
前端·javascript·github
胡楚昊5 分钟前
Polar WEB(21-
前端
BD_Marathon12 分钟前
【JavaWeb】HTML专业词汇
前端
lichong95117 分钟前
鸿蒙系统 4.1.0 兼容 Android apk 如何检测兼容的 Android 系统版本是多少
前端·javascript
colorFocus19 分钟前
Vue之如何获取自定义事件返回值
前端·vue.js
草帽lufei19 分钟前
业务代码迭代重构利器:SOLO中国版保障项目需求
前端·ai编程·trae
好_快19 分钟前
Arco Design Layout 中使用 ResizeBox 实现可拖拽侧边栏
前端·vue.js·arco design
越努力越幸运50819 分钟前
node.js学习三:Session,jwt,cors,mysql,api的学习
node.js
重铸码农荣光20 分钟前
JavaScript 面向对象编程:从字面量到原型继承的深度探索
前端·javascript·设计模式