software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -0,0 +1,282 @@
/// 自然写互动课堂手机端应用软件 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();
}
}