Files
2026-03-22 15:24:40 +08:00

283 lines
9.0 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 自然写互动课堂手机端应用软件 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字节');
}
// 生成随机IV12字节)
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, ..., UcXOR累加
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<String> 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();
}
}