software copyright
This commit is contained in:
@@ -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字节');
|
||||
}
|
||||
|
||||
// 生成随机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<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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user