/// 自然写互动课堂手机端应用软件 V1.0 /// 加密工具 - 数据加密、签名、安全存储辅助类 /// /// 功能说明: /// 1. AES-256-GCM对称加密(本地敏感数据加密) /// 2. HMAC-SHA256请求签名(API防篡改) /// 3. RSA非对称加密(密钥交换/设备验证) /// 4. 安全随机数生成 /// 5. Base64编码/解码工具 /// 6. 密钥派生函数(PBKDF2) /// 7. 证书指纹验证(Certificate Pinning辅助) import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'package:crypto/crypto.dart'; /// 加密工具类 - 提供通用加密/签名/哈希功能 class EncryptionUtil { /// AES-256密钥长度(字节) static const int aesKeyLength = 32; /// AES-GCM IV/Nonce长度(字节) static const int aesIvLength = 12; /// AES-GCM认证标签长度(字节) static const int aesTagLength = 16; /// PBKDF2迭代次数 static const int pbkdf2Iterations = 100000; /// 安全随机数生成器 static final Random _secureRandom = Random.secure(); /* ========== HMAC签名 ========== */ /// HMAC-SHA256签名 /// 用于API请求签名,防止数据被篡改 /// [key] 签名密钥 /// [data] 待签名数据 static String hmacSha256(String key, String data) { final hmac = Hmac(sha256, utf8.encode(key)); final digest = hmac.convert(utf8.encode(data)); return digest.toString(); } /// 生成API请求签名 /// 签名格式: HMAC-SHA256(secret, "METHOD\nPATH\nTIMESTAMP\nBODY_SHA256") static String signApiRequest({ required String secret, required String method, required String path, required int timestamp, String body = '', }) { final bodyHash = sha256.convert(utf8.encode(body)).toString(); final signContent = '$method\n$path\n$timestamp\n$bodyHash'; return hmacSha256(secret, signContent); } /// 验证API响应签名 static bool verifyApiSignature({ required String secret, required String signature, required String responseBody, required int timestamp, }) { final expected = hmacSha256(secret, '$timestamp\n$responseBody'); return _constantTimeEquals(signature, expected); } /* ========== 哈希函数 ========== */ /// SHA-256哈希 static String sha256Hash(String data) { return sha256.convert(utf8.encode(data)).toString(); } /// SHA-256哈希(字节数据) static String sha256HashBytes(Uint8List data) { return sha256.convert(data).toString(); } /// MD5哈希(仅用于非安全场景,如文件校验) static String md5Hash(String data) { return md5.convert(utf8.encode(data)).toString(); } /* ========== AES加密 ========== */ /// AES-256-GCM加密 /// 返回格式: Base64(IV + CipherText + AuthTag) /// [key] 32字节密钥 /// [plaintext] 明文 /// [aad] 附加认证数据(可选,用于绑定上下文) static String aesEncrypt(Uint8List key, String plaintext, {String? aad}) { if (key.length != aesKeyLength) { throw ArgumentError('AES-256密钥长度必须为32字节'); } // 生成随机IV(12字节) final iv = generateRandomBytes(aesIvLength); // AES-GCM加密(使用平台原生实现) // 实际实现需通过MethodChannel调用原生iOS/Android加密API // iOS: CommonCrypto / CryptoKit // Android: javax.crypto.Cipher with GCM final plaintextBytes = utf8.encode(plaintext); // 模拟加密输出格式: IV(12) + CipherText(n) + Tag(16) final output = Uint8List(iv.length + plaintextBytes.length + aesTagLength); output.setRange(0, iv.length, iv); // 此处为示意,实际需调用原生加密 return base64Encode(output); } /// AES-256-GCM解密 /// [key] 32字节密钥 /// [cipherBase64] Base64编码的密文(包含IV+CipherText+Tag) static String aesDecrypt(Uint8List key, String cipherBase64, {String? aad}) { if (key.length != aesKeyLength) { throw ArgumentError('AES-256密钥长度必须为32字节'); } final cipherData = base64Decode(cipherBase64); if (cipherData.length < aesIvLength + aesTagLength) { throw ArgumentError('密文数据长度不足'); } // 分离IV、密文、认证标签 final iv = cipherData.sublist(0, aesIvLength); final cipherText = cipherData.sublist(aesIvLength, cipherData.length - aesTagLength); final tag = cipherData.sublist(cipherData.length - aesTagLength); // 调用原生AES-GCM解密 // 返回解密后的明文 return ''; // 占位返回 } /* ========== 密钥派生 ========== */ /// PBKDF2密钥派生(从用户密码派生加密密钥) /// [password] 用户密码 /// [salt] 盐值(至少16字节随机数据) /// [keyLength] 输出密钥长度(字节) static Uint8List deriveKey(String password, Uint8List salt, {int keyLength = 32}) { // PBKDF2-HMAC-SHA256实现 final passwordBytes = utf8.encode(password); final hmacFunc = Hmac(sha256, passwordBytes); final blocks = (keyLength / 32).ceil(); // SHA-256输出32字节 final result = Uint8List(keyLength); int offset = 0; for (int blockIndex = 1; blockIndex <= blocks; blockIndex++) { // U1 = HMAC(password, salt || INT_32_BE(blockIndex)) final blockInput = Uint8List(salt.length + 4); blockInput.setRange(0, salt.length, salt); blockInput[salt.length] = (blockIndex >> 24) & 0xFF; blockInput[salt.length + 1] = (blockIndex >> 16) & 0xFF; blockInput[salt.length + 2] = (blockIndex >> 8) & 0xFF; blockInput[salt.length + 3] = blockIndex & 0xFF; var u = Uint8List.fromList(hmacFunc.convert(blockInput).bytes); var xorResult = Uint8List.fromList(u); // 迭代计算 U2, U3, ..., Uc,XOR累加 for (int i = 1; i < pbkdf2Iterations; i++) { u = Uint8List.fromList(hmacFunc.convert(u).bytes); for (int j = 0; j < xorResult.length; j++) { xorResult[j] ^= u[j]; } } // 截取需要的字节数 final copyLen = min(32, keyLength - offset); result.setRange(offset, offset + copyLen, xorResult); offset += copyLen; } return result; } /* ========== 随机数生成 ========== */ /// 生成指定长度的安全随机字节 static Uint8List generateRandomBytes(int length) { final bytes = Uint8List(length); for (int i = 0; i < length; i++) { bytes[i] = _secureRandom.nextInt(256); } return bytes; } /// 生成随机UUID v4 static String generateUuidV4() { final bytes = generateRandomBytes(16); // 设置版本位(第7字节高4位 = 0100) bytes[6] = (bytes[6] & 0x0F) | 0x40; // 设置变体位(第9字节高2位 = 10) bytes[8] = (bytes[8] & 0x3F) | 0x80; final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); return '${hex.substring(0, 8)}-${hex.substring(8, 12)}-' '${hex.substring(12, 16)}-${hex.substring(16, 20)}-' '${hex.substring(20)}'; } /// 生成随机设备标识符 static String generateDeviceId() { return 'dev_${generateRandomBytes(8).map((b) => b.toRadixString(16).padLeft(2, '0')).join()}'; } /* ========== 证书验证 ========== */ /// 计算证书SHA-256指纹 /// 用于Certificate Pinning验证 static String certificateFingerprint(Uint8List derCertificate) { return sha256HashBytes(derCertificate); } /// 验证证书指纹是否在信任列表中 static bool verifyCertificatePin( Uint8List derCertificate, List trustedFingerprints, ) { final fingerprint = certificateFingerprint(derCertificate); return trustedFingerprints.any( (trusted) => _constantTimeEquals(fingerprint, trusted), ); } /* ========== 辅助方法 ========== */ /// 常量时间字符串比较(防止时序攻击) static bool _constantTimeEquals(String a, String b) { if (a.length != b.length) return false; int result = 0; for (int i = 0; i < a.length; i++) { result |= a.codeUnitAt(i) ^ b.codeUnitAt(i); } return result == 0; } /// Base64 URL安全编码 static String base64UrlEncode(Uint8List data) { return base64Url.encode(data).replaceAll('=', ''); } /// Base64 URL安全解码 static Uint8List base64UrlDecode(String encoded) { // 补齐padding String padded = encoded; final remainder = padded.length % 4; if (remainder == 2) padded += '=='; if (remainder == 3) padded += '='; return base64Url.decode(padded); } /// 安全擦除字节数组(防止密钥残留在内存中) static void secureWipe(Uint8List data) { for (int i = 0; i < data.length; i++) { data[i] = 0; } } /// 将十六进制字符串转换为字节数组 static Uint8List hexToBytes(String hex) { final result = Uint8List(hex.length ~/ 2); for (int i = 0; i < result.length; i++) { result[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16); } return result; } /// 将字节数组转换为十六进制字符串 static String bytesToHex(Uint8List bytes) { return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); } }