Initial project

This commit is contained in:
Cody
2026-03-06 14:56:17 +08:00
parent 977b627b15
commit bf9e099747
1180 changed files with 50973 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
/// Method Channel 常量
/// 定義 Flutter 與原生平台通訊的方法名稱
///
/// 注意:加密邏輯已移至 Flutter 端
/// 原生端僅保留同步功能
class EncryptionMethodChannel {
// ==================== Channel 配置 ====================
/// Channel 名稱
static const String channelName = 'cipher_guard_sdk/e2e';
// ==================== 同步功能 (保留在原生端) ====================
/// 同步單個聊天室金鑰到原生
static const String syncEncryptionKey = 'syncEncryptionKey';
/// 批量同步所有聊天室金鑰
static const String syncAllEncryptionKeys = 'syncAllEncryptionKeys';
// ==================== 配置相關 ====================
/// 獲取 SDK 版本
static const String getPlatformVersion = 'getPlatformVersion';
}

View File

@@ -0,0 +1,471 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:asn1lib/asn1lib.dart';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart' as encrypt_pkg;
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/key_generators/api.dart';
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
import 'package:pointycastle/random/fortuna_random.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/asymmetric/pkcs1.dart';
/// Flutter Encryption Service
/// Implements all encryption logic in Flutter using pointycastle and encrypt packages
/// Replaces native Android/iOS encryption implementations
class EncryptionFlutterService {
// ==================== Constants ====================
static const int sessionKeySize = 32;
static const int gcmIvLength = 12;
// ==================== RSA Key Management ====================
/// Generate RSA key pair in PEM format
RsaKeyPairResult generateRsaKeyPair({int keySize = 1024}) {
try {
// Get secure random
final secureRandom = FortunaRandom();
secureRandom.seed(KeyParameter(_generateSecureRandomBytes(32)));
// Create RSA key generator
final keyGen = RSAKeyGenerator();
keyGen.init(ParametersWithRandom(
RSAKeyGeneratorParameters(BigInt.parse('65537'), keySize, 64),
secureRandom,
));
// Generate key pair
final keyPair = keyGen.generateKeyPair();
final rsaPublicKey = keyPair.publicKey as RSAPublicKey;
final rsaPrivateKey = keyPair.privateKey as RSAPrivateKey;
// Export to PEM format
final publicKeyPem = _encodeRSAPublicKey(rsaPublicKey);
final privateKeyPem = _encodeRSAPrivateKey(rsaPrivateKey);
return RsaKeyPairResult(
publicKey: publicKeyPem,
privateKey: privateKeyPem,
);
} catch (e) {
throw Exception('Failed to generate RSA key pair: $e');
}
}
/// Encode RSA public key to PEM format using asn1lib
String _encodeRSAPublicKey(RSAPublicKey publicKey) {
// Build RSAPublicKeyInfo structure
final topSeq = ASN1Sequence();
// AlgorithmIdentifier: OID 1.2.840.113549.1.1.1 + NULL
final algoSeq = ASN1Sequence();
algoSeq.add(ASN1ObjectIdentifier([1, 2, 840, 113549, 1, 1, 1])); // RSA
algoSeq.add(ASN1Null());
topSeq.add(algoSeq);
// RSAPublicKey: modulus + publicExponent
final keySeq = ASN1Sequence();
keySeq.add(ASN1Integer(publicKey.n!));
keySeq.add(ASN1Integer(publicKey.exponent!));
// BitString wrapping the key (with 0 unused bits prefix)
final keyBytes = keySeq.encodedBytes;
final keyList = List<int>.from(keyBytes);
keyList.insert(0, 0); // Add unused bits byte
topSeq.add(ASN1BitString(keyList));
final derBytes = topSeq.encodedBytes;
final base64 = base64Encode(derBytes.toList());
return '-----BEGIN PUBLIC KEY-----\n$base64\n-----END PUBLIC KEY-----';
}
/// Encode RSA private key to PEM format using asn1lib
String _encodeRSAPrivateKey(RSAPrivateKey privateKey) {
// Build RSAPrivateKey structure (PKCS#8 format)
final topSeq = ASN1Sequence();
// Version (0)
topSeq.add(ASN1Integer(BigInt.zero));
// Modulus
topSeq.add(ASN1Integer(privateKey.n!));
// Public Exponent
topSeq.add(ASN1Integer(privateKey.exponent!));
// Private Exponent
topSeq.add(ASN1Integer(privateKey.privateExponent!));
// Prime P
topSeq.add(ASN1Integer(privateKey.p!));
// Prime Q
topSeq.add(ASN1Integer(privateKey.q!));
// (Optional CRT params omitted for simplicity)
final derBytes = topSeq.encodedBytes;
final base64 = base64Encode(derBytes.toList());
return '-----BEGIN PRIVATE KEY-----\n$base64\n-----END PRIVATE KEY-----';
}
// ==================== Private Key Encryption/Decryption ====================
/// Encrypt private key with password (AES-CBC with MD5-derived key)
String encryptPrivateKey({
required String privateKey,
required String password,
}) {
try {
// Generate AES key from MD5(password)
final aesKey = _md5Hash(password);
// Generate random IV (16 bytes)
final iv = _generateSecureRandomBytes(16);
// AES encrypt using encrypt package
final secretKey = encrypt_pkg.Key(aesKey);
final encryptor = encrypt_pkg.Encrypter(
encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.cbc),
);
final encrypted = encryptor.encrypt(privateKey, iv: encrypt_pkg.IV(iv));
final encryptedBytes = encrypted.bytes;
// Combine IV + encrypted data
final combined = Uint8List(iv.length + encryptedBytes.length);
combined.setAll(0, iv);
combined.setAll(iv.length, encryptedBytes);
return base64Encode(combined);
} catch (e) {
throw Exception('Failed to encrypt private key: $e');
}
}
/// Decrypt private key with password (AES-CBC with MD5-derived key)
String decryptPrivateKey({
required String encryptedPrivateKey,
required String password,
}) {
try {
// Generate AES key from MD5(password)
final aesKey = _md5Hash(password);
// Decode Base64
final combined = base64Decode(encryptedPrivateKey);
// Extract IV and encrypted data
final iv = combined.sublist(0, 16);
final encBytes = combined.sublist(16);
// AES decrypt
final secretKey = encrypt_pkg.Key(aesKey);
final encryptor = encrypt_pkg.Encrypter(
encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.cbc),
);
final decrypted = encryptor.decrypt(
encrypt_pkg.Encrypted(encBytes),
iv: encrypt_pkg.IV(iv),
);
return decrypted;
} catch (e) {
throw Exception('Failed to decrypt private key: $e');
}
}
// ==================== Session Key Management ====================
/// Generate session key (32 bytes random)
SessionKeyResult generateSessionKey({int initialRound = 1}) {
final keyBytes = _generateSecureRandomBytes(sessionKeySize);
final key = base64Encode(keyBytes);
return SessionKeyResult(
key: key,
round: initialRound,
);
}
/// Encrypt session key with RSA public key
String encryptSessionKey({
required String sessionKey,
required String publicKey,
}) {
try {
// Parse RSA public key
final rsaPublicKey = _parsePublicKey(publicKey);
// RSA encrypt using PKCS1 padding (like native implementations)
final cipher = PKCS1Encoding(RSAEngine());
cipher.init(true, PublicKeyParameter<RSAPublicKey>(rsaPublicKey));
final encryptedBytes = cipher.process(utf8.encode(sessionKey));
return base64Encode(encryptedBytes);
} catch (e) {
throw Exception('Failed to encrypt session key: $e');
}
}
/// Decrypt session key with RSA private key
String decryptSessionKey({
required String encryptedSessionKey,
required String privateKey,
}) {
try {
// Parse RSA private key
final rsaPrivateKey = _parsePrivateKey(privateKey);
// RSA decrypt using PKCS1 padding (like native implementations)
final cipher = PKCS1Encoding(RSAEngine());
cipher.init(false, PrivateKeyParameter<RSAPrivateKey>(rsaPrivateKey));
final decryptedBytes = cipher.process(base64Decode(encryptedSessionKey));
return utf8.decode(decryptedBytes);
} catch (e) {
throw Exception('Failed to decrypt session key: $e');
}
}
// ==================== Message Encryption/Decryption ====================
/// Encrypt message (AES-CTR with round-based key derivation)
EncryptedMessageResult encryptMessage({
required String plaintext,
required String sessionKey,
required int round,
}) {
try {
// Derive key for round
final actualKey = _deriveKeyForRound(sessionKey, round);
// Generate random IV (16 bytes for CTR)
final iv = _generateSecureRandomBytes(16);
// AES-CTR encrypt
final secretKey = encrypt_pkg.Key(actualKey);
final encryptor = encrypt_pkg.Encrypter(
encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.ctr),
);
final encrypted = encryptor.encrypt(plaintext, iv: encrypt_pkg.IV(iv));
final encryptedBytes = encrypted.bytes;
// Combine IV + encrypted data
final combined = Uint8List(iv.length + encryptedBytes.length);
combined.setAll(0, iv);
combined.setAll(iv.length, encryptedBytes);
final data = base64Encode(combined);
return EncryptedMessageResult(
round: round,
data: data,
);
} catch (e) {
throw Exception('Failed to encrypt message: $e');
}
}
/// Decrypt message (AES-CTR with round-based key derivation)
String decryptMessage({
required String encryptedData,
required String sessionKey,
required int round,
}) {
try {
// Derive key for round
final actualKey = _deriveKeyForRound(sessionKey, round);
// Decode Base64
final combined = base64Decode(encryptedData);
// Extract IV and encrypted data
final iv = combined.sublist(0, 16);
final encBytes = combined.sublist(16);
// AES-CTR decrypt
final secretKey = encrypt_pkg.Key(actualKey);
final encryptor = encrypt_pkg.Encrypter(
encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.ctr),
);
final decrypted = encryptor.decrypt(
encrypt_pkg.Encrypted(encBytes),
iv: encrypt_pkg.IV(iv),
);
return decrypted;
} catch (e) {
throw Exception('Failed to decrypt message: $e');
}
}
// ==================== Push Notification Decryption ====================
/// Set AES secret for push notification decryption
void setAesSecret(String aesSecret) {
_aesSecret = aesSecret;
}
String? _aesSecret;
/// Decrypt push notification (AES-GCM)
String decryptPushNotification({
required String encryptedData,
}) {
try {
final secret = _aesSecret;
if (secret == null) {
throw Exception('AES_SECRET not set');
}
// Convert hex string to bytes
final secretBytes = _hexStringToBytes(secret);
// Decode Base64
final combined = base64Decode(encryptedData);
// Extract IV and encrypted data
final iv = combined.sublist(0, gcmIvLength);
final encBytes = combined.sublist(gcmIvLength);
// AES-GCM decrypt
final secretKey = encrypt_pkg.Key(secretBytes);
final encryptor = encrypt_pkg.Encrypter(
encrypt_pkg.AES(secretKey, mode: encrypt_pkg.AESMode.gcm),
);
final decrypted = encryptor.decrypt(
encrypt_pkg.Encrypted(encBytes),
iv: encrypt_pkg.IV(iv),
);
return decrypted;
} catch (e) {
throw Exception('Failed to decrypt push notification: $e');
}
}
// ==================== Helper Methods ====================
/// Generate secure random bytes
Uint8List _generateSecureRandomBytes(int length) {
final random = Random.secure();
final bytes = Uint8List(length);
for (var i = 0; i < length; i++) {
bytes[i] = random.nextInt(256);
}
return bytes;
}
/// MD5 hash
Uint8List _md5Hash(String input) {
final bytes = utf8.encode(input);
final hash = md5.convert(bytes).bytes as Uint8List;
return hash;
}
/// Derive key for round (MD5 hash of session key)
Uint8List _deriveKeyForRound(String sessionKey, int targetRound) {
// Base64 decode session key
final keyBytes = base64Decode(sessionKey);
// Apply MD5 for the round (simplified version)
final hash = md5.convert(keyBytes).bytes as Uint8List;
return hash;
}
/// Parse RSA public key from PEM
RSAPublicKey _parsePublicKey(String pem) {
final base64 = pem
.replaceAll('-----BEGIN PUBLIC KEY-----', '')
.replaceAll('-----END PUBLIC KEY-----', '')
.replaceAll('\n', '')
.trim();
final bytes = base64Decode(base64);
// Parse ASN.1 DER format
final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
final subjectPublicKeyInfo = topLevelSeq.elements[1] as ASN1BitString;
final keyBytes = subjectPublicKeyInfo.contentBytes();
final keyParser = ASN1Parser(Uint8List.fromList(keyBytes));
final keySeq = keyParser.nextObject() as ASN1Sequence;
final modulus = keySeq.elements[0] as ASN1Integer;
final publicExponent = keySeq.elements[1] as ASN1Integer;
return RSAPublicKey(
modulus.valueAsBigInteger,
publicExponent.valueAsBigInteger,
);
}
/// Parse RSA private key from PEM
RSAPrivateKey _parsePrivateKey(String pem) {
final base64 = pem
.replaceAll('-----BEGIN PRIVATE KEY-----', '')
.replaceAll('-----END PRIVATE KEY-----', '')
.replaceAll('\n', '')
.trim();
final bytes = base64Decode(base64);
// Parse ASN.1 DER format
final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
final keySeq = asn1Parser.nextObject() as ASN1Sequence;
final modulus = keySeq.elements[1] as ASN1Integer;
final privateExponent = keySeq.elements[3] as ASN1Integer;
final p = keySeq.elements[4] as ASN1Integer;
final q = keySeq.elements[5] as ASN1Integer;
return RSAPrivateKey(
modulus.valueAsBigInteger,
privateExponent.valueAsBigInteger,
p.valueAsBigInteger,
q.valueAsBigInteger,
);
}
/// Convert hex string to bytes
Uint8List _hexStringToBytes(String hex) {
final len = hex.length;
final data = Uint8List(len ~/ 2);
for (var i = 0; i < len; i += 2) {
data[i ~/ 2] = (int.parse(hex[i], radix: 16) << 4) + int.parse(hex[i + 1], radix: 16);
}
return data;
}
}
// ==================== Result Classes ====================
class RsaKeyPairResult {
final String publicKey;
final String privateKey;
RsaKeyPairResult({required this.publicKey, required this.privateKey});
}
class SessionKeyResult {
final String key;
final int round;
SessionKeyResult({required this.key, required this.round});
}
class EncryptedMessageResult {
final int round;
final String data;
EncryptedMessageResult({required this.round, required this.data});
}

