前端使用 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);