|
|
@@ -1,150 +1,80 @@
|
|
|
import CryptoJS from "crypto-js";
|
|
|
|
|
|
/**
|
|
|
- * 加密配置接口
|
|
|
+ * AES-128-CBC 加解密工具类
|
|
|
+ * 参考中台项目实现,采用类实例模式
|
|
|
*/
|
|
|
-export interface CryptoConfig {
|
|
|
- key: string;
|
|
|
- iv: string;
|
|
|
-}
|
|
|
+class CryptoUtil {
|
|
|
+ private key: CryptoJS.lib.WordArray;
|
|
|
+ private iv: CryptoJS.lib.WordArray;
|
|
|
|
|
|
-/**
|
|
|
- * AES 加密工具类
|
|
|
- * 使用 AES-128-CBC 算法进行加解密
|
|
|
- */
|
|
|
-export class CryptoUtils {
|
|
|
- private static readonly KEY_LENGTH = 16;
|
|
|
- private static readonly IV_LENGTH = 16;
|
|
|
+ constructor(key: string, iv: string) {
|
|
|
+ this.key = CryptoJS.enc.Utf8.parse(key);
|
|
|
+ this.iv = CryptoJS.enc.Utf8.parse(iv);
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 验证密钥和向量的长度
|
|
|
- * @param key AES 密钥(16字节)
|
|
|
- * @param iv AES 向量(16字节)
|
|
|
- * @returns 是否有效
|
|
|
+ * 验证密钥和向量是否有效
|
|
|
*/
|
|
|
- static validateConfig(key: string, iv: string): boolean {
|
|
|
- return key.length === this.KEY_LENGTH && iv.length === this.IV_LENGTH;
|
|
|
+ isConfigured(): boolean {
|
|
|
+ return Boolean(this.key.sigBytes && this.iv.sigBytes);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 加密数据
|
|
|
- * @param data 待加密的数据(对象或字符串)
|
|
|
- * @param key AES 密钥(16字节)
|
|
|
- * @param iv AES 向量(16字节)
|
|
|
- * @returns Base64 编码的加密字符串
|
|
|
+ * 加密:对象/字符串 -> Base64
|
|
|
*/
|
|
|
- static encrypt(data: any, key: string, iv: string): string {
|
|
|
- if (!this.validateConfig(key, iv)) {
|
|
|
- throw new Error(`Invalid crypto config: key must be ${this.KEY_LENGTH} bytes, iv must be ${this.IV_LENGTH} bytes`);
|
|
|
- }
|
|
|
-
|
|
|
+ encrypt(data: any): string {
|
|
|
+ if (data === null || data === undefined) return data;
|
|
|
try {
|
|
|
- // 将对象转换为 JSON 字符串
|
|
|
const plainText = typeof data === "object" ? JSON.stringify(data) : String(data);
|
|
|
-
|
|
|
- // 将密钥和向量转换为 WordArray
|
|
|
- const keyWordArray = CryptoJS.enc.Utf8.parse(key);
|
|
|
- const ivWordArray = CryptoJS.enc.Utf8.parse(iv);
|
|
|
-
|
|
|
- // 使用 AES-128-CBC 加密
|
|
|
- const encrypted = CryptoJS.AES.encrypt(plainText, keyWordArray, {
|
|
|
- iv: ivWordArray,
|
|
|
+ const encrypted = CryptoJS.AES.encrypt(plainText, this.key, {
|
|
|
+ iv: this.iv,
|
|
|
mode: CryptoJS.mode.CBC,
|
|
|
padding: CryptoJS.pad.Pkcs7
|
|
|
});
|
|
|
-
|
|
|
- // 返回 Base64 编码的密文
|
|
|
return encrypted.toString();
|
|
|
} catch (error) {
|
|
|
- throw new Error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
+ console.error("Encryption failed:", error);
|
|
|
+ throw new Error("请求参数加密失败");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 解密数据
|
|
|
- * @param ciphertext Base64 编码的密文
|
|
|
- * @param key AES 密钥(16字节)
|
|
|
- * @param iv AES 向量(16字节)
|
|
|
- * @returns 解密后的数据(对象或字符串)
|
|
|
+ * 解解密:Base64 -> 对象/字符串
|
|
|
*/
|
|
|
- static decrypt(ciphertext: string, key: string, iv: string): any {
|
|
|
- if (!this.validateConfig(key, iv)) {
|
|
|
- throw new Error(`Invalid crypto config: key must be ${this.KEY_LENGTH} bytes, iv must be ${this.IV_LENGTH} bytes`);
|
|
|
- }
|
|
|
-
|
|
|
+ decrypt(cipherText: string): any {
|
|
|
+ if (!cipherText || typeof cipherText !== "string") return cipherText;
|
|
|
try {
|
|
|
- // 将密钥和向量转换为 WordArray
|
|
|
- const keyWordArray = CryptoJS.enc.Utf8.parse(key);
|
|
|
- const ivWordArray = CryptoJS.enc.Utf8.parse(iv);
|
|
|
-
|
|
|
- // 使用 AES-128-CBC 解密
|
|
|
- const decrypted = CryptoJS.AES.decrypt(ciphertext, keyWordArray, {
|
|
|
- iv: ivWordArray,
|
|
|
+ const decrypted = CryptoJS.AES.decrypt(cipherText, this.key, {
|
|
|
+ iv: this.iv,
|
|
|
mode: CryptoJS.mode.CBC,
|
|
|
padding: CryptoJS.pad.Pkcs7
|
|
|
});
|
|
|
-
|
|
|
- // 将解密结果转换为 UTF-8 字符串
|
|
|
const result = decrypted.toString(CryptoJS.enc.Utf8);
|
|
|
-
|
|
|
- // 如果解密结果为空,返回空字符串
|
|
|
if (!result) {
|
|
|
- return "";
|
|
|
+ throw new Error("Decryption failed: empty result");
|
|
|
}
|
|
|
-
|
|
|
- // 尝试解析为 JSON 对象
|
|
|
+ // 尝试解析为 JSON
|
|
|
try {
|
|
|
return JSON.parse(result);
|
|
|
- } catch (e) {
|
|
|
- // 如果不是 JSON,则直接返回字符串
|
|
|
+ } catch {
|
|
|
return result;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- throw new Error(`Decryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
+ console.error("Decryption failed:", error);
|
|
|
+ throw new Error(`响应数据解密失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * 默认加密工具实例
|
|
|
- */
|
|
|
-export const crypto = {
|
|
|
- /**
|
|
|
- * 加密数据(使用默认配置)
|
|
|
- */
|
|
|
- encrypt(data: any, config?: Partial<CryptoConfig>): string {
|
|
|
- const key = config?.key || import.meta.env.VITE_API_ENCRYPTION_KEY || "";
|
|
|
- const iv = config?.iv || import.meta.env.VITE_API_ENCRYPTION_IV || "";
|
|
|
-
|
|
|
- if (!key || !iv) {
|
|
|
- console.warn("Encryption keys not configured, returning plaintext");
|
|
|
- return typeof data === "object" ? JSON.stringify(data) : String(data);
|
|
|
- }
|
|
|
+// 获取环境变量
|
|
|
+const KEY = import.meta.env.VITE_CRYPTO_KEY || "";
|
|
|
+const IV = import.meta.env.VITE_CRYPTO_IV || "";
|
|
|
|
|
|
- return CryptoUtils.encrypt(data, key, iv);
|
|
|
- },
|
|
|
+// 单例导出
|
|
|
+export const cryptoUtil = new CryptoUtil(KEY, IV);
|
|
|
|
|
|
- /**
|
|
|
- * 解密数据(使用默认配置)
|
|
|
- */
|
|
|
- decrypt(ciphertext: string, config?: Partial<CryptoConfig>): any {
|
|
|
- const key = config?.key || import.meta.env.VITE_API_ENCRYPTION_KEY || "";
|
|
|
- const iv = config?.iv || import.meta.env.VITE_API_ENCRYPTION_IV || "";
|
|
|
-
|
|
|
- if (!key || !iv) {
|
|
|
- console.warn("Decryption keys not configured, returning ciphertext");
|
|
|
- return ciphertext;
|
|
|
- }
|
|
|
-
|
|
|
- return CryptoUtils.decrypt(ciphertext, key, iv);
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 检查是否启用了加密配置
|
|
|
- */
|
|
|
- isConfigured(): boolean {
|
|
|
- const key = import.meta.env.VITE_API_ENCRYPTION_KEY || "";
|
|
|
- const iv = import.meta.env.VITE_API_ENCRYPTION_IV || "";
|
|
|
- return CryptoUtils.validateConfig(key, iv);
|
|
|
- }
|
|
|
-};
|
|
|
+/**
|
|
|
+ * @deprecated 请使用 cryptoUtil
|
|
|
+ */
|
|
|
+export const crypto = cryptoUtil;
|