View File

@@ -0,0 +1,153 @@
import 'dart:convert';
import 'package:cipher_guard_sdk/src/data/constants/method_channel_constants.dart';
import 'package:flutter/services.dart';
import '../dto/method_channel_response.dart';
// ==================== Method Channel 資料來源 ====================
/// Method Channel 資料來源
/// 負責與原生平台 (iOS/Android) 通訊
///
/// 注意:加密邏輯移至 Flutter 端
/// 原生端僅保留同步功能
class EncryptionMethodChannelDataSource {
final MethodChannel _channel;
EncryptionMethodChannelDataSource() : _channel = const MethodChannel(EncryptionMethodChannel.channelName);
// ==================== 內部響應解析 ====================
/// 標準化響應解析
/// 解析原生平台返回的標準化響應格式
/// 格式: {"success": bool, "data": {...}, "error": String?}
MethodChannelResponse<Map<String, dynamic>> _parseResponse(Object? result) {
if (result == null) {
return MethodChannelResponse.failure('Null response from native');
}
if (result is Map<String, dynamic>) {
return _parseStandardResponse(result);
}
if (result is Map<Object?, Object?>) {
var mapResult = _coerceToMapStringDynamic(result);
return _parseStandardResponse(mapResult);
}
// 嘗試解析 JSON 字串
if (result is String && result.startsWith('{')) {
try {
final decoded = jsonDecode(result);
if (decoded is Map<String, dynamic>) {
return _parseStandardResponse(decoded);
}
} catch (_) {}
}
// 不支援的格式
return MethodChannelResponse.failure(
'Unsupported response format: ${result.runtimeType}',
);
}
/// 解析標準化響應格式
MethodChannelResponse<Map<String, dynamic>> _parseStandardResponse(Map<String, dynamic> map) {
final success = map['success'] == "true";
final data = map['data'];
final error = map['error'];
final decodedResult = jsonDecode(data);
if (success) {
return MethodChannelResponse.success(decodedResult);
}
return MethodChannelResponse<Map<String, dynamic>>(
isSuccess: success,
data: null,
error: error?.toString() ?? (success ? null : 'Unknown error'),
);
}
/// 將任意回傳結果轉成 `Map<String, dynamic>`
Map<String, dynamic> _coerceToMapStringDynamic(Object? result) {
if (result == null) return <String, dynamic>{};
if (result is Map) {
return result.map((k, v) => MapEntry(k.toString(), v));
}
if (result is String) {
try {
final decoded = jsonDecode(result);
if (decoded is Map) {
return decoded.map((k, v) => MapEntry(k.toString(), v));
}
} catch (_) {}
}
return <String, dynamic>{};
}
// ==================== 原生平台同步 ====================
/// 同步加密金鑰到原生平台
Future<MethodChannelResponse<bool>> syncEncryptionKey({
required String chatId,
required int activeRound,
required int round,
required String activeKey,
required bool isSingle,
}) async {
final result = await _channel.invokeMethod(
EncryptionMethodChannel.syncEncryptionKey,
{
'chatId': chatId,
'activeRound': activeRound,
'round': round,
'activeKey': activeKey,
'isSingle': isSingle,
},
);
final response = _parseResponse(result);
return MethodChannelResponse<bool>(
isSuccess: response.isSuccess,
data: response.isSuccess,
error: response.error,
);
}
/// 批量同步所有加密金鑰
Future<MethodChannelResponse<bool>> syncAllEncryptionKeys({
required Map<String, Map<String, dynamic>> chatMap,
}) async {
final result = await _channel.invokeMethod(
EncryptionMethodChannel.syncAllEncryptionKeys,
{'chatMap': chatMap},
);
final response = _parseResponse(result);
return MethodChannelResponse<bool>(
isSuccess: response.isSuccess,
data: response.isSuccess,
error: response.error,
);
}
// ==================== 配置相關 ====================
/// 獲取平台版本
Future<MethodChannelResponse<String>> getPlatformVersion() async {
final result = await _channel.invokeMethod(
EncryptionMethodChannel.getPlatformVersion,
);
final response = _parseResponse(result);
if (response.isSuccess) {
final version = response.data!['version']?.toString() ?? 'Unknown';
return MethodChannelResponse.success(version);
}
return MethodChannelResponse.failure(response.error);
}
}

