neelakandanz/Flutter-Neo-Shield
GitHub: neelakandanz/Flutter-Neo-Shield
一款专为 Flutter 应用设计的全方位安全防护库,提供日志自动脱敏、内存擦除、防截屏及运行时环境检测,防止敏感数据在开发、运行及静态分析阶段泄露。
Stars: 0 | Forks: 1
# flutter_neo_shield
[](https://pub.dev/packages/flutter_neo_shield)
[](https://opensource.org/licenses/MIT)
**保护您的 Flutter 应用中的敏感数据 —— 包括日志、剪贴板、内存和编译后的二进制文件。**
100% 离线运行。无需后端。无需 API keys。无需服务器调用。
## 什么是 PII?
PII = **Personally Identifiable Information**(个人身份信息)。例如:
- 电子邮件地址 (`john@gmail.com`)
- 电话号码 (`+1 555-123-4567`)
- 信用卡号 (`4532 0151 1283 0366`)
- 社会安全号码 (`123-45-6789`)
- 密码、API keys、tokens、IP 地址、出生日期
如果这些数据通过日志、剪贴板或内存泄露,这就是一个安全风险。
**flutter_neo_shield 拥有 6 个模块来防止这种情况:**
| 模块 | 功能简介(一句话) |
|--------|---------------------------|
| **Log Shield** | 您使用 `shieldLog()` 代替 `print()` —— 它在打印前隐藏敏感数据 |
| **Clipboard Shield** | 当用户复制敏感文本时,它会在 X 秒后自动从剪贴板删除 |
| **Memory Shield** | 将机密信息存储为字节,并在您使用完毕后用零覆盖它们 |
| **String Shield** | 在编译时加密字符串字面量,使其无法通过 `strings` 从二进制文件中提取 |
| **RASP Shield** | 在运行时检测 Root、越狱、调试器、模拟器、Frida 和篡改,以阻止攻击者 |
| **Screen Shield** | 阻止截屏、录屏和应用切换器缩略图捕获敏感屏幕 |
## 各模块工作原理(简单解释)
### 1. Log Shield —— “安全的 print()”
**问题所在:**
在开发过程中,您经常打印内容到调试控制台:
```
print('User logged in: john@gmail.com with token: Bearer sk-abc123');
```
这会将 **真实的 email 和 token** 打印到控制台。如果您在发布应用前忘记删除
这个打印语句,同样的数据最终会出现在崩溃报告服务(Crashlytics, Sentry 等)中 —— 这就是数据泄露。
**Log Shield 如何解决它:**
您用 `shieldLog()` 替换 `print()`。它为您提供结构化的、PII 安全的日志记录:
- **开发期间** (`flutter run`):`shieldLog()` 正常显示所有真实值以便调试。
- **在发布构建中** (`flutter build`):相同的 `shieldLog()` 调用 **自动隐藏敏感数据** —— 或者保持完全静默。
**您只需编写一次代码。它会自动在每个模式下做正确的事。**
```
shieldLog('User logged in: john@gmail.com with token: Bearer sk-abc123');
// BY DEFAULT — PII is hidden in all modes (debug + release):
// → [INFO] User logged in: [EMAIL HIDDEN] with token: Bearer [TOKEN HIDDEN]
```
**您不需要在开发和生产环境之间更改任何代码。** 只需在所有地方使用
`shieldLog()` 代替 `print()`,它就会处理这两种模式。
**它自动检测并隐藏的内容(在发布模式下):**
| 您的输入 | 发布控制台显示的内容 |
|------------|---------------------------|
| `john@gmail.com` | `[EMAIL HIDDEN]` |
| `+1 555-123-4567` | `[PHONE HIDDEN]` |
| `123-45-6789` | `[SSN HIDDEN]` |
| `4532015112830366` | `[CARD HIDDEN]` |
| `eyJhbGciOi...` (JWT) | `[JWT HIDDEN]` |
| `Bearer sk-abc123` | `Bearer [TOKEN HIDDEN]` |
| `password=secret` | `password=[HIDDEN]` |
| `sk_live_abc123...` | `[API_KEY HIDDEN]` |
| `1985-03-15` | `[DOB HIDDEN]` |
| `192.168.1.1` | `[IP HIDDEN]` |
| `GB29 NWBK 6016 1331 9268 19` | `[IBAN HIDDEN]` |
| `AB 12 34 56 C` (UK NIN) | `[NI NUMBER HIDDEN]` |
| `123-456-789` (Canadian SIN) | `[SIN HIDDEN]` |
| `A12345678` (Passport) | `[PASSPORT HIDDEN]` |
### 2. Clipboard Shield —— “自动删除剪贴板”
**问题所在:**
想象您的应用有一个“复制 API Key”按钮。用户点击它,API key 就进入了剪贴板。现在那个 key **永远留在剪贴板上** —— 直到用户复制其他东西。手机上的任何其他应用都可以读取它。
**Clipboard Shield 如何解决它:**
不用 Flutter 的 `Clipboard.setData()`,而是使用 `ClipboardShield().copy()`。它正常复制文本,但 **启动一个倒计时器**。计时器到期后(例如 15 秒),它会自动清除剪贴板。
```
// BEFORE (unsafe):
Clipboard.setData(ClipboardData(text: 'sk-my-secret-api-key'));
// The API key stays on clipboard FOREVER until user copies something else.
// AFTER (safe):
await ClipboardShield().copy('sk-my-secret-api-key', expireAfter: Duration(seconds: 15));
// The API key is copied to clipboard.
// After 15 seconds → clipboard is automatically cleared (emptied).
// Bonus: it also tells you "hey, that text contained an API key" (PII detection).
```
**它还为您提供了现成的 widgets:**
```
// A button that copies text securely when tapped:
SecureCopyButton(
text: 'sk-my-secret-api-key',
expireAfter: Duration(seconds: 15),
child: ElevatedButton(onPressed: null, child: Text('Copy Key')),
)
// A text field that clears the clipboard after the user pastes into it:
SecurePasteField(
decoration: InputDecoration(labelText: 'Paste password'),
clearAfterPaste: true, // clipboard is emptied right after paste
)
```
### 3. Memory Shield —— “用完后粉碎机密”
**问题所在:**
当您将密码或 API key 存储在普通的 Dart `String` 中时,即使您停止使用它,它仍然留在手机的 RAM(内存)中。Dart 的垃圾回收器最终会删除它,但它不会覆盖字节 —— 机密信息只是停留在内存中,直到其他东西碰巧写入该位置。
这是一个风险,因为内存转储攻击或调试工具可以从 RAM 读取旧值。
**Memory Shield 如何解决它:**
不用普通的 `String`,而是使用 `SecureString`。它将文本存储为原始字节。当您调用 `.dispose()` 时,它 **用零覆盖每个字节** —— 机密信息被真正销毁,而不仅仅是被遗忘。
```
// BEFORE (unsafe):
String apiKey = 'sk-my-secret-key';
// ... use it ...
apiKey = ''; // You THINK it's gone, but the old bytes are still in RAM!
// AFTER (safe):
final apiKey = SecureString('sk-my-secret-key');
print(apiKey.value); // Use it: 'sk-my-secret-key'
apiKey.dispose(); // Every byte overwritten with 0. Actually gone.
apiKey.value; // Throws error — can't read disposed secret.
```
**额外功能:**
```
// Use a secret once, then it auto-destroys:
final result = SecureString('password123').useOnce((password) {
return hashPassword(password); // Use the password
});
// password123 is already wiped from memory here
// Auto-destroy after 5 minutes:
final temp = SecureString('session-token', maxAge: Duration(minutes: 5));
// Wipe ALL secrets at once (e.g., on logout):
MemoryShield().disposeAll();
```
### 4. String Shield —— “对逆向工程师隐藏字符串”
**问题所在:**
Flutter 的 `--obfuscate` 标志只会混淆类和方法名称。字符串字面量 —— API URL、keys、配置值 —— 在编译后的二进制文件中仍保持 **明文** 状态。攻击者运行 `strings libapp.so` 就能看到一切:
```
https://api.myapp.com/v2
sk_live_abc123xyz
my-secret-salt
```
**String Shield 如何解决它:**
您用 `@Obfuscate()` 注解您的机密字符串。在构建时(通过 `build_runner`),生成器将每个字符串替换为加密的字节数组。在运行时,它们在被访问时会被透明地解密。
```
import 'package:flutter_neo_shield/string_shield.dart';
part 'secrets.g.dart';
@ObfuscateClass()
abstract class AppSecrets {
@Obfuscate()
static const String apiUrl = 'https://api.myapp.com/v2';
@Obfuscate(strategy: ObfuscationStrategy.enhancedXor)
static const String apiKey = 'sk_live_abc123xyz';
}
// Usage — transparent, just like accessing a normal field:
final url = $AppSecrets.apiUrl; // decrypted at runtime
```
运行 `dart run build_runner build`,生成器将创建包含加密数据的 `secrets.g.dart`。现在 `strings libapp.so` 显示的是随机字节而不是您的机密信息。
**三种混淆策略:**
| 策略 | 工作原理 | 最适合 |
|----------|-------------|----------|
| `xor` (默认) | 使用随机 key 进行 XOR | 大多数字符串 —— 快速,阻止 `strings` 命令 |
| `enhancedXor` | XOR + 反转 + 垃圾字节 | 高价值机密 —— 更难的模式分析 |
| `split` | 拆分为打乱的块 | 绝不能连续出现的字符串 |
**String Shield 的设置:**
```
# pubspec.yaml
dev_dependencies:
build_runner: ^2.4.0
```
然后运行: `dart run build_runner build`
### 5. RASP Shield —— “Runtime App Self Protection”
**问题所在:**
攻击者通常将您的应用安装在已 root 的设备或模拟器上,附加调试器,或注入像 [Frida](https://frida.re/) 这样的工具来挂钩您应用的内存并窃取 API keys 或绕过付费墙。
**RASP Shield 如何解决它:**
它会检测这些恶意环境,以便您可以限制功能、清除敏感数据或让应用崩溃。

```
import 'package:flutter_neo_shield/rasp_shield.dart';
// Perform a full security scan on startup:
// In strict mode, throws SecurityException if any threat is detected.
final report = await RaspShield.fullSecurityScan(mode: SecurityMode.strict);
// Or use warn mode to log threats and continue:
final report = await RaspShield.fullSecurityScan(mode: SecurityMode.warn);
// Or silent mode with manual handling:
final report = await RaspShield.fullSecurityScan();
if (!report.isSafe) {
print('SECURITY WARNING: Unsafe environment detected!');
if (report.debuggerDetected) print('Debugger attached!');
if (report.rootDetected) print('Device is rooted/jailbroken!');
if (report.emulatorDetected) print('Running on emulator!');
if (report.fridaDetected) print('Frida instrumentation detected!');
if (report.hookDetected) print('Hooking framework (Substrate/Xposed) detected!');
if (report.integrityTampered) print('App binary was tampered/sideloaded!');
}
// Exit the app
_terminateApp();
```
您也可以在敏感操作(如处理支付)之前运行独立检查:
```
if ((await RaspShield.checkFrida()).isDetected) {
throw Exception("Payment blocked: Security risk.");
}
```
### 6. Screen Shield —— “阻止截屏和录屏”
**问题所在:**
您的应用显示敏感数据 —— 银行余额、OTP、医疗记录、信用卡号。恶意应用(甚至用户)可以截取或录制此数据。在 Android 上,任何拥有 `MEDIA_PROJECTION` 权限的应用都可以静默录制您的屏幕。在 iOS 上,内置屏幕录制器或 AirPlay 镜像可以捕获所有内容。
甚至 **应用切换器**(最近的应用视图)也会截取您的屏幕快照 —— 因此当用户按下主屏幕按钮时,任何看着手机的人都能看到您的敏感数据缩略图。
**Screen Shield 如何解决它:**
它使用 OS 级别的 API 使您应用的内容 **对所有捕获方法不可见**:
- **Android:** 在 Activity 窗口上设置 `FLAG_SECURE`。OS 本身为所有捕获呈现 **黑屏** —— 截图、录屏、Chromecast、`adb screencap` 和应用切换器缩略图。这适用于所有 Android 版本。
- **iOS:** 使用带有 `isSecureTextEntry = true` 的 `UITextField` 作为渲染层。OS 将此层中的内容视为受 DRM 保护的,并在 **捕获期间将其留空**。这与银行应用使用的技术相同。此外,应用切换器中会显示模糊遮罩。
**最简单的用法 —— 保护整个应用:**
```
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterNeoShield.init(
screenConfig: ScreenShieldConfig(
blockScreenshots: true, // Block screenshots
blockRecording: true, // Block screen recording
guardAppSwitcher: true, // Blur content in app switcher
),
);
runApp(MyApp());
}
// That's it. Every screen in your app is now protected.
```
**按屏幕保护 —— 仅保护敏感屏幕:**
```
// Wrap only the screens that show sensitive data:
class PaymentScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenShieldScope(
enableProtection: true, // Block capture on this screen
guardAppSwitcher: true, // Blur in app switcher
onScreenshot: () { // Called when screenshot is taken (iOS only)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Screenshots are not allowed on this screen')),
);
},
child: Scaffold(
body: PaymentForm(), // This content will be black in screenshots
),
);
}
}
// When the user navigates away from PaymentScreen, protection is auto-disabled.
```
**动态切换保护 —— 例如,仅在显示 OTP 期间进行保护:**
```
// Show OTP — enable protection
await FlutterNeoShield.screen.enableProtection();
// ... user enters OTP ...
// OTP consumed — disable protection
await FlutterNeoShield.screen.disableProtection();
```
**检测屏幕录制 (iOS) —— 例如,暂停敏感内容:**
```
FlutterNeoShield.screen.onRecordingStateChanged.listen((event) {
if (event.isRecording) {
// Someone started screen recording!
// Navigate away, pause video, or show a warning
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => RecordingBlockedScreen()),
);
}
});
```
**启用保护时各平台的行为:**
| 操作 | Android | iOS |
|--------|---------|-----|
| 用户截屏 | 捕获 **黑屏** | 捕获 **空白内容** |
| 用户开始录屏 | 录制 **黑屏** | 内容 **留空**;录制状态事件触发 |
| 用户 Chromecast / AirPlay | 电视上显示 **黑屏** | 电视上显示 **空白内容** |
| 应用出现在最近任务中 | 缩略图是 **黑色** | 缩略图是 **模糊的** |
| `adb screencap` (开发者工具) | 捕获 **黑屏** | N/A |
## 安装
**步骤 1:** 添加到 `pubspec.yaml`:
```
dependencies:
flutter_neo_shield: ^0.6.0
```
**步骤 2:** 运行:
```
flutter pub get
```
**步骤 3:** 在 `main.dart` 中初始化:
```
import 'package:flutter_neo_shield/flutter_neo_shield.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterNeoShield.init(); // That's it!
runApp(MyApp());
}
```
**步骤 4:** 在您应用的任何地方开始使用它:
```
// Instead of print():
shieldLog('Debug: user email is john@test.com');
// Instead of Clipboard.setData():
await ClipboardShield().copy('sensitive-text', expireAfter: Duration(seconds: 15));
// Instead of String for secrets:
final secret = SecureString('my-api-key');
// Protect strings in compiled binary:
// (see String Shield section above for full setup)
final url = $AppSecrets.apiUrl; // decrypted at runtime
// Check if device is safe (RASP):
final report = await RaspShield.fullSecurityScan();
if (!report.isSafe) {
// exit or restrict user
}
// Block screenshots & recording:
await FlutterNeoShield.screen.enableProtection();
// Or wrap a single screen:
ScreenShieldScope(child: SensitiveScreen())
```
## 现实世界的例子
这是一个典型的 Flutter 应用场景 —— 登录屏幕:
```
// ============================================
// WITHOUT flutter_neo_shield (unsafe)
// ============================================
Future login(String email, String password) async {
print('Login attempt: $email'); // LEAKS email in debug AND release!
final token = await api.login(email, password);
print('Got token: $token'); // LEAKS token in debug AND release!
final savedToken = token; // Token stays in RAM forever
}
// ============================================
// WITH flutter_neo_shield (safe — zero extra effort)
// ============================================
Future login(String email, String password) async {
shieldLog('Login attempt: $email');
// Debug console: [INFO] Login attempt: john@gmail.com ← you see it for debugging!
// Release console: [INFO] Login attempt: [EMAIL HIDDEN] ← hidden in production!
final token = await api.login(email, password);
shieldLog('Got token: $token');
// Debug console: [INFO] Got token: eyJhbGci... ← you see it for debugging!
// Release console: [INFO] Got token: [JWT HIDDEN] ← hidden in production!
// Token stored securely — wiped from RAM on dispose:
final savedToken = SecureString(token);
savedToken.dispose(); // Bytes overwritten with zeros
}
```
**注意:** 您为开发和生产环境编写完全相同的代码。`shieldLog()`
自动知道您处于哪种模式并做正确的事。
## 初学者常见问题解答
**问:Log Shield 会自动隐藏我所有的 `print()` 语句吗?**
答:**不会。** 您需要在代码中将 `print()` 替换为 `shieldLog()`。这是一个手动替换 —— 但只是一个词。在您的项目中搜索 `print(` 并替换为 `shieldLog(`。
**问:但是如果 `shieldLog()` 隐藏了数据,我在开发期间如何调试?**
答:默认情况下 (v0.5.2+),为了安全起见,`shieldLog()` 在所有模式下都会隐藏 PII。要在本地开发期间查看真实值,请在您的 `LogShieldConfig` 中设置 `sanitizeInDebug: false`。您编写一次代码,它会在每种模式下做正确的事。
**问:我需要使用所有 6 个模块吗?**
答:不需要。只使用您需要的。您可以只导入一个模块:
```
import 'package:flutter_neo_shield/log_shield.dart'; // Only Log Shield
import 'package:flutter_neo_shield/clipboard_shield.dart'; // Only Clipboard Shield
import 'package:flutter_neo_shield/memory_shield.dart'; // Only Memory Shield
import 'package:flutter_neo_shield/string_shield.dart'; // Only String Shield
import 'package:flutter_neo_shield/rasp_shield.dart'; // Only RASP Shield
import 'package:flutter_neo_shield/flutter_neo_shield.dart'; // Screen Shield (included in main import)
```
**问:Screen Shield 真的能阻止截屏吗?**
答:**是的,在 Android 和 iOS 上。** 在 Android 上,`FLAG_SECURE` 使 OS 为所有捕获方法渲染黑屏 —— 这是在 OS 级别强制执行的,没有 root 无法绕过。在 iOS 上,安全文本字段技巧在捕获期间留空内容。然而,没有任何软件可以阻止某人用物理相机对着屏幕拍摄。
**问:我可以只保护某些屏幕而不保护其他屏幕吗?**
答:可以。使用 `ScreenShieldScope` widget 仅包装显示敏感数据的屏幕。当用户导航离开时,保护会自动禁用:
```
ScreenShieldScope(
child: PaymentScreen(), // Protected
)
// Other screens remain unprotected
```
或者手动切换:`await FlutterNeoShield.screen.enableProtection()` / `.disableProtection()`。
**问:Screen Shield 支持 web 或桌面吗?**
答:还不支持。防止屏幕捕获需要 web 浏览器和 Linux 不公开的 OS 级 API。macOS (`NSWindow.sharingType = .none`) 和 Windows (`SetWindowDisplayAffinity`) 支持计划在未来的版本中推出。在不支持的平台上,调用是无操作 (no-ops) —— 没有错误,没有崩溃。
**问:这会把我的数据发送到任何服务器吗?**
答:**不会。** 一切都在设备本地运行。零网络调用。零 API keys。零后端。
**问:Clipboard Shield 会阻止用户复制文本吗?**
答:**不会。** 文本被正常复制。用户可以立即粘贴。Clipboard Shield 只是启动一个计时器,**在时间到期后清除剪贴板**默认 30 秒)。所以如果用户在 30 秒内粘贴,一切正常。
**问:如果我忘记在 SecureString 上调用 `dispose()` 会怎样?**
答:您可以设置 `maxAge` 自动释放它,或者在注销时调用 `MemoryShield().disposeAll()`。您也可以启用 `autoDisposeOnBackground: true` 以在应用进入后台时清除所有机密。
**问:我可以添加自己的模式来检测吗?**
答:可以!例如,如果您的公司使用像 `ACCT-1234567890` 这样的内部账号:
```
PIIDetector().addPattern(PIIPattern(
type: PIIType.custom,
regex: RegExp(r'ACCT-\d{10}'),
replacement: '[ACCOUNT HIDDEN]',
description: 'Internal account numbers',
));
shieldLog('Account: ACCT-1234567890');
// Output: Account: [ACCOUNT HIDDEN]
```
**问:Dio 拦截器会更改我的实际 HTTP 请求吗?**
答:不会。它只清理 **日志输出**。您的实际请求和响应保持不变。
## 配置(可选)
默认设置对大多数应用都适用。但您可以自定义所有内容:
```
FlutterNeoShield.init(
config: ShieldConfig(
enabledTypes: {PIIType.email, PIIType.phone, PIIType.ssn}, // Only detect these (empty = all)
enableReporting: true, // Track how many detections
),
logConfig: LogShieldConfig(
silentInRelease: true, // No logs at all in release builds
showRedactionNotice: true, // Show "[2 items redacted]" at end of log
),
clipboardConfig: ClipboardShieldConfig(
defaultExpiry: Duration(seconds: 30), // Auto-clear after 30s
clearAfterPaste: true, // Also clear after user pastes
),
memoryConfig: MemoryShieldConfig(
autoDisposeOnBackground: true, // Wipe all secrets when app goes to background
),
stringShieldConfig: StringShieldConfig(
enableCache: false, // Set true to cache decrypted strings (faster but less secure)
enableStats: false, // Track deobfuscation counts (off by default)
),
screenConfig: ScreenShieldConfig(
blockScreenshots: true, // Prevent screenshots from capturing app content
blockRecording: true, // Prevent screen recording
guardAppSwitcher: true, // Blur/hide content in recent apps
detectScreenshots: true, // Listen for screenshot events (iOS)
detectRecording: true, // Listen for recording state changes (iOS)
enableOnInit: true, // Auto-enable on init (default: true)
),
);
```
## 日志函数参考
| 函数 | 何时使用 |
|----------|-------------|
| `shieldLog('message', level: 'ERROR', tag: 'auth')` | 带有级别和标签的 PII 清理日志 |
| `shieldLogJson('label', {...})` | 记录 JSON/Map 并清理所有值 |
| `shieldLogError('message', error: e, stackTrace: s)` | 记录带有堆栈跟踪的错误 |
## Dio 集成
如果您使用 Dio 进行 HTTP 调用,请添加拦截器以清理所有 HTTP 日志:
```
final dio = Dio();
dio.interceptors.add(DioShieldInterceptor());
// Now all request/response logs have PII hidden automatically.
```
请参阅 GitHub 上的 [Dio 集成文件](https://github.com/neelakandanz/flutter-neo-shield/blob/main/lib/src/log_shield/dio_shield_interceptor.dart)。
## 平台支持
| 平台 | Log Shield | Clipboard Shield | Memory Shield | String Shield | RASP Shield | Screen Shield |
|----------|:----------:|:----------------:|:-------------:|:-------------:|:-----------:|:-------------:|
| Android | 是 | 是 | 是 (原生擦除) | 是 | 是 | 是 (FLAG_SECURE) |
| iOS | 是 | 是 | 是 (原生擦除) | 是 | 是 | 是 (安全层 + 检测) |
| Web | 是 | 是 | 是 (Dart 回退) | 是 | 否 | 否 |
| macOS | 是 | 是 | 是 (Dart 回退) | 是 | 否 | 计划中 |
| Windows | 是 | 是 | 是 (Dart 回退) | 是 | 否 | 计划中 |
| Linux | 是 | 是 | 是 (Dart 回退) | 是 | 否 | 否 |
## 贡献
1. Fork 本仓库
2. 创建一个功能分支
3. 为您的更改编写测试
4. 确保 `dart analyze` 通过且零问题
5. 确保 `dart format` 不产生任何更改
6. 提交 pull request
## 许可证
MIT License。详情请见 [LICENSE](LICENSE)。
Copyright (c) 2024 Neelakandan
标签:App安全, DLP, DNS 反向解析, DOM解析, Flutter, GDPR, PII保护, RASP, Root检测, 内存安全, 剪贴板管理, 反调试, 字符串加密, 敏感信息屏蔽, 数据防泄露, 日志脱敏, 目录枚举, 移动安全, 越狱检测, 逆向防护, 防录屏, 防截屏, 隐私合规