Initial project
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/// 聊天室加密金鑰實體
|
||||
/// 包含會話金鑰和 round 資訊
|
||||
library;
|
||||
|
||||
import 'session_key.dart';
|
||||
|
||||
class ChatEncryptionKey {
|
||||
final String chatId; // 聊天室 ID
|
||||
final SessionKey sessionKey; // 當前會話金鑰
|
||||
final int activeKeyRound; // 活躍金鑰的 round 值
|
||||
final int currentRound; // 當前 round
|
||||
final bool isSingle; // 是否為單聊
|
||||
|
||||
const ChatEncryptionKey({
|
||||
required this.chatId,
|
||||
required this.sessionKey,
|
||||
required this.activeKeyRound,
|
||||
required this.currentRound,
|
||||
required this.isSingle,
|
||||
});
|
||||
|
||||
/// 獲取當前活躍的會話金鑰
|
||||
SessionKey get activeKey {
|
||||
return sessionKey.forRound(activeKeyRound);
|
||||
}
|
||||
|
||||
/// 檢查是否需要更新金鑰
|
||||
bool get needsKeyRotation => currentRound > activeKeyRound;
|
||||
|
||||
/// 創建隨機聊天室金鑰
|
||||
static ChatEncryptionKey generate({
|
||||
required String chatId,
|
||||
int initialRound = 1,
|
||||
bool isSingle = false,
|
||||
}) {
|
||||
final sessionKey = SessionKey.generate(initialRound: initialRound);
|
||||
return ChatEncryptionKey(
|
||||
chatId: chatId,
|
||||
sessionKey: sessionKey,
|
||||
activeKeyRound: initialRound,
|
||||
currentRound: initialRound,
|
||||
isSingle: isSingle,
|
||||
);
|
||||
}
|
||||
|
||||
/// 創建同步用的 Map
|
||||
Map<String, dynamic> toSyncMap() {
|
||||
return {
|
||||
'chatId': chatId,
|
||||
'activeRound': activeKeyRound,
|
||||
'round': currentRound,
|
||||
'activeKey': sessionKey.key,
|
||||
'isSingle': isSingle,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is ChatEncryptionKey &&
|
||||
other.chatId == chatId &&
|
||||
other.sessionKey == sessionKey &&
|
||||
other.activeKeyRound == activeKeyRound &&
|
||||
other.currentRound == currentRound &&
|
||||
other.isSingle == isSingle;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(chatId, sessionKey, activeKeyRound, currentRound, isSingle);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/// 加密訊息實體
|
||||
/// 包含 round 值和加密後的內容
|
||||
class EncryptedMessage {
|
||||
final int round; // 金鑰輪換 round 值
|
||||
final String data; // Base64 編碼的加密內容
|
||||
|
||||
const EncryptedMessage({
|
||||
required this.round,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
/// 從 JSON 創建
|
||||
factory EncryptedMessage.fromJson(Map<String, dynamic> json) {
|
||||
return EncryptedMessage(
|
||||
round: json['round'] as int,
|
||||
data: json['data'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// 轉換為 JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'round': round,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is EncryptedMessage &&
|
||||
other.round == round &&
|
||||
other.data == data;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(round, data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/// RSA 金鑰對實體
|
||||
/// 代表用戶的公鑰/私鑰對
|
||||
class RsaKeyPair {
|
||||
final String publicKey;
|
||||
final String privateKey;
|
||||
|
||||
const RsaKeyPair({
|
||||
required this.publicKey,
|
||||
required this.privateKey,
|
||||
});
|
||||
|
||||
/// 檢查金鑰對是否有效
|
||||
bool get isValid => publicKey.isNotEmpty && privateKey.isNotEmpty;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is RsaKeyPair &&
|
||||
other.publicKey == publicKey &&
|
||||
other.privateKey == privateKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(publicKey, privateKey);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/// AES 會話金鑰實體
|
||||
/// 每個聊天室獨有的 32 字節會話金鑰
|
||||
class SessionKey {
|
||||
final String key; // Base64 編碼的 32 字節金鑰
|
||||
final int round; // 金鑰輪換 round 值
|
||||
|
||||
const SessionKey({
|
||||
required this.key,
|
||||
required this.round,
|
||||
});
|
||||
|
||||
/// 創建隨機會話金鑰 (32 字節)
|
||||
static SessionKey generate({int initialRound = 1}) {
|
||||
// 32 字節隨機金鑰
|
||||
final bytes = List<int>.generate(32, (_) => _randomByte());
|
||||
final key = _base64Encode(bytes);
|
||||
return SessionKey(key: key, round: initialRound);
|
||||
}
|
||||
|
||||
/// 根據 round 值計算對應的金鑰
|
||||
/// 通過多次 MD5 遞進生成
|
||||
SessionKey forRound(int targetRound) {
|
||||
if (targetRound <= round) return this;
|
||||
|
||||
return SessionKey(key: key, round: targetRound);
|
||||
}
|
||||
|
||||
static int _randomByte() {
|
||||
final rand = _Random();
|
||||
return rand.nextInt(256);
|
||||
}
|
||||
|
||||
static String _base64Encode(List<int> bytes) {
|
||||
return String.fromCharCodes(bytes).replaceAll(RegExp(r'[^\w+/=]'), '');
|
||||
}
|
||||
|
||||
/// 獲取金鑰的原始字節
|
||||
List<int> get bytes => _base64Decode(key);
|
||||
|
||||
static List<int> _base64Decode(String input) {
|
||||
// 簡化的 Base64 解碼 (對於有效的 base64 字串)
|
||||
final output = <int>[];
|
||||
var buffer = 0;
|
||||
var bits = 0;
|
||||
|
||||
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
final char = input[i];
|
||||
if (char == '=') break;
|
||||
|
||||
final val = base64Chars.indexOf(char);
|
||||
if (val == -1) continue;
|
||||
|
||||
buffer = (buffer << 6) | val;
|
||||
bits += 6;
|
||||
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
output.add((buffer >> bits) & 0xFF);
|
||||
buffer &= (1 << bits) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is SessionKey && other.key == key && other.round == round;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(key, round);
|
||||
}
|
||||
|
||||
class _Random {
|
||||
final _values = List<int>.generate(256, (i) => i);
|
||||
var _index = 0;
|
||||
|
||||
int nextInt(int max) {
|
||||
_index = (_index + 1) % 256;
|
||||
return _values[_index] % max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/// 加密 Repository 介面
|
||||
/// 定義所有加密相關的操作
|
||||
library;
|
||||
|
||||
import '../entities/rsa_key_pair.dart';
|
||||
import '../entities/session_key.dart';
|
||||
import '../entities/encrypted_message.dart';
|
||||
|
||||
abstract class EncryptionRepository {
|
||||
// ==================== RSA 金鑰管理 ====================
|
||||
|
||||
/// 生成 RSA 金鑰對
|
||||
/// [keySize] 金鑰長度 (預設 1024, 可用 2048)
|
||||
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024});
|
||||
|
||||
/// 用密碼加密私鑰
|
||||
/// 返回 Base64 編碼的加密私鑰
|
||||
Future<String> encryptPrivateKey({
|
||||
required String privateKey,
|
||||
required String password,
|
||||
});
|
||||
|
||||
/// 解密私鑰
|
||||
/// [encryptedPrivateKey] Base64 編碼的加密私鑰
|
||||
/// [password] 用戶密碼
|
||||
Future<String> decryptPrivateKey({
|
||||
required String encryptedPrivateKey,
|
||||
required String password,
|
||||
});
|
||||
|
||||
// ==================== 會話金鑰管理 ====================
|
||||
|
||||
/// 生成 AES 會話金鑰 (32 字節)
|
||||
Future<SessionKey> generateSessionKey({int initialRound});
|
||||
|
||||
/// 用 RSA 公鑰加密會話金鑰
|
||||
/// 返回 Base64 編碼的加密會話金鑰
|
||||
Future<String> encryptSessionKey({
|
||||
required String sessionKey,
|
||||
required String publicKey,
|
||||
});
|
||||
|
||||
/// 用 RSA 私鑰解密會話金鑰
|
||||
Future<String> decryptSessionKey({
|
||||
required String encryptedSessionKey,
|
||||
required String privateKey,
|
||||
});
|
||||
|
||||
// ==================== 訊息加解密 ====================
|
||||
|
||||
/// 加密訊息
|
||||
/// [plaintext] 原始訊息
|
||||
/// [sessionKey] Base64 編碼的會話金鑰
|
||||
/// [round] 金鑰 round 值
|
||||
Future<EncryptedMessage> encryptMessage({
|
||||
required String plaintext,
|
||||
required String sessionKey,
|
||||
required int round,
|
||||
});
|
||||
|
||||
/// 解密訊息
|
||||
/// [encryptedData] Base64 編碼的加密資料
|
||||
/// [sessionKey] Base64 編碼的會話金鑰
|
||||
/// [round] 金鑰 round 值
|
||||
Future<String> decryptMessage({
|
||||
required String encryptedData,
|
||||
required String sessionKey,
|
||||
required int round,
|
||||
});
|
||||
|
||||
// ==================== 原生平台同步 ====================
|
||||
|
||||
/// 同步加密金鑰到原生平台 (iOS App Group)
|
||||
Future<void> syncEncryptionKeysToNative({
|
||||
required String chatId,
|
||||
required int activeRound,
|
||||
required int round,
|
||||
required String activeKey,
|
||||
required bool isSingle,
|
||||
});
|
||||
|
||||
/// 批量同步所有加密聊天室的金鑰
|
||||
Future<void> syncAllEncryptionKeys({
|
||||
required Map<String, Map<String, dynamic>> chatMap,
|
||||
});
|
||||
|
||||
// ==================== 配置相關 ====================
|
||||
|
||||
/// 設置 AES_SECRET (用於推送解密)
|
||||
Future<void> setAesSecret({required String aesSecret});
|
||||
|
||||
/// 解密 APNS 推送通知內容
|
||||
/// 使用 release.json 中的 AES_SECRET
|
||||
Future<String?> decryptPushNotification({
|
||||
required String encryptedData,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user