View File

@@ -0,0 +1,67 @@
/// 聊天室金鑰 DTO
/// 負責與原生平台傳輸聊天室加密金鑰資料
library;
import '../../domain/entities/chat_encryption_key.dart';
import '../../domain/entities/session_key.dart';
class ChatEncryptionKeyDto {
final String chatId;
final String activeKey; // Base64 編碼的活躍金鑰
final int activeRound;
final int round;
final bool isSingle;
const ChatEncryptionKeyDto({
required this.chatId,
required this.activeKey,
required this.activeRound,
required this.round,
required this.isSingle,
});
/// 從 JSON 創建
factory ChatEncryptionKeyDto.fromJson(Map<String, dynamic> json) {
return ChatEncryptionKeyDto(
chatId: json['chatId'] as String,
activeKey: json['activeKey'] as String,
activeRound: json['activeRound'] as int,
round: json['round'] as int,
isSingle: json['isSingle'] as bool,
);
}
/// 轉換為 JSON
Map<String, dynamic> toJson() {
return {
'chatId': chatId,
'activeKey': activeKey,
'activeRound': activeRound,
'round': round,
'isSingle': isSingle,
};
}
/// 轉換為 Domain Entity
ChatEncryptionKey toEntity() {
return ChatEncryptionKey(
chatId: chatId,
sessionKey: SessionKey(key: activeKey, round: activeRound),
activeKeyRound: activeRound,
currentRound: round,
isSingle: isSingle,
);
}
/// 從 Domain Entity 創建
static ChatEncryptionKeyDto fromEntity(ChatEncryptionKey entity) {
return ChatEncryptionKeyDto(
chatId: entity.chatId,
activeKey: entity.sessionKey.key,
activeRound: entity.activeKeyRound,
round: entity.currentRound,
isSingle: entity.isSingle,
);
}
}

