import { $api } from '@/api/api'; import { useCryptStore } from '@/stores/useCryptStore'; import { SM2, SM4 } from 'gm-crypto'; // 与后端SM2Utils对应的模式配置 const SM2_MODE = SM2.constants.C1C3C2; const SM2_INPUT_ENCODING = 'utf8'; const SM2_OUTPUT_ENCODING = 'hex'; // 与后端SM4Utils对应的模式配置(ECB模式) const SM4_MODE = SM4.constants.ECB; const SM4_INPUT_ENCODING = 'utf8'; const SM4_OUTPUT_ENCODING = 'hex'; /** * 安全工具类 * 提供SM2/SM4加密解密功能 */ export const SafetyUtils = { /** * 获取SM2公钥 * @returns SM2公钥 */ getSm2PublicKey: async (): Promise => { const cryptStore = useCryptStore(); if (cryptStore.sm2PublicKey) return cryptStore.sm2PublicKey; try { const res = await $api.crypto.getSm2PublicKey(); const publicKey = res.data.publicKey; cryptStore.sm2PublicKey = publicKey; return publicKey; } catch (error) { console.error('获取SM2公钥失败:', error); throw new Error('获取加密公钥失败'); } }, /** * 生成随机SM4密钥(16字节) * @returns SM4密钥 */ generateSm4Key: (): string => { return SafetyUtils._generateRandomHex(16); }, /** * SM2非对称加密(公钥加密) * @param data - 待加密数据 * @param publicKey - 公钥(可选) * @returns 加密后的数据 */ sm2Encrypt: async ( data: object | string, publicKey?: string ): Promise => { try { const targetPublicKey = publicKey || (await SafetyUtils.getSm2PublicKey()); const plaintext = typeof data === 'string' ? data : JSON.stringify(data); return ( '04' + SM2.encrypt(plaintext, targetPublicKey, { mode: SM2_MODE, inputEncoding: SM2_INPUT_ENCODING, outputEncoding: SM2_OUTPUT_ENCODING, }) ); } catch (error) { console.error('SM2加密失败:', error); throw new Error('数据加密失败'); } }, /** * SM2非对称解密(私钥解密) * @param data - 待解密数据 * @param privateKey - 私钥 * @returns 解密后的数据 */ sm2Decrypt: async (data: string, privateKey: string): Promise => { try { return SM2.decrypt(data.substring(2), privateKey, { inputEncoding: SM2_OUTPUT_ENCODING, outputEncoding: SM2_INPUT_ENCODING, mode: SM2.constants.C1C3C2, }); } catch (error) { console.error('SM2加密失败:', error); throw new Error('数据加密失败'); } }, /** * SM4对称加密(ECB模式) * @param key - 密钥 * @param data - 待加密数据 * @returns 加密后的数据 */ sm4Encrypt: (key: string, data: object | string): string => { try { const plaintext = typeof data === 'string' ? data : JSON.stringify(data); return SM4.encrypt(plaintext, key, { mode: SM4_MODE, inputEncoding: SM4_INPUT_ENCODING, outputEncoding: SM4_OUTPUT_ENCODING, }); } catch (error) { console.error('SM4加密失败:', error); throw new Error('数据加密失败'); } }, /** * SM4对称解密(ECB模式) * @param key - 密钥 * @param encryptedData - 待解密数据 * @returns 解密后的数据 */ sm4Decrypt: (key: string, encryptedData: string): object | string => { try { const plaintext = SM4.decrypt(encryptedData, key, { mode: SM4_MODE, inputEncoding: SM4_OUTPUT_ENCODING, outputEncoding: SM4_INPUT_ENCODING, }); // 尝试解析JSON,兼容对象和字符串 try { return JSON.parse(plaintext); } catch { return plaintext; } } catch (error) { console.error('SM4解密失败:', error); throw new Error('数据解密失败'); } }, /** * 加密FormData中的普通字段 * @param key - 密钥 * @param formData - 表单数据 * @returns 加密后的表单数据 */ encryptFormData: (key: string, formData: FormData): FormData => { const encryptedFormData = new FormData(); for (const [fieldName, value] of formData.entries()) { if (value instanceof Blob) { // 保留文件字段 encryptedFormData.append(fieldName, value, (value as File).name); } else { // 加密普通字段 const encryptedValue = SafetyUtils.sm4Encrypt(key, String(value)); encryptedFormData.append(fieldName, encryptedValue); } } return encryptedFormData; }, /** * 生成指定长度的随机十六进制字符串 * @param length - 长度 * @returns 随机十六进制字符串 */ _generateRandomHex: (length: number): string => { const uint8Array = new Uint8Array(length); window.crypto.getRandomValues(uint8Array); return Array.from(uint8Array, (byte) => byte.toString(16).padStart(2, '0') ).join(''); }, };