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

37
packages/cipher_guard_sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
# However, for Flutter plugins, keeping pubspec.lock is recommended for dependency consistency
# /pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/
example/pubspec.lock
# Cipher Guard SDK specific
cipher_guard_sdk.iml

View File

@@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "bd7a4a6b5576630823ca344e3e684c53aa1a0f46"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
base_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
- platform: android
create_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
base_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
- platform: ios
create_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
base_revision: bd7a4a6b5576630823ca344e3e684c53aa1a0f46
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,18 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [0.0.1] - 2026-01-30
### 初始版本
- 發布 Cipher Guard SDK 初始版本
- RSA 金鑰管理
- ES 會話金鑰
- 訊息加解密
- 推送通知解密
- 原生平台同步

View File

@@ -0,0 +1 @@
TODO: Add your license here.

View File

@@ -0,0 +1,873 @@
# CipherGuard SDK API Documentation
## 概述
CipherGuard SDK 是一個 Flutter 外掛提供完整的端對端加密E2EE解決方案。該 SDK 簡化了加密整合流程,提供 RSA 金鑰管理、AES 會話金鑰管理、訊息加解密等功能。
### 主要特性
- **統一 API 介面** - 簡潔的抽象類設計,易於整合和使用
- **跨平台加密** - 所有加密邏輯在 Flutter 層實現Android 和 iOS 保持一致
- **RSA 金鑰管理** - 生成、加密/解密私鑰
- **AES 會話金鑰** - 每個聊天室獨立的安全會話金鑰
- **訊息加解密** - 使用 AES-CTR 模式進行高效加密
- **推送通知解密** - 支援 AES-GCM 模式解密推播通知
- **原生平台同步** - 支援 iOS App Group 同步加密金鑰
---
## Demo螢幕截圖
以下為 CipherGuard SDK 的範例應用程式截圖,展示各種功能設定介面:
<img src="https://raw.githubusercontent.com/leechupe/qrcode_sample/master/lib/cipher.PNG" width="100" alt="CipherGuard SDK Example">
圖片說明:
- 生成 RSA 金鑰對:產生 RSA Public / Private Key
- 加密私鑰:使用使用者密碼對 RSA 私鑰進行加密
- 解密私鑰:驗證加密後私鑰是否能正確還原
- 生成會話金鑰:產生聊天室專用的 AES Session Key
- 加密會話金鑰:使用 RSA 公鑰加密 Session Key模擬金鑰交換
- 解密會話金鑰:使用 RSA 私鑰解密 Session Key
- 加密訊息:使用 AES-CTR 模式加密文字訊息
- 解密訊息:解密加密後的訊息內容
- 畫面中顯示 解密結果Hello, this is a secret message!,代表端對端加解密流程成功
- 同步加密金鑰到原生:將聊天室金鑰同步至原生平台(例如 iOS App Group
- 用於支援 Notification Service Extension 或其他原生模組存取加密金鑰
- 設定 AES_SECRET設定用於推送通知 AES-GCM 解密的密鑰
- 對應實際 APNS 推送通知加密 / 解密使用情境
---
## API 總覽
<style>
table { font-size: 14px; }
td, th { padding: 8px; }
</style>
### 方法Methods
| 方法 | 回傳類型 | 說明 |
|------|----------|------|
| `platformVersion()` | `Future<String?>` | 獲取平台版本 |
| `generateRsaKeyPair({int keySize = 1024})` | `Future<RsaKeyPair>` | 生成 RSA 金鑰對 |
| `encryptPrivateKey({required String privateKey, required String password})` | `Future<String>` | 用密碼加密私鑰 |
| `decryptPrivateKey({required String encryptedPrivateKey, required String password})` | `Future<String>` | 解密私鑰 |
| `generateSessionKey({int initialRound = 1})` | `Future<SessionKey>` | 生成 AES 會話金鑰 |
| `encryptSessionKey({required String sessionKey, required String publicKey})` | `Future<String>` | 用 RSA 公鑰加密會話金鑰 |
| `decryptSessionKey({required String encryptedSessionKey, required String privateKey})` | `Future<String>` | 用 RSA 私鑰解密會話金鑰 |
| `encryptMessage({required String plaintext, required String sessionKey, required int round})` | `Future<EncryptedMessage>` | 加密訊息 |
| `decryptMessage({required String encryptedData, required String sessionKey, required int round})` | `Future<String>` | 解密訊息 |
| `syncEncryptionKey({required String chatId, required int activeRound, required int round, required String activeKeySingle})` |, required bool is `Future<void>` | 同步加密金鑰到原生平台 |
| `syncAllEncryptionKeys({required Map<String, Map<String, dynamic>> chatMap})` | `Future<void>` | 批量同步所有加密金鑰 |
| `setAesSecret({required String aesSecret})` | `Future<void>` | 設置 AES_SECRET |
| `decryptPushNotification({required String encryptedData})` | `Future<String?>` | 解密推送通知 |
### 實體類別Entities
| 類別 | 說明 |
|------|------|
| `RsaKeyPair` | RSA 金鑰對(公鑰/私鑰) |
| `SessionKey` | AES 會話金鑰 |
| `EncryptedMessage` | 加密訊息 |
---
## 安裝
`pubspec.yaml` 中新增依賴:
```yaml
dependencies:
cipher_guard_sdk: ^1.0.0
```
### Flutter 安裝
執行以下命令獲取依賴:
```bash
flutter pub get
```
### 初始化
`main.dart` 中初始化 SDK
```dart
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
```
---
## 快速開始
### 1. 初始化 SDK
```dart
import 'package:flutter/material.dart';
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final CipherGuardSdkApi cipherGuard = CipherGuardSdkApi();
@override
void initState() {
super.initState();
_testEncryption();
}
Future<void> _testEncryption() async {
try {
// 1. 生成 RSA 金鑰對
final keyPair = await cipherGuard.generateRsaKeyPair(keySize: 1024);
print('Public Key: ${keyPair.publicKey}');
print('Private Key: ${keyPair.privateKey}');
// 2. 用密碼加密私鑰
const password = 'user_password_123';
final encryptedPrivateKey = await cipherGuard.encryptPrivateKey(
privateKey: keyPair.privateKey,
password: password,
);
print('Encrypted Private Key: $encryptedPrivateKey');
// 3. 解密私鑰
final decryptedPrivateKey = await cipherGuard.decryptPrivateKey(
encryptedPrivateKey: encryptedPrivateKey,
password: password,
);
print('Decrypted Private Key: $decryptedPrivateKey');
// 4. 生成會話金鑰
final sessionKey = await cipherGuard.generateSessionKey(initialRound: 1);
print('Session Key: ${sessionKey.key}');
print('Session Round: ${sessionKey.round}');
// 5. 用 RSA 公鑰加密會話金鑰
final encryptedSessionKey = await cipherGuard.encryptSessionKey(
sessionKey: sessionKey.key,
publicKey: keyPair.publicKey,
);
print('Encrypted Session Key: $encryptedSessionKey');
// 6. 用 RSA 私鑰解密會話金鑰
final decryptedSessionKey = await cipherGuard.decryptSessionKey(
encryptedSessionKey: encryptedSessionKey,
privateKey: keyPair.privateKey,
);
print('Decrypted Session Key: $decryptedSessionKey');
// 7. 加密訊息
const message = 'Hello, this is a secret message!';
final encryptedMessage = await cipherGuard.encryptMessage(
plaintext: message,
sessionKey: sessionKey.key,
round: sessionKey.round,
);
print('Encrypted Message: ${encryptedMessage.data}');
print('Message Round: ${encryptedMessage.round}');
// 8. 解密訊息
final decryptedMessage = await cipherGuard.decryptMessage(
encryptedData: encryptedMessage.data,
sessionKey: sessionKey.key,
round: encryptedMessage.round,
);
print('Decrypted Message: $decryptedMessage');
// 9. 設置 AES_SECRET 用於推送解密
await cipherGuard.setAesSecret(aesSecret: 'your_aes_secret_key_here');
print('All encryption tests passed!');
} catch (e) {
print('Encryption test failed: $e');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('CipherGuard SDK Demo')),
body: const Center(
child: Text('CipherGuard SDK Demo'),
),
),
);
}
}
```
---
## CipherGuardSdkApi 介面
`CipherGuardSdkApi` 是 SDK 的核心抽象類,提供了所有加密相關功能。
### 方法
#### `platformVersion`
```dart
Future<String?> platformVersion();
```
**描述**:獲取當前平台版本。
**使用範例**
```dart
final version = await cipherGuard.platformVersion();
print('Platform Version: $version');
```
---
#### `generateRsaKeyPair`
```dart
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024});
```
**描述**:生成 RSA 金鑰對。
**參數**
| 參數 | 類型 | 預設值 | 說明 |
|------|------|--------|------|
| `keySize` | `int` | 1024 | RSA 金鑰長度(支援 1024、2048 |
**使用範例**
```dart
final keyPair = await cipherGuard.generateRsaKeyPair(keySize: 2048);
print('Public Key: ${keyPair.publicKey}');
print('Private Key: ${keyPair.privateKey}');
```
**返回結果**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `publicKey` | `String` | PEM 格式的公鑰 |
| `privateKey` | `String` | PEM 格式的私鑰 |
---
#### `encryptPrivateKey`
```dart
Future<String> encryptPrivateKey({
required String privateKey,
required String password,
});
```
**描述**:使用密碼對 RSA 私鑰進行加密。採用 AES-CBC 模式,使用 MD5(password) 作為金鑰。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `privateKey` | `String` | 是 | PEM 格式的私鑰 |
| `password` | `String` | 是 | 用於加密的密碼 |
**使用範例**
```dart
final encryptedKey = await cipherGuard.encryptPrivateKey(
privateKey: keyPair.privateKey,
password: 'user_password',
);
```
---
#### `decryptPrivateKey`
```dart
Future<String> decryptPrivateKey({
required String encryptedPrivateKey,
required String password,
});
```
**描述**:使用密碼解密 RSA 私鑰。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `encryptedPrivateKey` | `String` | 是 | Base64 編碼的加密私鑰 |
| `password` | `String` | 是 | 用於解密的密碼 |
**使用範例**
```dart
final privateKey = await cipherGuard.decryptPrivateKey(
encryptedPrivateKey: encryptedKey,
password: 'user_password',
);
```
---
#### `generateSessionKey`
```dart
Future<SessionKey> generateSessionKey({int initialRound = 1});
```
**描述**:生成 AES 會話金鑰32 字節隨機金鑰)。
**參數**
| 參數 | 類型 | 預設值 | 說明 |
|------|------|--------|------|
| `initialRound` | `int` | 1 | 初始 round 值 |
**使用範例**
```dart
final sessionKey = await cipherGuard.generateSessionKey(initialRound: 1);
print('Key: ${sessionKey.key}');
print('Round: ${sessionKey.round}');
```
**返回結果**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `key` | `String` | Base64 編碼的 32 字節會話金鑰 |
| `round` | `int` | 金鑰 round 值 |
---
#### `encryptSessionKey`
```dart
Future<String> encryptSessionKey({
required String sessionKey,
required String publicKey,
});
```
**描述**:使用 RSA 公鑰加密會話金鑰。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `sessionKey` | `String` | 是 | Base64 編碼的會話金鑰 |
| `publicKey` | `String` | 是 | PEM 格式的 RSA 公鑰 |
**使用範例**
```dart
final encryptedSessionKey = await cipherGuard.encryptSessionKey(
sessionKey: sessionKey.key,
publicKey: keyPair.publicKey,
);
```
---
#### `decryptSessionKey`
```dart
Future<String> decryptSessionKey({
required String encryptedSessionKey,
required String privateKey,
});
```
**描述**:使用 RSA 私鑰解密會話金鑰。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `encryptedSessionKey` | `String` | 是 | Base64 編碼的加密會話金鑰 |
| `privateKey` | `String` | 是 | PEM 格式的 RSA 私鑰 |
**使用範例**
```dart
final decryptedKey = await cipherGuard.decryptSessionKey(
encryptedSessionKey: encryptedSessionKey,
privateKey: keyPair.privateKey,
);
```
---
#### `encryptMessage`
```dart
Future<EncryptedMessage> encryptMessage({
required String plaintext,
required String sessionKey,
required int round,
});
```
**描述**:使用 AES-CTR 模式加密訊息。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `plaintext` | `String` | 是 | 要加密的原始訊息 |
| `sessionKey` | `String` | 是 | Base64 編碼的會話金鑰 |
| `round` | `int` | 是 | 金鑰 round 值 |
**使用範例**
```dart
final encryptedMessage = await cipherGuard.encryptMessage(
plaintext: 'Hello, World!',
sessionKey: sessionKey.key,
round: sessionKey.round,
);
print('Encrypted Data: ${encryptedMessage.data}');
```
**返回結果**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `round` | `int` | 金鑰 round 值 |
| `data` | `String` | Base64 編碼的加密內容IV + 加密資料) |
---
#### `decryptMessage`
```dart
Future<String> decryptMessage({
required String encryptedData,
required String sessionKey,
required int round,
});
```
**描述**:使用 AES-CTR 模式解密訊息。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `encryptedData` | `String` | 是 | Base64 編碼的加密資料 |
| `sessionKey` | `String` | 是 | Base64 編碼的會話金鑰 |
| `round` | `int` | 是 | 金鑰 round 值 |
**使用範例**
```dart
final decrypted = await cipherGuard.decryptMessage(
encryptedData: encryptedMessage.data,
sessionKey: sessionKey.key,
round: encryptedMessage.round,
);
print('Decrypted: $decrypted');
```
---
#### `syncEncryptionKey`
```dart
Future<void> syncEncryptionKey({
required String chatId,
required int activeRound,
required int round,
required String activeKey,
required bool isSingle,
});
```
**描述**同步單個聊天室的加密金鑰到原生平台iOS App Group
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `chatId` | `String` | 是 | 聊天室 ID |
| `activeRound` | `int` | 是 | 當前活躍的 round 值 |
| `round` | `int` | 是 | round 值 |
| `activeKey` | `String` | 是 | 活躍的加密金鑰 |
| `isSingle` | `bool` | 是 | 是否為單聊 |
**使用範例**
```dart
await cipherGuard.syncEncryptionKey(
chatId: 'chat_123',
activeRound: 1,
round: 1,
activeKey: sessionKey.key,
isSingle: false,
);
```
---
#### `syncAllEncryptionKeys`
```dart
Future<void> syncAllEncryptionKeys({
required Map<String, Map<String, dynamic>> chatMap,
});
```
**描述**:批量同步所有加密聊天室的金鑰到原生平台。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `chatMap` | `Map<String, Map<String, dynamic>>` | 是 | 聊天室金鑰映射 |
**使用範例**
```dart
final chatMap = {
'chat_1': {
'activeKey': sessionKey.key,
'activeRound': 1,
'round': 1,
'isSingle': false,
},
'chat_2': {
'activeKey': anotherKey.key,
'activeRound': 1,
'round': 1,
'isSingle': true,
},
};
await cipherGuard.syncAllEncryptionKeys(chatMap: chatMap);
```
---
#### `setAesSecret`
```dart
Future<void> setAesSecret({required String aesSecret});
```
**描述**:設置 AES_SECRET用於推送通知解密
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `aesSecret` | `String` | 是 | AES 密鑰(十六進制字串) |
**使用範例**
```dart
await cipherGuard.setAesSecret(aesSecret: '0123456789abcdef');
```
---
#### `decryptPushNotification`
```dart
Future<String?> decryptPushNotification({required String encryptedData});
```
**描述**:解密 APNS 推送通知內容。使用 AES-GCM 模式。
**參數**
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `encryptedData` | `String` | 是 | Base64 編碼的加密資料 |
**使用範例**
```dart
final decrypted = await cipherGuard.decryptPushNotification(
encryptedData: pushNotificationData,
);
```
---
## 實體類別
### RsaKeyPair
```dart
class RsaKeyPair {
final String publicKey;
final String privateKey;
const RsaKeyPair({
required this.publicKey,
required this.privateKey,
});
/// 檢查金鑰對是否有效
bool get isValid => publicKey.isNotEmpty && privateKey.isNotEmpty;
}
```
**屬性說明**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `publicKey` | `String` | PEM 格式的 RSA 公鑰 |
| `privateKey` | `String` | PEM 格式的 RSA 私鑰 |
| `isValid` | `bool` | 金鑰對是否有效 |
---
### SessionKey
```dart
class SessionKey {
final String key; // Base64 編碼的 32 字節金鑰
final int round; // 金鑰輪換 round 值
const SessionKey({
required this.key,
required this.round,
});
}
```
**屬性說明**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `key` | `String` | Base64 編碼的 32 字節 AES 會話金鑰 |
| `round` | `int` | 金鑰輪換 round 值 |
---
### EncryptedMessage
```dart
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,
};
}
}
```
**屬性說明**
| 屬性 | 類型 | 說明 |
|------|------|------|
| `round` | `int` | 金鑰 round 值 |
| `data` | `String` | Base64 編碼的加密內容 |
---
## 加密流程
### 訊息加密流程
```
1. 用戶 A 生成 RSA 金鑰對
├── publicKey_A (公開)
└── privateKey_A (保密,用密碼加密存儲)
2. 用戶 A 生成會話金鑰
└── sessionKey (每個聊天室獨立)
3. 用戶 A 用 RSA publicKey_B 加密會話金鑰
└── encryptedSessionKey (傳送給用戶 B)
4. 用戶 A 加密訊息
└── encryptMessage(plaintext, sessionKey, round)
└── EncryptedMessage(round, data)
```
### 訊息解密流程
```
1. 用戶 B 收到加密的會話金鑰
└── decryptSessionKey(encryptedSessionKey, privateKey_B)
2. 用戶 B 解密訊息
└── decryptMessage(encryptedData, sessionKey, round)
└── plaintext
```
---
## iOS 設定
### App Groups 設定
用於加密金鑰同步,需要設定 App Groups
在 Xcode 中選擇你的主 Target
- **Signing & Capabilities** → **+ Capability** → **App Groups**
- 建立名為 `group.com.example.cipherguard` 的 App Group
---
## Android 設定
無需額外設定。SDK 使用 Flutter 層的加密邏輯。
---
## 完整範例
以下是一個完整的端對端加密聊天整合範例:
```dart
import 'package:flutter/material.dart';
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
void main() {
runApp(const ChatApp());
}
class ChatApp extends StatefulWidget {
const ChatApp({super.key});
@override
State<ChatApp> createState() => _ChatAppState();
}
class _ChatAppState extends State<ChatApp> {
final CipherGuardSdkApi cipherGuard = CipherGuardSdkApi();
// 用戶的金鑰對
RsaKeyPair? _keyPair;
// 聊天室的會話金鑰
SessionKey? _sessionKey;
@override
void initState() {
super.initState();
_initializeEncryption();
}
Future<void> _initializeEncryption() async {
// 生成 RSA 金鑰對
_keyPair = await cipherGuard.generateRsaKeyPair(keySize: 2048);
// 生成會話金鑰
_sessionKey = await cipherGuard.generateSessionKey(initialRound: 1);
setState(() {});
}
Future<void> _sendMessage(String message) async {
if (_sessionKey == null) return;
// 加密訊息
final encrypted = await cipherGuard.encryptMessage(
plaintext: message,
sessionKey: _sessionKey!.key,
round: _sessionKey!.round,
);
// 發送 encrypted.round 和 encrypted.data 給對方
print('Sending encrypted message: ${encrypted.data}');
}
Future<String> _receiveMessage(String encryptedData, int round) async {
if (_sessionKey == null) return '';
// 解密訊息
final decrypted = await cipherGuard.decryptMessage(
encryptedData: encryptedData,
sessionKey: _sessionKey!.key,
round: round,
);
return decrypted;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('E2E Chat Demo')),
body: Center(
child: _keyPair == null
? const CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('RSA Key Generated: ${_keyPair!.publicKey.substring(0, 50)}...'),
const SizedBox(height: 20),
Text('Session Key: ${_sessionKey!.key.substring(0, 20)}...'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => _sendMessage('Hello, World!'),
child: const Text('Send Encrypted Message'),
),
],
),
),
),
);
}
}
```
---
## 安全性說明
### 金鑰管理
1. **RSA 私鑰保護**:私鑰必須使用強密碼加密後存儲
2. **會話金鑰**:每個聊天室應使用獨立的會話金鑰
3. **金鑰輪換**:透過 round 值實現金鑰輪換
### 加密算法
| 功能 | 算法 | 模式 | 說明 |
|------|------|------|------|
| RSA 金鑰生成 | RSA | - | 支援 1024/2048 位元 |
| 私鑰加密 | AES | CBC | 使用 MD5(password) 作為金鑰 |
| 會話金鑰加密 | RSA | PKCS1 | 標準 RSA 加密 |
| 訊息加密 | AES | CTR | 高效流式加密 |
| 推送解密 | AES | GCM | 帶認證的加密 |
---
## 版本歷史
| 版本 | 日期 | 說明 |
|-------|-----------|--------|
| 0.0.1 | 01/30/2026 | 相關金鑰生成 |
---
## 許可協議
MIT License