View File

@@ -0,0 +1,51 @@
/// 加密訊息 DTO
/// 負責與原生平台傳輸加密資料
library;
import '../../domain/entities/encrypted_message.dart';
class EncryptedMessageDto {
final int round;
final String data;
const EncryptedMessageDto({
required this.round,
required this.data,
});
/// 從標準化響應創建
/// 響應格式: {"success": true, "data": {"round": int, "encryptedData": "..."}, "error": null}
factory EncryptedMessageDto.fromResponse(Map<String, dynamic> response) {
final data = response['data'] as Map<String, dynamic>? ?? {};
return EncryptedMessageDto(
round: data['round'] as int? ?? 0,
data: data['encryptedData']?.toString() ?? '',
);
}
/// 轉換為 JSON
Map<String, dynamic> toJson() {
return {
'round': round,
'data': data,
};
}
/// 轉換為 Domain Entity
EncryptedMessage toEntity() {
return EncryptedMessage(
round: round,
data: data,
);
}
/// 從 Domain Entity 創建
static EncryptedMessageDto fromEntity(EncryptedMessage entity) {
return EncryptedMessageDto(
round: entity.round,
data: entity.data,
);
}
}

View File

@@ -0,0 +1,76 @@
/// Method Channel 標準化響應 DTO
/// 定義與原生平台通訊的標準響應格式
class MethodChannelResponse<T> {
final bool isSuccess;
final T? data;
final String? error;
const MethodChannelResponse({
required this.isSuccess,
this.data,
this.error,
});
/// 創建成功響應
factory MethodChannelResponse.success(T data) {
return MethodChannelResponse(
isSuccess: true,
data: data,
error: null,
);
}
/// 創建失敗響應
factory MethodChannelResponse.failure(String? error) {
return MethodChannelResponse<T>(
isSuccess: false,
data: null,
error: error,
);
}
/// 檢查是否成功
bool get succeeded => isSuccess;
/// 獲取資料,失敗時拋出異常
T getOrThrow() {
if (!isSuccess || data == null) {
throw Exception(error ?? 'Unknown error');
}
return data!;
}
}
/// RSA 金鑰對響應 DTO
class RsaKeyPairResponse {
final String publicKey;
final String privateKey;
const RsaKeyPairResponse({
required this.publicKey,
required this.privateKey,
});
}
/// 會話金鑰響應 DTO
class SessionKeyResponse {
final String key;
final int round;
const SessionKeyResponse({
required this.key,
required this.round,
});
}
/// 加密訊息響應 DTO
class EncryptedMessageResponse {
final int round;
final String encryptedData;
const EncryptedMessageResponse({
required this.round,
required this.encryptedData,
});
}

