neelakandanz/Flutter-Neo-Shield

GitHub: neelakandanz/Flutter-Neo-Shield

一款专为 Flutter 应用设计的全方位安全防护库,提供日志自动脱敏、内存擦除、防截屏及运行时环境检测,防止敏感数据在开发、运行及静态分析阶段泄露。

Stars: 0 | Forks: 1

# flutter_neo_shield [![pub package](https://img.shields.io/pub/v/flutter_neo_shield.svg)](https://pub.dev/packages/flutter_neo_shield) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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 如何解决它:** 它会检测这些恶意环境,以便您可以限制功能、清除敏感数据或让应用崩溃。 ![RASP Security Report in Action](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/120495a3b2013647.png) ``` 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检测, 内存安全, 剪贴板管理, 反调试, 字符串加密, 敏感信息屏蔽, 数据防泄露, 日志脱敏, 目录枚举, 移动安全, 越狱检测, 逆向防护, 防录屏, 防截屏, 隐私合规