View File

@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@@ -0,0 +1,66 @@
group = "com.example.cipher_guard_sdk"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "2.2.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "com.example.cipher_guard_sdk"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 24
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

View File

@@ -0,0 +1 @@
rootProject.name = 'cipher_guard_sdk'

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cipher_guard_sdk">
</manifest>

View File

@@ -0,0 +1,38 @@
package com.example.cipher_guard_sdk
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** CipherGuardSdkPlugin */
class CipherGuardSdkPlugin :
FlutterPlugin,
MethodCallHandler {
// The MethodChannel that will the communication between Flutter and native Android
//
// This local reference serves to register the plugin with the Flutter Engine and unregister it
// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cipher_guard_sdk")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(
call: MethodCall,
result: Result
) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}

View File

@@ -0,0 +1,27 @@
package com.example.cipher_guard_sdk
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.mockito.Mockito
import kotlin.test.Test
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class CipherGuardSdkPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = CipherGuardSdkPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}

View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,16 @@
# cipher_guard_sdk_example
Demonstrates how to use the cipher_guard_sdk plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
avoid_print: false
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.cipher_guard_sdk_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.cipher_guard_sdk_example"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="cipher_guard_sdk_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package com.example.cipher_guard_sdk_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

View File

@@ -0,0 +1,25 @@
// This is a basic Flutter integration test.
//
// Since integration tests run in a full Flutter application, they can interact
// with the host side of a plugin implementation, unlike Dart unit tests.
//
// For more information about Flutter integration tests, please see
// https://flutter.dev/to/integration-testing
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final CipherGuardSdkApi plugin = CipherGuardSdkApi();
final String? version = await plugin.platformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
});
}