View File

@@ -0,0 +1,51 @@
/// RSA 金鑰對 DTO
/// 負責與原生平台傳輸資料
library;
import '../../domain/entities/rsa_key_pair.dart';
class RsaKeyPairDto {
final String publicKey;
final String privateKey;
const RsaKeyPairDto({
required this.publicKey,
required this.privateKey,
});
/// 從標準化響應創建
/// 響應格式: {"success": true, "data": {"publicKey": "...", "privateKey": "..."}, "error": null}
factory RsaKeyPairDto.fromResponse(Map<String, dynamic> response) {
final data = response['data'] as Map<String, dynamic>? ?? {};
return RsaKeyPairDto(
publicKey: data['publicKey']?.toString() ?? '',
privateKey: data['privateKey']?.toString() ?? '',
);
}
/// 轉換為 JSON
Map<String, dynamic> toJson() {
return {
'publicKey': publicKey,
'privateKey': privateKey,
};
}
/// 轉換為 Domain Entity
RsaKeyPair toEntity() {
return RsaKeyPair(
publicKey: publicKey,
privateKey: privateKey,
);
}
/// 從 Domain Entity 創建
static RsaKeyPairDto fromEntity(RsaKeyPair entity) {
return RsaKeyPairDto(
publicKey: entity.publicKey,
privateKey: entity.privateKey,
);
}
}

