加解密性能优化,预埋
This commit is contained in:
@@ -21,12 +21,12 @@ import 'package:pointycastle/random/fortuna_random.dart';
|
||||
/// 密钥派生模式
|
||||
///
|
||||
/// 决定 [EncryptionFlutterService._deriveKeyForRound] 使用哪种算法。
|
||||
/// 默认 [md5](UU 兼容),可选 [pbkdf2](增强安全性)。
|
||||
/// 默认 [md5],可选 [pbkdf2](增强安全性)。
|
||||
///
|
||||
/// 解密旧数据时必须使用加密时相同的模式,
|
||||
/// 通过消息的 version 字段区分。
|
||||
enum KdfMode {
|
||||
/// MD5 简单哈希(UU 兼容默认模式)
|
||||
/// MD5 简单哈希(默认模式)
|
||||
///
|
||||
/// 适用于 session key 已是 32 字节强随机值的场景。
|
||||
/// 性能好,每次调用 < 0.1ms。
|
||||
@@ -48,14 +48,26 @@ enum KdfMode {
|
||||
///
|
||||
/// - **RSA 密钥生成**:通过 [generateRsaKeyPairAsync] 在 Isolate 中运行,
|
||||
/// 避免阻塞主线程(1024-bit 约 150ms,2048-bit 约 300ms)
|
||||
/// - **派生密钥缓存**:[_deriveKeyForRound] 结果按 (sessionKey, round) 缓存,
|
||||
/// 同一 session 的重复加解密直接命中缓存
|
||||
/// - **RSA 解析缓存**:[_parsePublicKey] / [_parsePrivateKey] 缓存 ASN1 解析结果,
|
||||
/// 同一密钥 PEM 只做一次 BigInt 构造,后续命中缓存(LRU,上限 8 条)
|
||||
/// - **Session key bytes 缓存**:[_getSessionKeyBytes] 缓存 base64 → Uint8List 结果,
|
||||
/// 同一 session 的多条消息只解码一次(LRU,上限 64 条)
|
||||
/// - **派生密钥缓存**:[_deriveKeyForRound] 结果按 (sessionKey, round, mode) 缓存,
|
||||
/// 同一 session + round 的重复加解密直接命中(LRU,上限 64 条)
|
||||
/// - **Random.secure() 复用**:全局单例,不再每次调用创建新实例
|
||||
/// - **KDF 双模式**:MD5(默认,UU 兼容)/ PBKDF2(可选,增强安全性)
|
||||
/// - **KDF 双模式**:MD5(默认)/ PBKDF2(可选,增强安全性)
|
||||
///
|
||||
/// ## 正确的接入姿势(避免重复读文件)
|
||||
///
|
||||
/// 调用方(App 层)在登录后调一次 [CipherGuardSdkApi.setActiveKeyPair],
|
||||
/// 把从安全存储读出的公私钥注入 SDK 内存。后续加解密使用
|
||||
/// [CipherGuardSdkApi.encryptSessionKeyWithActiveKey] /
|
||||
/// [CipherGuardSdkApi.decryptSessionKeyWithActiveKey],
|
||||
/// 不再每次传 key 参数,也不再重复读文件。
|
||||
class EncryptionFlutterService {
|
||||
// ==================== 配置 ====================
|
||||
|
||||
/// 密钥派生模式,默认 MD5(UU 兼容)
|
||||
/// 密钥派生模式,默认 MD5
|
||||
final KdfMode kdfMode;
|
||||
|
||||
/// PBKDF2 迭代次数(仅 PBKDF2 模式有效,默认 10000)
|
||||
@@ -71,6 +83,8 @@ class EncryptionFlutterService {
|
||||
static const int sessionKeySize = 32;
|
||||
static const int gcmIvLength = 12;
|
||||
static const int _maxDerivedKeyCacheSize = 64;
|
||||
static const int _maxRsaKeyCacheSize = 8;
|
||||
static const int _maxSessionKeyBytesCacheSize = 64;
|
||||
|
||||
// ==================== 性能优化:复用 Random 实例 ====================
|
||||
|
||||
@@ -88,6 +102,25 @@ class EncryptionFlutterService {
|
||||
/// 清空派生密钥缓存(session key 轮换时调用)
|
||||
void clearDerivedKeyCache() => _derivedKeyCache.clear();
|
||||
|
||||
// ==================== 性能优化:RSA 解析缓存 ====================
|
||||
|
||||
/// RSA 公钥解析缓存:PEM -> RSAPublicKey
|
||||
///
|
||||
/// RSA 密钥生命周期长(通常每设备一对),ASN1 解析 + BigInt 构造代价较高。
|
||||
/// 解析结果在内存中复用,省去重复解析开销。上限 8 条,满时淘汰最早。
|
||||
final _rsaPublicKeyCache = <String, RSAPublicKey>{};
|
||||
|
||||
/// RSA 私钥解析缓存:PEM -> RSAPrivateKey
|
||||
final _rsaPrivateKeyCache = <String, RSAPrivateKey>{};
|
||||
|
||||
// ==================== 性能优化:session key bytes 缓存 ====================
|
||||
|
||||
/// Session key Base64 → 字节缓存
|
||||
///
|
||||
/// _deriveKeyForRound 和 _pbkdf2Derive 每次都需要 base64Decode(sessionKey),
|
||||
/// 对同一会话的多条消息重复解码。缓存后只解码一次,满时淘汰最早。
|
||||
final _sessionKeyBytesCache = <String, Uint8List>{};
|
||||
|
||||
// ==================== RSA 密钥管理 ====================
|
||||
|
||||
/// 生成 RSA 密钥对(同步,阻塞主线程)
|
||||
@@ -419,7 +452,7 @@ class EncryptionFlutterService {
|
||||
switch (kdfMode) {
|
||||
case KdfMode.md5:
|
||||
// 将 sessionKey + round 一起参与 hash,保证不同 round 产出不同密钥
|
||||
final keyBytes = base64Decode(sessionKey);
|
||||
final keyBytes = _getSessionKeyBytes(sessionKey);
|
||||
final roundBytes = utf8.encode(':$targetRound');
|
||||
final combined = Uint8List(keyBytes.length + roundBytes.length)
|
||||
..setRange(0, keyBytes.length, keyBytes)
|
||||
@@ -449,7 +482,7 @@ class EncryptionFlutterService {
|
||||
/// 迭代次数由 [pbkdf2Iterations] 控制(默认 10000)。
|
||||
/// 输出 16 字节(AES-128 密钥)。
|
||||
Uint8List _pbkdf2Derive(String sessionKey, int targetRound) {
|
||||
final keyBytes = base64Decode(sessionKey);
|
||||
final keyBytes = _getSessionKeyBytes(sessionKey);
|
||||
final salt = utf8.encode('round:$targetRound');
|
||||
|
||||
final derivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
|
||||
@@ -460,14 +493,20 @@ class EncryptionFlutterService {
|
||||
return derivator.process(Uint8List.fromList(keyBytes));
|
||||
}
|
||||
|
||||
/// 解析 RSA 公钥 PEM
|
||||
/// 解析 RSA 公钥 PEM(带缓存)
|
||||
RSAPublicKey _parsePublicKey(String pem) {
|
||||
final base64 = pem
|
||||
final cached = _rsaPublicKeyCache.remove(pem);
|
||||
if (cached != null) {
|
||||
_rsaPublicKeyCache[pem] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
final b64 = pem
|
||||
.replaceAll('-----BEGIN PUBLIC KEY-----', '')
|
||||
.replaceAll('-----END PUBLIC KEY-----', '')
|
||||
.replaceAll('\n', '')
|
||||
.trim();
|
||||
final bytes = base64Decode(base64);
|
||||
final bytes = base64Decode(b64);
|
||||
|
||||
final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
|
||||
final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
|
||||
@@ -480,20 +519,32 @@ class EncryptionFlutterService {
|
||||
final modulus = keySeq.elements[0] as ASN1Integer;
|
||||
final publicExponent = keySeq.elements[1] as ASN1Integer;
|
||||
|
||||
return RSAPublicKey(
|
||||
final key = RSAPublicKey(
|
||||
modulus.valueAsBigInteger,
|
||||
publicExponent.valueAsBigInteger,
|
||||
);
|
||||
|
||||
if (_rsaPublicKeyCache.length >= _maxRsaKeyCacheSize) {
|
||||
_rsaPublicKeyCache.remove(_rsaPublicKeyCache.keys.first);
|
||||
}
|
||||
_rsaPublicKeyCache[pem] = key;
|
||||
return key;
|
||||
}
|
||||
|
||||
/// 解析 RSA 私钥 PEM
|
||||
/// 解析 RSA 私钥 PEM(带缓存)
|
||||
RSAPrivateKey _parsePrivateKey(String pem) {
|
||||
final base64 = pem
|
||||
final cached = _rsaPrivateKeyCache.remove(pem);
|
||||
if (cached != null) {
|
||||
_rsaPrivateKeyCache[pem] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
final b64 = pem
|
||||
.replaceAll('-----BEGIN PRIVATE KEY-----', '')
|
||||
.replaceAll('-----END PRIVATE KEY-----', '')
|
||||
.replaceAll('\n', '')
|
||||
.trim();
|
||||
final bytes = base64Decode(base64);
|
||||
final bytes = base64Decode(b64);
|
||||
|
||||
final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
|
||||
final keySeq = asn1Parser.nextObject() as ASN1Sequence;
|
||||
@@ -503,12 +554,35 @@ class EncryptionFlutterService {
|
||||
final p = keySeq.elements[4] as ASN1Integer;
|
||||
final q = keySeq.elements[5] as ASN1Integer;
|
||||
|
||||
return RSAPrivateKey(
|
||||
final key = RSAPrivateKey(
|
||||
modulus.valueAsBigInteger,
|
||||
privateExponent.valueAsBigInteger,
|
||||
p.valueAsBigInteger,
|
||||
q.valueAsBigInteger,
|
||||
);
|
||||
|
||||
if (_rsaPrivateKeyCache.length >= _maxRsaKeyCacheSize) {
|
||||
_rsaPrivateKeyCache.remove(_rsaPrivateKeyCache.keys.first);
|
||||
}
|
||||
_rsaPrivateKeyCache[pem] = key;
|
||||
return key;
|
||||
}
|
||||
|
||||
/// session key Base64 → 字节(带缓存)
|
||||
///
|
||||
/// 同一 session key 在多条消息加解密中反复 decode,缓存后只做一次。
|
||||
Uint8List _getSessionKeyBytes(String sessionKey) {
|
||||
final cached = _sessionKeyBytesCache.remove(sessionKey);
|
||||
if (cached != null) {
|
||||
_sessionKeyBytesCache[sessionKey] = cached;
|
||||
return cached;
|
||||
}
|
||||
final bytes = base64Decode(sessionKey);
|
||||
if (_sessionKeyBytesCache.length >= _maxSessionKeyBytesCacheSize) {
|
||||
_sessionKeyBytesCache.remove(_sessionKeyBytesCache.keys.first);
|
||||
}
|
||||
_sessionKeyBytesCache[sessionKey] = bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// Hex 字符串转字节
|
||||
|
||||
Reference in New Issue
Block a user