flutter_rasp 3.0.0
flutter_rasp: ^3.0.0 copied to clipboard
A comprehensive RASP (Runtime Application Self-Protection) plugin for Flutter. Detect root, jailbreak, emulators, debuggers, hooks, repackaging, untrusted installs, VPN, SSL pinning, and more.
Flutter RASP #
A comprehensive RASP (Runtime Application Self-Protection) plugin for Flutter. Protect your app against reverse engineering, tampering, runtime attacks, and man-in-the-middle attacks with zero external SDK dependencies.
Features #
Threat Detection #
| Threat | Android | iOS | Description |
|---|---|---|---|
| Root / Jailbreak | ✅ | ✅ | Compromised OS with full system access. Attackers can bypass app sandboxing, read private data, and inject code |
| Emulator / Simulator | ✅ | ✅ | Virtual environments used to automate attacks, bypass device-bound protections, and analyze app behavior at scale |
| Debugger | ✅ | ✅ | Attached debuggers (JDWP, ptrace) allow stepping through code, modifying variables at runtime, and extracting secrets |
| Hooks (Frida/Xposed) | ✅ | ✅ | Instrumentation frameworks that intercept and modify function calls at runtime, bypassing security checks |
| Repackaging | ✅ | ✅ | Tampered app re-signed with a different certificate. Used to inject malware, remove license checks, or steal data |
| Trusted Install | ✅ | ✅ | Sideloaded apps bypass store review and integrity checks, increasing risk of running modified or malicious builds |
| VPN | ✅ | ✅ | Active VPN or proxy that can intercept, inspect, and modify network traffic between the app and its servers |
| Developer Mode | ✅ | ❌ | Enabled developer options and ADB expose debugging interfaces that allow unauthorized access to app internals |
| Device Passcode | ✅ | ✅ | Device without screen lock. Physical access gives unrestricted access to app data and keychain entries |
| Secure Hardware | ✅ | ✅ | Missing hardware-backed keystore (TEE/StrongBox, Secure Enclave). Cryptographic keys can be extracted by software attacks |
| Obfuscation | ✅ | ❌ | Unobfuscated binary with readable class and symbol names, making reverse engineering and vulnerability discovery trivial |
| Time Spoofing | ✅ | ❌ | Manipulated system clock used to bypass time-based logic like token expiration, trial periods, or certificate validity |
| Location Spoofing | ✅ | ❌ | Fake GPS coordinates used to bypass geo-restrictions, cheat in location-based services, or commit region-locked fraud |
| Multi-Instance | ✅ | ❌ | Cloned or dual-app environments that run multiple copies of the app to abuse promotions, bypass rate limits, or impersonate users |
| Screen Capture | ✅ | ✅ | Blocks screenshots and screen recording to prevent leaking sensitive UI content like PINs, tokens, or personal data |
SSL Certificate Pinning #
| Feature | Description |
|---|---|
| MitM Protection | Prevents man-in-the-middle attacks by rejecting connections with certificates not matching your pinned hashes, even if signed by a trusted CA |
| Public Key Pinning | SHA-256 of SubjectPublicKeyInfo (SPKI) — survives certificate renewals |
| Certificate Pinning | SHA-256 of the full DER-encoded certificate |
| Backup Pins | Multiple pins per host for key rotation |
| HTTP Client Compatibility | dart:io, Dio (onHttpClientCreate), http (IOClient) |
Getting Started #
dependencies:
flutter_rasp: ^3.0.0
| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 13.0 |
No additional permissions required. Zero external SDK dependencies.
Usage #
Initialization #
import 'package:flutter_rasp/flutter_rasp.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.high,
monitoringInterval: Duration(seconds: 10),
androidConfig: AndroidRaspConfig(
signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='],
),
iosConfig: IosRaspConfig(
teamId: 'A1B2C3D4E5',
bundleIds: ['com.yourcompany.yourapp'],
),
),
onThreatDetected: (threats) => debugPrint('$threats'),
threatCallback: ThreatCallback(
onRoot: () => navigateToBlockedScreen(),
onVpn: () => showVpnWarning(),
),
);
runApp(const MyApp());
}
Note: At least one of
onThreatDetectedorthreatCallbackmust be provided.
Platform Configuration #
Android — signingCertHashes expects your signing certificate fingerprint in Base64 format.
-
Get the SHA-256 fingerprint from
keytool, Google Play Console (App signing section), or Firebase Console. You will end up with a hex string like this:A1:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09:1A:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09 -
Convert it to Base64. You can get it directly from terminal:
keytool -list -v -keystore your-keystore.jks -alias your-alias 2>/dev/null \ | grep SHA256 | awk '{print $2}' | tr -d ':' | xxd -r -p | base64Or use the built-in utility:
final base64Hash = hashConverter.fromSha256toBase64( 'A1:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09:1A:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09', );
iOS — Find your Team ID at Apple Developer Account → Membership Details.
SSL Certificate Pinning #
SSL Pinning operates independently from the threat detection system. Use SslPinningClient to create an HttpClient that rejects connections not matching your pins.
Getting the Pin Hash
Use openssl to extract the pin hash from your server. The command depends on the pinning mode you choose:
Public Key Pin (recommended — survives certificate renewals):
openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
Certificate Pin (full certificate hash — changes on every renewal):
openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
| openssl x509 -outform DER \
| openssl dgst -sha256 -binary \
| base64
Note: Public key pins survive certificate renewals as long as the same key pair is reused. Certificate pins change every time the certificate is renewed, requiring an app update.
Basic Usage (dart:io)
import 'package:flutter_rasp/flutter_rasp.dart';
final config = SslPinningConfig(pins: {
'api.example.com': [
SslPin.publicKey('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX='),
],
});
final client = SslPinningClient.create(config);
final request = await client.getUrl(Uri.parse('https://api.example.com/data'));
final response = await request.close();
With Backup Pins (Key Rotation)
final config = SslPinningConfig(pins: {
'api.example.com': [
SslPin.publicKey('current_key_hash_AAAAAAAAAAAAAAAAAAAAAAAAAAA='),
SslPin.publicKey('backup_key_hash_BBBBBBBBBBBBBBBBBBBBBBBBBBBB='),
],
});
With Dio
final client = SslPinningClient.create(config);
final dio = Dio()
..httpClientAdapter = IOHttpClientAdapter(
onHttpClientCreate: (_) => client,
);
With http Package
final client = SslPinningClient.create(config);
final httpClient = IOClient(client);
final response = await httpClient.get(Uri.parse('https://api.example.com/data'));
Pinning Failure Callback
final client = SslPinningClient.create(
config,
onPinningFailure: (host, certificate) {
debugPrint('Pinning failed for $host');
},
);
Pinning Modes
| Mode | Constructor | Description |
|---|---|---|
| Public Key | SslPin.publicKey(hash) |
SHA-256 of the SPKI. Recommended: survives certificate renewals |
| Certificate | SslPin.certificate(hash) |
SHA-256 of the full DER-encoded certificate |
Tip: Prefer
publicKeymode — it survives certificate renewals as long as the key pair is reused.
Supported Stores / Distribution Methods #
The supportedStores parameter in AndroidRaspConfig controls which install sources are considered trusted.
| Store / Distribution method | Package name | Notes |
|---|---|---|
| App Store (iOS) | Included by default, no action needed | |
| TestFlight (iOS) | Included by default, no action needed | |
| Google Play | com.android.vending |
Included by default, no action needed |
| Huawei AppGallery | com.huawei.appmarket |
Included by default, no action needed |
| Amazon Appstore | com.amazon.venezia |
Included by default, no action needed |
| Samsung Galaxy Store | com.sec.android.app.samsungapps |
Included by default, no action needed |
| Firebase App Distribution | dev.firebase.appdistribution |
|
| Vivo App Store | com.vivo.appstore |
Common on Vivo devices |
| HeyTap | com.heytap.market |
Common on Realme and Oppo devices |
| Oppo App Market | com.oppo.market |
Common on Oppo devices |
| GetApps | com.xiaomi.mipicks |
Common on Xiaomi, Redmi and POCO devices |
To support additional stores, add their package names:
AndroidRaspConfig(
signingCertHashes: ['...'],
supportedStores: [
...AndroidRaspConfig().supportedStores,
'dev.firebase.appdistribution', // Firebase App Distribution
'com.vivo.appstore', // Vivo App Store
'com.heytap.market', // HeyTap (Realme, Oppo)
'com.oppo.market', // Oppo App Market
'com.xiaomi.mipicks', // GetApps (Xiaomi, Redmi, POCO)
],
);
Threat Policies #
Policies control which threats terminate the app at the native level before Dart code can react.
| Policy | Exit Threats |
|---|---|
ThreatPolicy.none |
None (report only) |
ThreatPolicy.low |
repackaging, trustedInstall |
ThreatPolicy.medium |
root, hook, repackaging, trustedInstall, obfuscationIssues, multiInstance |
ThreatPolicy.high |
root, hook, repackaging, trustedInstall, debug, devicePasscode, obfuscationIssues, multiInstance, secureHardwareNotAvailable, locationSpoofing |
const policy = ThreatPolicy(
exitThreats: {Threat.root, Threat.repackaging, Threat.vpn},
);
Tip: Use
ThreatPolicy.noneduring development.
Scans & Individual Checks #
final result = await FlutterRasp.instance.scanAll();
if (result.isCompromised) {
debugPrint('Detected: ${result.detectedThreats}');
}
Available: isRooted(), isEmulator(), isDebugged(), isHooked(), isRepackaged(), isUntrustedInstall(), isVpnConnected(), isDeveloperMode(), isDevicePasscodeDisabled(), isSecureHardwareUnavailable(), hasObfuscationIssues(), isTimeSpoofed(), isLocationSpoofed(), isMultiInstance().
Screen Capture Protection #
await FlutterRasp.instance.blockScreenCapture(true);
Architecture #
Flutter App
|
FlutterRasp (Singleton) SslPinningClient (Independent)
| |
FlutterRaspPlatform (Interface) PinValidator
| | |
MethodChannelFlutterRasp SHA-256 SpkiExtractor
|--- MethodChannel (commands/checks)
|--- EventChannel (threat stream)
Android (Kotlin) iOS (Swift)
----------------- -----------------
DetectorRegistry DetectorRegistry
|-- RootDetector |-- JailbreakDetector
|-- EmulatorDetector |-- SimulatorDetector
|-- DebugDetector |-- DebugDetector
|-- HookDetector |-- HookDetector
|-- RepackagingDetector |-- RepackagingDetector
|-- TrustedInstallDetector |-- TrustedInstallDetector
|-- VpnDetector |-- VpnDetector
|-- DeveloperModeDetector |-- DevicePasscodeDetector
|-- DevicePasscodeDetector |-- SecureHardwareDetector
|-- SecureHardwareDetector ScreenCaptureManager
|-- ObfuscationDetector
|-- TimeSpoofingDetector
|-- LocationSpoofingDetector
|-- MultiInstanceDetector
ScreenCaptureManager
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
Adding a New Detector #
- Create a detector class implementing
ThreatDetector(Android) orThreatDetectable(iOS) - Add it to the
DetectorRegistrylist - Add the corresponding
Threatenum value in Dart
License #
This project is licensed under the MIT License - see the LICENSE file for details.