View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@@ -0,0 +1,619 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 26222CCML7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 26222CCML7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 26222CCML7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.cipherGuardSdkExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Cipher Guard Sdk</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>cipher_guard_sdk_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,27 @@
import Flutter
import UIKit
import XCTest
@testable import cipher_guard_sdk
// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
//
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
class RunnerTests: XCTestCase {
func testGetPlatformVersion() {
let plugin = CipherGuardSdkPlugin()
let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
let resultExpectation = expectation(description: "result block must be called.")
plugin.handle(call) { result in
XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
resultExpectation.fulfill()
}
waitForExpectations(timeout: 1)
}
}

View File

@@ -0,0 +1,377 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final CipherGuardSdkApi _sdk = CipherGuardSdkApi();
// 加密相關狀態
RsaKeyPair? _keyPair;
String? _encryptedPrivateKey;
SessionKey? _sessionKey;
EncryptedMessage? _encryptedMessage;
String _decryptedMessage = '';
String _statusMessage = '';
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = (await _sdk.platformVersion()) ?? 'Unknown platform version';
} catch (e) {
platformVersion = 'Failed to get platform version: $e';
}
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
// ==================== RSA 金鑰管理 ====================
Future<void> _generateRsaKeyPair() async
{
setState(() => _statusMessage = 'Generating RSA key pair...');
try {
final keyPair = await _sdk.generateRsaKeyPair(keySize: 1024);
setState(() {
_keyPair = keyPair;
_statusMessage = 'RSA key pair generated successfully!\nPublic Key: ${keyPair.publicKey.substring(0, 50)}...';
});
} catch (e) {
print("_generateRsaKeyPair error: $e");
setState(() => _statusMessage = 'Error: $e');
}
}
Future<void> _encryptPrivateKey() async
{
if (_keyPair == null) {
setState(() => _statusMessage = 'Please generate RSA key pair first!');
return;
}
const password = 'user_password_123';
setState(() => _statusMessage = 'Encrypting private key with password...');
try {
final encrypted = await _sdk.encryptPrivateKey(
privateKey: _keyPair!.privateKey,
password: password,
);
setState(() {
_encryptedPrivateKey = encrypted;
_statusMessage = 'Private key encrypted successfully!';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
Future<void> _decryptPrivateKey() async
{
if (_encryptedPrivateKey == null) {
setState(() => _statusMessage = 'No encrypted private key to decrypt!');
return;
}
const password = 'user_password_123';
setState(() => _statusMessage = 'Decrypting private key...');
try {
final decrypted = await _sdk.decryptPrivateKey(
encryptedPrivateKey: _encryptedPrivateKey!,
password: password,
);
setState(() {
_statusMessage = 'Private key decrypted successfully!\n$decrypted';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
// ==================== 會話金鑰管理 ====================
Future<void> _generateSessionKey() async
{
setState(() => _statusMessage = 'Generating session key...');
try {
final sessionKey = await _sdk.generateSessionKey(initialRound: 1);
setState(() {
_sessionKey = sessionKey;
_statusMessage = 'Session key generated!\nKey: ${sessionKey.key}\nRound: ${sessionKey.round}';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
Future<void> _encryptSessionKey() async
{
if (_sessionKey == null || _keyPair == null) {
setState(() => _statusMessage = 'Please generate session key and RSA key pair first!');
return;
}
setState(() => _statusMessage = 'Encrypting session key with RSA public key...');
try {
final encrypted = await _sdk.encryptSessionKey(
sessionKey: _sessionKey!.key,
publicKey: _keyPair!.publicKey,
);
setState(() {
_statusMessage = 'Session key encrypted successfully!\n$encrypted';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
Future<void> _decryptSessionKey() async
{
if (_keyPair == null) {
setState(() => _statusMessage = 'Please generate RSA key pair first!');
return;
}
// 這裡應該使用之前加密的會話金鑰
if (_sessionKey == null) {
setState(() => _statusMessage = 'Please generate session key first!');
return;
}
setState(() => _statusMessage = 'Decrypting session key...');
try {
// 先加密再解密測試
final encrypted = await _sdk.encryptSessionKey(
sessionKey: _sessionKey!.key,
publicKey: _keyPair!.publicKey,
);
final decrypted = await _sdk.decryptSessionKey(
encryptedSessionKey: encrypted,
privateKey: _keyPair!.privateKey,
);
setState(() {
_statusMessage = 'Session key decrypted successfully!\nOriginal: ${_sessionKey!.key}\nDecrypted: $decrypted';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
// ==================== 訊息加解密 ====================
Future<void> _encryptMessage() async
{
if (_sessionKey == null) {
setState(() => _statusMessage = 'Please generate session key first!');
return;
}
const plaintext = 'Hello, this is a secret message!';
setState(() => _statusMessage = 'Encrypting message...');
try {
final encrypted = await _sdk.encryptMessage(
plaintext: plaintext,
sessionKey: _sessionKey!.key,
round: _sessionKey!.round,
);
setState(() {
_encryptedMessage = encrypted;
_statusMessage = 'Message encrypted!\nRound: ${encrypted.round}\nData: ${encrypted.data}';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
Future<void> _decryptMessage() async
{
if (_encryptedMessage == null || _sessionKey == null) {
setState(() => _statusMessage = 'No encrypted message to decrypt!');
return;
}
setState(() => _statusMessage = 'Decrypting message...');
try {
final decrypted = await _sdk.decryptMessage(
encryptedData: _encryptedMessage!.data,
sessionKey: _sessionKey!.key,
round: _encryptedMessage!.round,
);
setState(() {
_decryptedMessage = decrypted;
_statusMessage = 'Message decrypted: $decrypted';
});
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
// ==================== 原生平台同步 ====================
Future<void> _syncEncryptionKeys() async
{
if (_sessionKey == null) {
setState(() => _statusMessage = 'Please generate session key first!');
return;
}
setState(() => _statusMessage = 'Syncing encryption keys to native...');
try {
await _sdk.syncEncryptionKey(
chatId: 'test_chat_123',
activeRound: _sessionKey!.round,
round: _sessionKey!.round,
activeKey: _sessionKey!.key,
isSingle: false,
);
setState(() => _statusMessage = 'Encryption keys synced to native platform!');
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
// ==================== 推送通知解密 ====================
Future<void> _setupAesSecret() async
{
// 設置 release.json 中的 AES_SECRET
const aesSecret = '468171c825c02408cc99935447c785a5';
setState(() => _statusMessage = 'Setting AES_SECRET...');
try {
await _sdk.setAesSecret(aesSecret: aesSecret);
setState(() => _statusMessage = 'AES_SECRET set successfully!');
} catch (e) {
setState(() => _statusMessage = 'Error: $e');
}
}
@override
Widget build(BuildContext context)
{
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('CipherGuard SDK Example'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Running on: $_platformVersion\n', style: Theme.of(context).textTheme.titleMedium),
const Text('=== RSA 金鑰管理 ===', style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child:ElevatedButton(onPressed: _generateRsaKeyPair, child: const Text('生成 RSA 金鑰對',)
),
),
const SizedBox(width: 8),
Expanded(child: ElevatedButton(onPressed: _keyPair != null ? _encryptPrivateKey : null, child: const Text('加密私鑰',style: TextStyle(fontSize: 10),))),
const SizedBox(width: 8),
Expanded(child: ElevatedButton(onPressed: _encryptedPrivateKey != null ? _decryptPrivateKey : null, child: const Text('解密私鑰',style: TextStyle(fontSize: 10),)),),
],
),
const SizedBox(height: 8),
const Text('=== 會話金鑰管理 ===', style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child: ElevatedButton(onPressed: _generateSessionKey, child: const Text('生成會話金鑰',style: TextStyle(fontSize: 10),)),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: (_sessionKey != null && _keyPair != null) ? _encryptSessionKey : null,
child: const Text('加密會話金鑰',style: TextStyle(fontSize: 10),)
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: (_keyPair != null && _sessionKey != null) ? _decryptSessionKey : null,
child: const Text('解密會話金鑰',style: TextStyle(fontSize: 10),)
),
),
],
),
const SizedBox(height: 8),
const Text('=== 訊息加解密 ===', style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
ElevatedButton(
onPressed: _sessionKey != null ? _encryptMessage : null,
child: const Text('加密訊息')
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _encryptedMessage != null ? _decryptMessage : null,
child: const Text('解密訊息')
),
],
),
if (_decryptedMessage.isNotEmpty)
Text('解密結果: $_decryptedMessage', style: const TextStyle(color: Colors.green)),
const SizedBox(height: 8),
const Text('=== 原生平台同步 ===', style: TextStyle(fontWeight: FontWeight.bold)),
ElevatedButton(
onPressed: _sessionKey != null ? _syncEncryptionKeys : null,
child: const Text('同步加密金鑰到原生')
),
const SizedBox(height: 8),
const Text('=== 推送通知解密 ===', style: TextStyle(fontWeight: FontWeight.bold)),
ElevatedButton(onPressed: _setupAesSecret, child: const Text('設置 AES_SECRET')),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
color: Colors.grey[200],
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('狀態:', style: TextStyle(fontWeight: FontWeight.bold)),
Text(_statusMessage),
],
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,85 @@
name: cipher_guard_sdk_example
description: "Demonstrates how to use the cipher_guard_sdk plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ^3.11.0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
cipher_guard_sdk:
# When depending on this package from a real application you should use:
# cipher_guard_sdk: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package

View File

@@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:cipher_guard_sdk_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

View File

@@ -0,0 +1,19 @@
import Flutter
import UIKit
public class CipherGuardSdkPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "cipher_guard_sdk", binaryMessenger: registrar.messenger())
let instance = CipherGuardSdkPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint cipher_guard_sdk.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'cipher_guard_sdk'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '13.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
# If your plugin requires a privacy manifest, for example if it uses any
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
# plugin's privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'cipher_guard_sdk_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

View File

@@ -0,0 +1,15 @@
/// CipherGuard SDK - 端对端加密 Flutter 插件
///
/// 提供完整的加密/解密功能:
/// - RSA 密钥对管理(用户级别)
/// - AES 会话密钥管理(聊天室级别)
/// - 消息加密/解密
/// - 原生平台同步iOS App Group
/// - 推送通知解密
library;
export 'src/presentation/facade/cipher_guard_sdk_api.dart';
export 'src/domain/entities/rsa_key_pair.dart';
export 'src/domain/entities/session_key.dart';
export 'src/domain/entities/encrypted_message.dart';
export 'src/domain/entities/chat_encryption_key.dart';

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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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,
});
}

View File

@@ -0,0 +1,65 @@
/// SDK API
/// 端對端加密的統一入口
library;
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/presentation/wiring/cipher_guard_sdk_wiring.dart';
abstract class CipherGuardSdkApi
{
factory CipherGuardSdkApi() => CipherGuardSdkWiring.build();
// ==================== 平台版本 ====================
/// 獲取平台版本
Future<String?> platformVersion();
// ==================== RSA 金鑰管理 ====================
/// 生成 RSA 金鑰對
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024});
/// 用密碼加密私鑰
Future<String> encryptPrivateKey({required String privateKey, required String password,});
/// 解密私鑰
Future<String> decryptPrivateKey({required String encryptedPrivateKey, required String password,});
// ==================== 會話金鑰管理 ====================
/// 生成 AES 會話金鑰
Future<SessionKey> generateSessionKey({int initialRound = 1});
/// 用 RSA 公鑰加密會話金鑰
Future<String> encryptSessionKey({required String sessionKey, required String publicKey,});
/// 用 RSA 私鑰解密會話金鑰
Future<String> decryptSessionKey({required String encryptedSessionKey, required String privateKey,});
// ==================== 訊息加解密 ====================
/// 加密訊息
Future<EncryptedMessage> encryptMessage({required String plaintext, required String sessionKey, required int round,});
/// 解密訊息
Future<String> decryptMessage({required String encryptedData, required String sessionKey, required int round,});
// ==================== 原生平台同步 ====================
/// 同步加密金鑰到原生平台 (iOS App Group)
Future<void> syncEncryptionKey({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 推送通知內容
Future<String?> decryptPushNotification({required String encryptedData,});
}

View File

@@ -0,0 +1,131 @@
import 'package:cipher_guard_sdk/src/presentation/facade/cipher_guard_sdk_api.dart';
import 'package:cipher_guard_sdk/src/presentation/wiring/cipher_guard_sdk_core.dart';
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';
/// SDK API 实现
class CipherGuardSdkApiImpl implements CipherGuardSdkApi {
final CipherGuardSdkCore _core;
CipherGuardSdkApiImpl({required CipherGuardSdkCore core}) : _core = core;
@override
Future<String?> platformVersion() => _core.platform.getPlatformVersion();
@override
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024}) {
return _core.encryptionRepo.generateRsaKeyPair(keySize: keySize);
}
@override
Future<String> encryptPrivateKey({
required String privateKey,
required String password,
}) {
return _core.encryptionRepo.encryptPrivateKey(
privateKey: privateKey,
password: password,
);
}
@override
Future<String> decryptPrivateKey({
required String encryptedPrivateKey,
required String password,
}) {
return _core.encryptionRepo.decryptPrivateKey(
encryptedPrivateKey: encryptedPrivateKey,
password: password,
);
}
@override
Future<SessionKey> generateSessionKey({int initialRound = 1}) {
return _core.encryptionRepo.generateSessionKey(initialRound: initialRound);
}
@override
Future<String> encryptSessionKey({
required String sessionKey,
required String publicKey,
}) {
return _core.encryptionRepo.encryptSessionKey(
sessionKey: sessionKey,
publicKey: publicKey,
);
}
@override
Future<String> decryptSessionKey({
required String encryptedSessionKey,
required String privateKey,
}) {
return _core.encryptionRepo.decryptSessionKey(
encryptedSessionKey: encryptedSessionKey,
privateKey: privateKey,
);
}
@override
Future<EncryptedMessage> encryptMessage({
required String plaintext,
required String sessionKey,
required int round,
}) {
return _core.encryptionRepo.encryptMessage(
plaintext: plaintext,
sessionKey: sessionKey,
round: round,
);
}
@override
Future<String> decryptMessage({
required String encryptedData,
required String sessionKey,
required int round,
}) {
return _core.encryptionRepo.decryptMessage(
encryptedData: encryptedData,
sessionKey: sessionKey,
round: round,
);
}
@override
Future<void> syncEncryptionKey({
required String chatId,
required int activeRound,
required int round,
required String activeKey,
required bool isSingle,
}) {
return _core.encryptionRepo.syncEncryptionKeysToNative(
chatId: chatId,
activeRound: activeRound,
round: round,
activeKey: activeKey,
isSingle: isSingle,
);
}
@override
Future<void> syncAllEncryptionKeys({
required Map<String, Map<String, dynamic>> chatMap,
}) {
return _core.encryptionRepo.syncAllEncryptionKeys(chatMap: chatMap);
}
@override
Future<void> setAesSecret({required String aesSecret}) {
return _core.encryptionRepo.setAesSecret(aesSecret: aesSecret);
}
@override
Future<String?> decryptPushNotification({
required String encryptedData,
}) {
return _core.encryptionRepo.decryptPushNotification(encryptedData: encryptedData);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:cipher_guard_sdk/src/domain/repositories/encryption_repository.dart';
/// SDK Core
/// 持有所有核心依賴
class CipherGuardSdkCore {
final EncryptionRepository encryptionRepo;
final CipherGuardPlatform platform;
CipherGuardSdkCore({
required this.encryptionRepo,
required this.platform,
});
}
/// 平台介面 (用於原生通訊)
abstract class CipherGuardPlatform {
Future<String?> getPlatformVersion();
}

Some files were not shown because too many files have changed in this diff Show More