View File

@@ -0,0 +1,150 @@
import 'package:cipher_guard_sdk/src/domain/entities/rsa_key_pair.dart';
import 'package:cipher_guard_sdk/src/domain/entities/session_key.dart';
import 'package:cipher_guard_sdk/src/domain/entities/encrypted_message.dart';
import 'package:cipher_guard_sdk/src/domain/repositories/encryption_repository.dart';
import '../datasources/encryption_flutter_service.dart';
/// 加密 Repository 實作
/// 實現 EncryptionRepository 介面
/// 使用 Flutter 本地加密服務,無需原生平台
class EncryptionRepositoryImpl implements EncryptionRepository {
final EncryptionFlutterService _service;
const EncryptionRepositoryImpl(this._service);
// ==================== RSA 金鑰管理 ====================
@override
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024}) async {
final result = _service.generateRsaKeyPair(keySize: keySize);
return RsaKeyPair(
publicKey: result.publicKey,
privateKey: result.privateKey,
);
}
@override
Future<String> encryptPrivateKey({
required String privateKey,
required String password,
}) async {
return _service.encryptPrivateKey(
privateKey: privateKey,
password: password,
);
}
@override
Future<String> decryptPrivateKey({
required String encryptedPrivateKey,
required String password,
}) async {
return _service.decryptPrivateKey(
encryptedPrivateKey: encryptedPrivateKey,
password: password,
);
}
// ==================== 會話金鑰管理 ====================
@override
Future<SessionKey> generateSessionKey({int initialRound = 1}) async {
final result = _service.generateSessionKey(initialRound: initialRound);
return SessionKey(
key: result.key,
round: result.round,
);
}
@override
Future<String> encryptSessionKey({
required String sessionKey,
required String publicKey,
}) async {
return _service.encryptSessionKey(
sessionKey: sessionKey,
publicKey: publicKey,
);
}
@override
Future<String> decryptSessionKey({
required String encryptedSessionKey,
required String privateKey,
}) async {
return _service.decryptSessionKey(
encryptedSessionKey: encryptedSessionKey,
privateKey: privateKey,
);
}
// ==================== 訊息加解密 ====================
@override
Future<EncryptedMessage> encryptMessage({
required String plaintext,
required String sessionKey,
required int round,
}) async {
final result = _service.encryptMessage(
plaintext: plaintext,
sessionKey: sessionKey,
round: round,
);
return EncryptedMessage(
round: result.round,
data: result.data,
);
}
@override
Future<String> decryptMessage({
required String encryptedData,
required String sessionKey,
required int round,
}) async {
return _service.decryptMessage(
encryptedData: encryptedData,
sessionKey: sessionKey,
round: round,
);
}
// ==================== 原生平台同步 ====================
@override
Future<void> syncEncryptionKeysToNative({
required String chatId,
required int activeRound,
required int round,
required String activeKey,
required bool isSingle,
}) async {
// 同步功能仍由原生平台處理 (iOS App Group / Android SharedPreferences)
// 此處保留接口,但實際同步透過 MethodChannel 完成
// 注意:同步功能目前標記為保留
}
@override
Future<void> syncAllEncryptionKeys({
required Map<String, Map<String, dynamic>> chatMap,
}) async {
// 批量同步功能仍由原生平台處理
// 此處保留接口,但實際同步透過 MethodChannel 完成
}
// ==================== 推送通知解密 ====================
@override
Future<void> setAesSecret({required String aesSecret}) async {
_service.setAesSecret(aesSecret);
}
@override
Future<String?> decryptPushNotification({
required String encryptedData,
}) async {
return _service.decryptPushNotification(encryptedData: encryptedData);
}
}