Most of us depend on our devices daily for almost everything we do. Whether it’s for taking photos of our WFH setup, ordering food, shopping online, and banking, a lot of us do this using our mobile phones.
With the increasing number of digital products and services we use, so as the data stored in our devices, this, in turn, increases the risk of security exploit or exposure of data to motivated attackers.
As developers, we should ask ourselves, “How can we mitigate the risk of a security exploit and protect the data of our users?”.
I am not a security expert but I do have experience building and shipping apps to millions of users with little to no security breaches at all. Also, I currently build mobile banking apps for a FinTech company using Flutter.
10 Tips to Secure Your Flutter Apps for Mobile
Disclaimer: This article does not guarantee to make your apps 100% secure, but I hope it will reduce the risk for any security exploit on your apps and increase the protection of your users’ data.
1. Stay up-to-date
Getting your Flutter SDK, plugins, and packages up-to-date is the easiest and one the best ways to secure your apps. Google Flutter Team releases security fixes and patches for vulnerabilities found on the Flutter framework.
If you think you found an issue that is unresolved or hasn’t been filed yet on the Flutter repo, report it to the Google Flutter Team.
2. Obfuscate code
The compiled binaries and code of your apps can be reversed engineered. Some of the things that can be exposed include the strings, method and class names, and API keys. These data are either in their original form or in are in plain text.
Obfuscation is the practice of making something difficult to understand.
Here are some good examples from this for code obfuscation:
Rename obfuscation
String encryption
Dart
Dart comes with code obfuscation argument --obfuscate
in addition to the build command:
Example 1. Android app bundle without splitting:
flutter build appbundle --obfuscate --split-debug-info=/<directory>
Example 2: Android app bundle with splitting:
flutter build appbundle --target-platform android-arm,android-arm64,android-x64 --obfuscate --split-debug-info=/<directory>
NOTE: This is only for Dart code, you have to handle platform-specific obfuscation for the native code, eg. using ProGuard for Android.
Android
In your /android/app/build.gradle
file, add the following:
android {
...
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Create a ProGuard configuration file in /android/app/proguard-rules.pro
:
# Flutter
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
With ProGuard, it does not only obfuscate your code but also helps you shrink the size of your Android apps.
iOS
If you are using Objective-C or Swift and building for iOS, the compiler strips the symbols and applies optimizations to your code, making it already harder for the motivated attacker to read the compiled output of your code.
There are also paid tools that help you obfuscate your code: iXGuard and Vermatrix.
Although obfuscation is regarded as one of the best ways to prevent reverse-engineering on your apps, some de-obfuscation techniques are out there. The question to have in mind, “What can the motivated attacker see on my code and use against me or my users?”.
3. Secure API keys
API keys come in multiple formats but often, it’s in a form of a String
. It will a lot be easier for any motivated attacker to use your API keys and abuse them if its not encrypted or obfuscated.
Server-side
You can restrict API access to an API key.
For example, if you are using Google APIs or Firebase, you may want to restrict the access for your apps or be used specific services only.
Go to your console, select the API & Services, then restrict any services that you have depending on how you deemed it necessary.
Restricting Google API access for Android with the given package name and SHA-1 fingerprint
For services that don’t have server-side restrictions, you can move the integrations to the backend and expose the methods via RESTful APIs.
But if this is not possible, another way you can do is to encrypt/decrypt API keys on runtime, eg. when the user is authenticated. It doesn’t guarantee that your keys will be 100% secured, but it adds an extra layer of protection and make it harder for a motivated attacker to read it.
Development
Do not track API keys on your repository. Especially for open-source projects. You should enforce the use of environment configuration file/s.
The files I’m talking about can be a .env
file that contains all the values for the API keys or URLs that your apps connect to. For example, using flutter_dotenv.
.env
SOME_API_KEY=SOME_VALUE
SOME_HTTP_URL=SOME_VALUE
lib/main.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
final baseUrl = env['SOME_HTTP_URL'];
NOTE: In the compiled output of your code, these values can still be exposed, although the risk is reduced by not tracking them on version control like Git.
You can issue the environment configuration file/s to your team using LastPass or a similar password manager.
Do not share API keys, tokens, or any sensitive data in plain-text on your communication tools like Slack or Discord.
Avoid using Firebase Remote Config
Firebase Remote Config is a service that allows you to make changes on-the-fly without publishing a new build, eg. using bool
values as toggles and for A/B testing.
Although it’s possible, you should not use this service for storing sensitive data, eg. with the hope of changing API keys. It is strictly not recommended for that use case.
4. Restrict network traffic
Most apps are connected to the internet, whether to a third-party service provider or to their own servers. Typically, the exchange of information happens on a Transport Secure Layer, a.k.a. TLS, to provide a secure connection between your mobile apps and your servers.
Trusted network
One way to restrict network traffic or connection to an unsecured endpoint is through explicitly whitelisting your domain.
For Android:
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
</network-security-config>
https://developer.android.com/training/articles/security-config
For iOS:
ios/Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSExceptionDomains</key>
<dict>
<key>cocoacasts.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
https://cocoacasts.com/how-to-add-app-transport-security-exception-domains
Certificate Pinning
You can also implement certificate pinning for your apps to restrict the secure connection to particular certificates. This ensures that the connection between your apps and your servers is trusted and authentic.
Photo credits: TLS Certificate Pinning 101 - Nettitude Labs
Without certificate pinning, often the motivate attacker can eavesdrop or tamper data when on transit using hacked or self-signed certificates.
Here are some in-depth guide on how you can implement certificate pinning for iOS and Android.
5. Limit permissions
Permissions is way for your apps to access hardware or native APIs of your users’ devices. In choosing a plugin, you should always check whether the plugin has dubious permission request.
For example, if it’s a audio-only package, wouldn’t it be weird if the package requests for a camera or network permission?
6. Secure user data
Personally identifiable information, a.k.a. PII, is the most critical data that you don’t want to store on your apps, because when expose, you’re company is in big trouble.
But there are instances where PII is needed, eg. for offline-first apps. When needed, you can use flutter_secure_storage for storing PII or other sensitive data such as auth token.
In a nutshell, Flutter Secure Storage is a package that uses Keystore for Android and Keychains for iOS, both of which are considered a standard in terms of securing sensitive data to your users’ mobile devices.
Android Keystore Architecture
Caching
For storing other sensitive data, other than PII, I would recommend the use of hive for performance gain, although requires more setup. It uses AES-256 encryption which helps you secure the data of your users from an unwanted exploit or tampering.
Bonus Tip: To secure the data of your users, do not store it in plain text.
7. Integrate local authentication
Integrate local_auth to your Flutter app to provide app-level security for your users.
8. Secure your developer identity
Files like keystore, keystore.properties
, Google Service Account or any secrets that can expose your developer identity should be encrypted at all times when tracking them in a repository.
Create a directory and use GPG to encrypt it (or decrypt it later):
cd android
gpg --symmetric --cipher-algo AES256 android_keys.zip
Encrypt sensitive files, eg. key.jks & keystore.properties
Ignore and don’t keep track of the unencrypted sensitive files in your repo.
# Ignore Android keys
key.jks
key.properties
service_account_key.json
android_keys.zip
To know more about how to apply this step to your CI, read this.
9. Secure your CI infrastructure
Depending on whether your CI infrastructure is self-hosted or using services like Github Actions, you should be aware of what’s going on in your VMs and workflows.
Latest updates
To ensure that your apps are running in a secured environment, you should always keep your VMs up-to-date or be on a lookout for any security vulnerabilities (eg. OS updates for Linux for Android or macOS VMs for iOS)
Secrets
I talked about this earlier, you should not commit API keys or related sensitive data (or secrets) in your code, instead (for CI) you add them on the secrets settings of your project. Other services like Bitrise provide the same option for storing secrets.
10. Enforce strict access control
This is not Flutter related, but it’s good to have for your team.
Access Board
If you don’t have it yet, create an Access board for your team and that contains the tickets created for each request made to access service/s. This allows your team to have transparency and record on who’s requesting for access, when, who’s provisioning it and why, etc.
Sample access board on Github
Password Manager
Use password manager like Lastpass to secure the way you share or provision credentials. You don’t want to see a plain text API key on the communication tools that you are using.
Bonus
Jailbroken and rooted devices
Supporting jailbroken and rooted devices is a risk.
Depending on who your users are, assume that their devices are always insecure.
Jailbroken iOS and rooted Android devices have more privileges and may introduce malware to your user’s device that can circumvent the normal operations of the device.
There are instances where your apps need to run on jailbroken or rooted devices, eg. for research and testing. Often, you have to consider your target users and determine whether you will support jailbroken and rooted devices.
flutter_jailbreak_detection is a package that helps you detect if your app is currently running on a jailbroken or rooted device.
Final thoughts
I do hope the tips that I’ve shared here helped you understand different areas where you can improve in terms of security and data privacy.
The takeaway for this article is to not just the application of these tips, but the mindset that it creates.
Now ask yourself, “How can I mitigate the risk of security exploit and protect the data and build trust with my users”?
If you liked this article or have any questions, feel free to reach out on Twitter @joshuamdeguzman ✌
Resources
- Flutter Security
- Obfuscating Flutter Code
- ProGuard manual
- Field Experience with Obfuscating Million-User iOS Apps in Large Enterprise Mobile Development
- Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning
- SSL Pinning in Android
- OWASP Top 10 Web Application Security Risk
- Application Hardening for Mobile Banking Apps: Root and Jailbreak Detection