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.

flutter_dotenv | Flutter Package
flutter_dotenv - Easily configure any flutter application with global variables using a `.env` file.

.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.

flutter_secure_storage | Flutter Package
flutter_secure_storage - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.

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.

hive | Dart Package
hive - Lightweight and blazing fast key-value database written in pure Dart. Strongly encrypted using AES-256.
To secure the data of your users, do not store it in plain text.

7. Integrate local authentication

local_auth | Flutter Package
local_auth - Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern.

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.

flutter_jailbreak_detection | Flutter Package
flutter_jailbreak_detection - Flutter jailbreak and root detection plugin. This plugin wraps Rootbeer for use on Android and DTTJailbreakDetection for use on iOS.

Summary

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