š±Securing Flutter App: Best Practices and Techniques (Part 2)š”ļø
This is Part #2 of Securing Flutter App, you can Check the Part #1 Article for the first 2 Measure to secure your flutter app
In this Article Weāll continue Adding Security Layers to our Flutter App by Adding 2 more security measures to our App.
In Part #1 we used Code Obfuscation & Enforcing HTTPS and SSL Certificate Pinning.
In this Part Weāll talk about the following:
- App Integrity and Runtime Environment Checks
- Secure Data Storage & Caching
So, Letās Startā¦
1. App Integrity and Runtime Environment Checks
Ensuring app integrity and securing the runtime environment are critical steps in safeguarding Flutter applications from potential threats like tampering, reverse engineering, or running in compromised environments. App integrity checks help confirm that the app hasnāt been modified or altered, ensuring the code running is exactly what the developers intended. Runtime environment checks, on the other hand, detect if the app is being executed in a secure and trusted environment, identifying risks such as rooted devices or the presence of debuggers. Together, these checks provide a crucial layer of security, helping developers protect user data, prevent unauthorized access, and ensure the app behaves as intended, even in hostile environments.
First, Letās know what could violate app integrity and what checks to do for Runtime Environment.
1. App Integrity Compromised:
Refers to the assurance that a mobile applicationās code and resources have not been tampered with, altered, or compromised since it was published by the developer. It ensures that the app being executed is the original, unmodified version. App integrity checks are vital for detecting unauthorized modifications, such as reverse engineering, repackaging, or injecting malicious code into the app, so we always need to check if applicationās integrity is compromised (e. g. invalid signature, package name, signing hash,ā¦).
2. App Running in Debug Mode:
When we release an App on Store, itās build should be Release of course as we all know, if unsafe environment (debugger) is detected, this could be considered a vulnerability in the App
3. App/Device Unbinding:
The device binding process ties an appās functionality to a specific device, ensuring that the app can only function on that particular device under normal, secure conditions. When unbinding occurs, the app disables access or locks certain features to prevent data leakage, unauthorized access, or further use of the app on that compromised device.
4. Hooks Detected:
Hooks in the context of reverse engineering and runtime manipulation refer to techniques where certain functions or methods of an app are intercepted or altered by malicious tools, allowing an attacker to modify the appās behaviour or extract sensitive data. Tools like Frida and Xposed are commonly used for this purpose, especially in scenarios where attackers want to bypass security mechanisms, capture sensitive information, or alter app functionality at runtime.
5. Secure Hardware Not Available:
When the app detects that secure hardware, such as a Trusted Execution Environment (TEE) or Secure Element (SE), is not available on the device. Secure hardware is critical for ensuring that sensitive operations like encryption, authentication, and key management are handled in a secure and isolated environment, protecting the app from attacks like tampering or data extraction.
6. Running on Simulator/Emulator:
Refers to when an application is executed in a virtual environment, such as an Android Emulator or iOS Simulator, instead of on a physical device. Simulators and emulators are often used during app development to test the app in various environments and configurations without needing multiple physical devices.
However, in the context of app security, detecting whether an app is running on a simulator or emulator is important because these environments are more vulnerable to reverse engineering, tampering, and other malicious activities. Simulators/emulators lack many security features present on physical devices, such as secure hardware (Trusted Execution Environment or Secure Element), making it easier for attackers to bypass certain protections.
7. Developer Mode Enabled:
Refers to a state in which an application is running in developer mode or development mode. In development mode, the app is not optimized for performance and security, and certain tools and features are enabled to aid developers in debugging and testing the app. This mode is used during development to allow for features such as hot reload, detailed logging, debugging, and live code editing.
However, running an app in developer mode in production environments can expose vulnerabilities and pose security risks, as many protections and optimizations are either disabled or bypassed.
8. Privileged Access:
When the device our App running on has elevated rights or privileged access. This typically occurs when the device has been rooted (in the case of Android) or jailbroken (in the case of iOS). Rooting or jailbreaking a device allows users to bypass certain security restrictions set by the operating system, giving them full control over the device, which can be exploited for malicious purposes.
When privileged access is detected, itās a sign that the deviceās security has been compromised.
9. Installed from unofficial Store:
Refers to the scenario where an app has been downloaded and installed from a source other than the official app stores like Google Play Store (for Android) or the Apple App Store (for iOS) or Huawei App Gallery. Apps installed from unofficial stores are considered higher security risks because they may bypass the usual security checks performed by official platforms.
10. Obfuscation Issues
refers to a situation where an application is not properly obfuscated, potentially exposing its source code and logic to reverse engineering and malicious activities. Obfuscation is a process that transforms the readable source code of an app into a more complex, hard-to-understand format without altering its functionality. It makes reverse engineering and analysing the code much more difficult for attackers.
If itās compromised, it indicates that the application is either running in a non-obfuscated state or that obfuscation is improperly applied, leaving the app vulnerable.
11. VPN Detected:
when the application detects that the device is connected to an active Virtual Private Network (VPN). VPNs are often used to protect user privacy and data by encrypting internet traffic and masking the userās IP address. However, for certain apps ā especially those that deal with sensitive or regulated data ā knowing whether a VPN is in use is crucial for ensuring that the appās security and compliance requirements are met.
While VPNs are generally used to enhance security, they can also raise potential issues for certain apps, such as:
- Geolocation Mismatches: VPNs can hide or alter the userās actual location, which may be problematic for location-based services or apps that enforce region-based access restrictions.
- Bypassing Network Controls: VPNs can bypass certain network firewalls or restrictions, which may allow unauthorized access to restricted resources.
- Privacy Concerns: In some cases, VPNs may be misused to anonymize malicious activity, or in regions where strict data policies apply, the app may need to be aware of VPN usage for compliance reasons.
Those are the most 11 checks we need to check to make sure our app is a bit more secure š”ļø
Lucky us there is a Package that has all those checks for us with very easy setup and use. Itās Called āFreeRASPā.
Letās dig deeper with itā¦
1. Add the FreeRASP package to your pubspec.yaml
:
dependencies:
freerasp: ^6.6.0
2. IOS Setup:
You need at least Xcode 14.3.1 to be able to build the application.
3. Android Setup:
- From root of your project, go to android > app > build.gradle
- In
defaultConfig
updateminSdkVersion
to at least 23 (Android 6.0) or higher
android {
...
defaultConfig {
...
minSdkVersion 23
...
}
...
}
4. Add imports to the top of the file where you want to use Talsec:
import 'package:freerasp/freerasp.dart';
5. Configure freeRASP for the APP:
To properly configure freeRASP for your app, you need to initialize it with a configuration that contains relevant details about your app. In addition for freeRASP to work correctly, it is necessary that Flutter Bindings are initialized. This can be satisfied by calling WidgetsFlutterBinding.ensureInitialized()
, as shown in the code snippet below.
void main() {
...
// This line is important!
WidgetsFlutterBinding.ensureInitialized();
// create configuration for freeRASP
final config = TalsecConfig(
/// For Android
androidConfig: AndroidConfig(
packageName: 'your.package.name',
signingCertHashes: [
'AKoRu...'
],
supportedStores: ['some.other.store'],
),
/// For iOS
iosConfig: IOSConfig(
bundleIds: ['YOUR_APP_BUNDLE_ID'],
teamId: 'M8AK35...',
),
watcherMail: 'your_mail@example.com',
isProd: true,
);
}
Here, a TalsecConfig
is created with both AndroidConfig
and IOSConfig
. Identifiers packageName
and signingCertHashes
are required for Android version.
packageName
- package name of your app you chose when you created itsigningCertHashes
- list of hashes of the certificates of the keys which were used to sign the application. At least one hash value must be provided. Hashes which are passed here must be encoded in Base64 form
The package provide a handy util tool to help you convert your SHA-256 hash to Base64:
// Signing hash of your app
String base64Hash = hashConverter.fromSha256toBase64(sha256HashHex);
supportedStores
-Add the supported stores like samsung galaxy store, or other Android stores available.- Similarly,
bundleIds
andteamId
are needed for iOS version of app. - Next, pass a mail address to
watcherMail
to be able to get reports. Mail has a strict formname@domain.com
which is passed as String. (you will get a weekly report for your application tells you what attacks or checks triggered for this week)
6. Provide the Checks we want:
final callback = ThreatCallback(
onAppIntegrity: () => _terminateApp('onAppIntegrity'),
onDebug: () => _terminateApp('onDebug'),
onDeviceBinding: () => _terminateApp('onDeviceBinding'),
onHooks: () => _terminateApp('onHooks'),
onSecureHardwareNotAvailable: () => _terminateApp('onSecureHardwareNotAvailable'),
onSimulator: () => _terminateApp('onSimulator'),
onDevMode: () => _terminateApp('onDevMode'),
onPrivilegedAccess: () => _terminateApp('onPrivilegedAccess'),
onUnofficialStore: () => _terminateApp('onUnofficialStore'),
onSystemVPN: () => _terminateApp('onSystemVPN'),
onObfuscationIssues: () => _terminateApp('onObfuscationIssues'),
);
// Attaching listener
Talsec.instance.attachListener(callback);
await Talsec.instance.start(config);
}
static void _terminateApp(String issue) async {
debugPrint('issue :$issue');
// Perform what you want if any issue detected for example:
exit(0);
}
Now whenever a security issue raised, you have the full control what to do, for example above, the App Terminates whenever an issue detected.
By this, our security measures for integrity are readyā¦
2. Secure Data Storage & Caching
Securing data storage and caching in Flutter is essential to protect sensitive information from unauthorized access or tampering. In Flutter apps, data is often stored locally for performance and offline access, and itās crucial to implement proper security practices for this stored data.
A. Use Secure Storage for Sensitive Data
For sensitive data such as authentication tokens, user credentials, and other confidential information, you should use secure storage instead of plain-text storage. The flutter_secure_storage
package provides a secure way to store data using the platformās underlying secure storage mechanisms (e.g., Keychain for iOS, Keystore for Android).
1. Installation:
dependencies:
flutter_secure_storage: ^9.2.2
2. Usage:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
final storage = FlutterSecureStorage(aOptions: _getAndroidOptions());
// Read value
String value = await storage.read(key: key);
// Read all values
Map<String, String> allValues = await storage.readAll();
// Delete value
await storage.delete(key: key);
// Delete all
await storage.deleteAll();
// Write value
await storage.write(key: key, value: value);
This ensures that sensitive information is encrypted and stored securely, relying on platform-specific encryption features.
B. Encrypt Data in Shared Preferences
For non-sensitive data that needs to be stored persistently, such as user preferences or app settings, you can use Shared Preferences. However, for added security, itās important to encrypt the data before storing it.
You can use the encrypt
package to encrypt data before storing it in shared preferences.
1. Installation:
dependencies:
shared_preferences: ^2.3.2
encrypt: ^5.0.3
2. Usage:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:encrypt/encrypt.dart';
final key = Key.fromUtf8('my 32 length key................');
final encrypter = Encrypter(AES(key));
final iv = IV.fromLength(16);
// Encrypt and store data
Future<void> storeEncryptedData(String key, String value) async {
final encrypted = encrypter.encrypt(value, iv: iv);
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, encrypted.base64);
}
// Retrieve and decrypt data
Future<String?> retrieveEncryptedData(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? encrypted = prefs.getString(key);
if (encrypted != null) {
final decrypted = encrypter.decrypt64(encrypted, iv: iv);
return decrypted;
}
return null;
}
This approach ensures that even data stored in shared preferences is encrypted and secured from prying eyes.
C. Implement Secure Caching Mechanisms with
When caching data, such as API responses or images, itās important to ensure that sensitive information in cache is protected. You can either encrypt the cache or clear sensitive cache data after a session or setting an expiration period for it.
For basic caching of API data, you can use any local storage Package and secure it in your way, for example, Hive
or Isar
package, which allows local storage in a key-value database, supports encryption out of the box with setting encryptionCipher
Also, you can use Drift
Package and set an Expiry Date or condition for the caching, then whenever you retrieve the data from the cache, check for the expiry date, if passed, clear the cache and retrieve the data from itās main source/ remote source and cache it again with new expiry date or condition
(of course, you can encrypt the data before caching and decrypt it when retrieving. Iāll leave this to you to try itš)
š”ļø This is Part 2 / 4 for Securing Flutter Appš”ļø
š±Securing Flutter App: Best Practices and Techniques:
Part #1: š±Securing Flutter App: Best Practices and Techniques (Part 1)š”ļø
Part #2: š±Securing Flutter App: Best Practices and Techniques (Part 2)š”ļø
Part #3: š±Securing Flutter App: Best Practices and Techniques (Part 3)š”ļø
Part #4: š±Securing Flutter App: Best Practices and Techniques (Part 4)š”ļø