|
|
@@ -0,0 +1,150 @@
|
|
|
+import CryptoJS from "crypto-js";
|
|
|
+
|
|
|
+/**
|
|
|
+ * 加密配置接口
|
|
|
+ */
|
|
|
+export interface CryptoConfig {
|
|
|
+ key: string;
|
|
|
+ iv: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES 加密工具类
|
|
|
+ * 使用 AES-128-CBC 算法进行加解密
|
|
|
+ */
|
|
|
+export class CryptoUtils {
|
|
|
+ private static readonly KEY_LENGTH = 16;
|
|
|
+ private static readonly IV_LENGTH = 16;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证密钥和向量的长度
|
|
|
+ * @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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加密数据
|
|
|
+ * @param data 待加密的数据(对象或字符串)
|
|
|
+ * @param key AES 密钥(16字节)
|
|
|
+ * @param iv AES 向量(16字节)
|
|
|
+ * @returns 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`);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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,
|
|
|
+ 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)}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解密数据
|
|
|
+ * @param ciphertext Base64 编码的密文
|
|
|
+ * @param key AES 密钥(16字节)
|
|
|
+ * @param iv AES 向量(16字节)
|
|
|
+ * @returns 解密后的数据(对象或字符串)
|
|
|
+ */
|
|
|
+ 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`);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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,
|
|
|
+ mode: CryptoJS.mode.CBC,
|
|
|
+ padding: CryptoJS.pad.Pkcs7
|
|
|
+ });
|
|
|
+
|
|
|
+ // 将解密结果转换为 UTF-8 字符串
|
|
|
+ const result = decrypted.toString(CryptoJS.enc.Utf8);
|
|
|
+
|
|
|
+ // 如果解密结果为空,返回空字符串
|
|
|
+ if (!result) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试解析为 JSON 对象
|
|
|
+ try {
|
|
|
+ return JSON.parse(result);
|
|
|
+ } catch (e) {
|
|
|
+ // 如果不是 JSON,则直接返回字符串
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ throw new Error(`Decryption failed: ${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);
|
|
|
+ }
|
|
|
+
|
|
|
+ return CryptoUtils.encrypt(data, 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);
|
|
|
+ }
|
|
|
+};
|