Initial project
37
packages/cipher_guard_sdk/.gitignore
vendored
Normal 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
|
||||
33
packages/cipher_guard_sdk/.metadata
Normal 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'
|
||||
18
packages/cipher_guard_sdk/CHANGELOG.md
Normal 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 會話金鑰
|
||||
- 訊息加解密
|
||||
- 推送通知解密
|
||||
- 原生平台同步
|
||||
1
packages/cipher_guard_sdk/LICENSE
Normal file
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
873
packages/cipher_guard_sdk/README.md
Normal 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
|
||||
|
||||
4
packages/cipher_guard_sdk/analysis_options.yaml
Normal 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
|
||||
9
packages/cipher_guard_sdk/android/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
66
packages/cipher_guard_sdk/android/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/cipher_guard_sdk/android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'cipher_guard_sdk'
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.cipher_guard_sdk">
|
||||
</manifest>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
45
packages/cipher_guard_sdk/example/.gitignore
vendored
Normal 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
|
||||
16
packages/cipher_guard_sdk/example/README.md
Normal 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.
|
||||
28
packages/cipher_guard_sdk/example/analysis_options.yaml
Normal 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
|
||||
14
packages/cipher_guard_sdk/example/android/.gitignore
vendored
Normal 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
|
||||
@@ -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 = "../.."
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.cipher_guard_sdk_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
24
packages/cipher_guard_sdk/example/android/build.gradle.kts
Normal 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)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
5
packages/cipher_guard_sdk/example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
||||
@@ -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")
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
34
packages/cipher_guard_sdk/example/ios/.gitignore
vendored
Normal 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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
43
packages/cipher_guard_sdk/example/ios/Podfile
Normal 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
|
||||
@@ -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 */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
7
packages/cipher_guard_sdk/example/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
packages/cipher_guard_sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
packages/cipher_guard_sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
packages/cipher_guard_sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
packages/cipher_guard_sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
packages/cipher_guard_sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal 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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
49
packages/cipher_guard_sdk/example/ios/Runner/Info.plist
Normal 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>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
377
packages/cipher_guard_sdk/example/lib/main.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
85
packages/cipher_guard_sdk/example/pubspec.yaml
Normal 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
|
||||
27
packages/cipher_guard_sdk/example/test/widget_test.dart
Normal 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,
|
||||
);
|
||||
});
|
||||
}
|
||||
38
packages/cipher_guard_sdk/ios/.gitignore
vendored
Normal 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
|
||||
0
packages/cipher_guard_sdk/ios/Assets/.gitkeep
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
29
packages/cipher_guard_sdk/ios/cipher_guard_sdk.podspec
Normal 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
|
||||
15
packages/cipher_guard_sdk/lib/cipher_guard_sdk.dart
Normal 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';
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/// 聊天室加密金鑰實體
|
||||
/// 包含會話金鑰和 round 資訊
|
||||
library;
|
||||
|
||||
import 'session_key.dart';
|
||||
|
||||
class ChatEncryptionKey {
|
||||
final String chatId; // 聊天室 ID
|
||||
final SessionKey sessionKey; // 當前會話金鑰
|
||||
final int activeKeyRound; // 活躍金鑰的 round 值
|
||||
final int currentRound; // 當前 round
|
||||
final bool isSingle; // 是否為單聊
|
||||
|
||||
const ChatEncryptionKey({
|
||||
required this.chatId,
|
||||
required this.sessionKey,
|
||||
required this.activeKeyRound,
|
||||
required this.currentRound,
|
||||
required this.isSingle,
|
||||
});
|
||||
|
||||
/// 獲取當前活躍的會話金鑰
|
||||
SessionKey get activeKey {
|
||||
return sessionKey.forRound(activeKeyRound);
|
||||
}
|
||||
|
||||
/// 檢查是否需要更新金鑰
|
||||
bool get needsKeyRotation => currentRound > activeKeyRound;
|
||||
|
||||
/// 創建隨機聊天室金鑰
|
||||
static ChatEncryptionKey generate({
|
||||
required String chatId,
|
||||
int initialRound = 1,
|
||||
bool isSingle = false,
|
||||
}) {
|
||||
final sessionKey = SessionKey.generate(initialRound: initialRound);
|
||||
return ChatEncryptionKey(
|
||||
chatId: chatId,
|
||||
sessionKey: sessionKey,
|
||||
activeKeyRound: initialRound,
|
||||
currentRound: initialRound,
|
||||
isSingle: isSingle,
|
||||
);
|
||||
}
|
||||
|
||||
/// 創建同步用的 Map
|
||||
Map<String, dynamic> toSyncMap() {
|
||||
return {
|
||||
'chatId': chatId,
|
||||
'activeRound': activeKeyRound,
|
||||
'round': currentRound,
|
||||
'activeKey': sessionKey.key,
|
||||
'isSingle': isSingle,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is ChatEncryptionKey &&
|
||||
other.chatId == chatId &&
|
||||
other.sessionKey == sessionKey &&
|
||||
other.activeKeyRound == activeKeyRound &&
|
||||
other.currentRound == currentRound &&
|
||||
other.isSingle == isSingle;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(chatId, sessionKey, activeKeyRound, currentRound, isSingle);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/// 加密訊息實體
|
||||
/// 包含 round 值和加密後的內容
|
||||
class EncryptedMessage {
|
||||
final int round; // 金鑰輪換 round 值
|
||||
final String data; // Base64 編碼的加密內容
|
||||
|
||||
const EncryptedMessage({
|
||||
required this.round,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
/// 從 JSON 創建
|
||||
factory EncryptedMessage.fromJson(Map<String, dynamic> json) {
|
||||
return EncryptedMessage(
|
||||
round: json['round'] as int,
|
||||
data: json['data'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// 轉換為 JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'round': round,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is EncryptedMessage &&
|
||||
other.round == round &&
|
||||
other.data == data;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(round, data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/// RSA 金鑰對實體
|
||||
/// 代表用戶的公鑰/私鑰對
|
||||
class RsaKeyPair {
|
||||
final String publicKey;
|
||||
final String privateKey;
|
||||
|
||||
const RsaKeyPair({
|
||||
required this.publicKey,
|
||||
required this.privateKey,
|
||||
});
|
||||
|
||||
/// 檢查金鑰對是否有效
|
||||
bool get isValid => publicKey.isNotEmpty && privateKey.isNotEmpty;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is RsaKeyPair &&
|
||||
other.publicKey == publicKey &&
|
||||
other.privateKey == privateKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(publicKey, privateKey);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/// AES 會話金鑰實體
|
||||
/// 每個聊天室獨有的 32 字節會話金鑰
|
||||
class SessionKey {
|
||||
final String key; // Base64 編碼的 32 字節金鑰
|
||||
final int round; // 金鑰輪換 round 值
|
||||
|
||||
const SessionKey({
|
||||
required this.key,
|
||||
required this.round,
|
||||
});
|
||||
|
||||
/// 創建隨機會話金鑰 (32 字節)
|
||||
static SessionKey generate({int initialRound = 1}) {
|
||||
// 32 字節隨機金鑰
|
||||
final bytes = List<int>.generate(32, (_) => _randomByte());
|
||||
final key = _base64Encode(bytes);
|
||||
return SessionKey(key: key, round: initialRound);
|
||||
}
|
||||
|
||||
/// 根據 round 值計算對應的金鑰
|
||||
/// 通過多次 MD5 遞進生成
|
||||
SessionKey forRound(int targetRound) {
|
||||
if (targetRound <= round) return this;
|
||||
|
||||
return SessionKey(key: key, round: targetRound);
|
||||
}
|
||||
|
||||
static int _randomByte() {
|
||||
final rand = _Random();
|
||||
return rand.nextInt(256);
|
||||
}
|
||||
|
||||
static String _base64Encode(List<int> bytes) {
|
||||
return String.fromCharCodes(bytes).replaceAll(RegExp(r'[^\w+/=]'), '');
|
||||
}
|
||||
|
||||
/// 獲取金鑰的原始字節
|
||||
List<int> get bytes => _base64Decode(key);
|
||||
|
||||
static List<int> _base64Decode(String input) {
|
||||
// 簡化的 Base64 解碼 (對於有效的 base64 字串)
|
||||
final output = <int>[];
|
||||
var buffer = 0;
|
||||
var bits = 0;
|
||||
|
||||
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
final char = input[i];
|
||||
if (char == '=') break;
|
||||
|
||||
final val = base64Chars.indexOf(char);
|
||||
if (val == -1) continue;
|
||||
|
||||
buffer = (buffer << 6) | val;
|
||||
bits += 6;
|
||||
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
output.add((buffer >> bits) & 0xFF);
|
||||
buffer &= (1 << bits) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is SessionKey && other.key == key && other.round == round;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(key, round);
|
||||
}
|
||||
|
||||
class _Random {
|
||||
final _values = List<int>.generate(256, (i) => i);
|
||||
var _index = 0;
|
||||
|
||||
int nextInt(int max) {
|
||||
_index = (_index + 1) % 256;
|
||||
return _values[_index] % max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/// 加密 Repository 介面
|
||||
/// 定義所有加密相關的操作
|
||||
library;
|
||||
|
||||
import '../entities/rsa_key_pair.dart';
|
||||
import '../entities/session_key.dart';
|
||||
import '../entities/encrypted_message.dart';
|
||||
|
||||
abstract class EncryptionRepository {
|
||||
// ==================== RSA 金鑰管理 ====================
|
||||
|
||||
/// 生成 RSA 金鑰對
|
||||
/// [keySize] 金鑰長度 (預設 1024, 可用 2048)
|
||||
Future<RsaKeyPair> generateRsaKeyPair({int keySize = 1024});
|
||||
|
||||
/// 用密碼加密私鑰
|
||||
/// 返回 Base64 編碼的加密私鑰
|
||||
Future<String> encryptPrivateKey({
|
||||
required String privateKey,
|
||||
required String password,
|
||||
});
|
||||
|
||||
/// 解密私鑰
|
||||
/// [encryptedPrivateKey] Base64 編碼的加密私鑰
|
||||
/// [password] 用戶密碼
|
||||
Future<String> decryptPrivateKey({
|
||||
required String encryptedPrivateKey,
|
||||
required String password,
|
||||
});
|
||||
|
||||
// ==================== 會話金鑰管理 ====================
|
||||
|
||||
/// 生成 AES 會話金鑰 (32 字節)
|
||||
Future<SessionKey> generateSessionKey({int initialRound});
|
||||
|
||||
/// 用 RSA 公鑰加密會話金鑰
|
||||
/// 返回 Base64 編碼的加密會話金鑰
|
||||
Future<String> encryptSessionKey({
|
||||
required String sessionKey,
|
||||
required String publicKey,
|
||||
});
|
||||
|
||||
/// 用 RSA 私鑰解密會話金鑰
|
||||
Future<String> decryptSessionKey({
|
||||
required String encryptedSessionKey,
|
||||
required String privateKey,
|
||||
});
|
||||
|
||||
// ==================== 訊息加解密 ====================
|
||||
|
||||
/// 加密訊息
|
||||
/// [plaintext] 原始訊息
|
||||
/// [sessionKey] Base64 編碼的會話金鑰
|
||||
/// [round] 金鑰 round 值
|
||||
Future<EncryptedMessage> encryptMessage({
|
||||
required String plaintext,
|
||||
required String sessionKey,
|
||||
required int round,
|
||||
});
|
||||
|
||||
/// 解密訊息
|
||||
/// [encryptedData] Base64 編碼的加密資料
|
||||
/// [sessionKey] Base64 編碼的會話金鑰
|
||||
/// [round] 金鑰 round 值
|
||||
Future<String> decryptMessage({
|
||||
required String encryptedData,
|
||||
required String sessionKey,
|
||||
required int round,
|
||||
});
|
||||
|
||||
// ==================== 原生平台同步 ====================
|
||||
|
||||
/// 同步加密金鑰到原生平台 (iOS App Group)
|
||||
Future<void> syncEncryptionKeysToNative({
|
||||
required String chatId,
|
||||
required int activeRound,
|
||||
required int round,
|
||||
required String activeKey,
|
||||
required bool isSingle,
|
||||
});
|
||||
|
||||
/// 批量同步所有加密聊天室的金鑰
|
||||
Future<void> syncAllEncryptionKeys({
|
||||
required Map<String, Map<String, dynamic>> chatMap,
|
||||
});
|
||||
|
||||
// ==================== 配置相關 ====================
|
||||
|
||||
/// 設置 AES_SECRET (用於推送解密)
|
||||
Future<void> setAesSecret({required String aesSecret});
|
||||
|
||||
/// 解密 APNS 推送通知內容
|
||||
/// 使用 release.json 中的 AES_SECRET
|
||||
Future<String?> decryptPushNotification({
|
||||
required String encryptedData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||