How to implement Secure Storage in Flutter?
This step-by-step guide outlines best practices for implementing secure data storage in Flutter applications, providing instructions for integration on both iOS and Android.
Last updated
This step-by-step guide outlines best practices for implementing secure data storage in Flutter applications, providing instructions for integration on both iOS and Android.
Last updated
Imagine you are developing a sales application for a local store. To expedite the purchasing process, the company decides to store users’ credit card data for future transactions. This data will be stored locally on the users’ devices. The crucial question that arises is: how and where to securely store this sensitive data? The answer to this question is what has led us to write this article.
To illustrate the importance of proper storage practices, let’s consider that you developed the feature without adhering to best practices, which led to the leakage of your customers’ credit card data, resulting in the loss of their trust and some lawsuits. Although this is a undesirable scenario, it could become a reality.
In this context, we will explore security concepts related to data storage and present the best practices for managing each available storage method, ensuring that users’ information remains protected against potential threats and vulnerabilities.
What defines secure data? Basically, it is information protected against unauthorized access, theft, corruption, and destruction outside the application’s expected lifecycle.
Although this definition brings with it a huge challenge, this subject has been debated for a long time in computing, and thanks to that, we currently have some concepts that help us better understand how to achieve this security. For this article, I would like to focus on three.
First, we have encryption, which transforms readable information into protected formats, ensuring that only authorized recipients can access and understand the data. Next, we have access controls, which allow applications to manage who is authorized to access the data and how this will occur. This control can be implemented through various components, such as: authentication, authorization, networking, management, and auditing. Finally, there are hardware-based security solutions, which use physical technologies to protect against attacks. This is possible thanks to execution in an isolated hardware environment, which takes place in a secure area within the main processor. A similar process can be done with the Trusted Execution Environment (TEE), or in a dedicated coprocessor, as happens with Apple’s devices that have the so-called Secure Enclave. These concepts will be further explored throughout the article.
Now that we understand what makes data insecure as well as some of the concepts behind data security, let’s take a look at the methods offered to developers that, by default, have a lower level of security and can be considered insecure.
In both operating systems, Android and iOS, applications can create and store files in the internal file system, even though this access is not exposed to the end user in the same way as in desktop systems. Each application has a dedicated storage space, which provides a certain level of isolation between the data of different applications.
The data best suited for this type of storage includes documents, images, and other larger files. For example, video streaming applications often offer the option to download content so that the user can watch offline. These would be excellent candidates for utilizing this storage method.
Another method for storing small amounts of data is by using UserDefaults on iOS and SharedPreferences on Android. Both were developed to store simple data using key-value pairs, consisting of a key of type String and a value limited to the types supported by each platform.
These systems are ideal for saving user preferences, application settings, and other non-sensitive data that needs to persist between sessions. For example, it is common to store the theme selected by the user, the last accessed tab, or configuration options.
In the previously presented storage methods, data is not encrypted by default. This makes it easier to access information on devices with root or jailbreak access, increasing the risk of exposing users’ sensitive data. On non-rooted or non-jailbroken devices, access to the file system is restricted to protect sensitive system files and application data for security purposes. Root or jailbreak permissions allow us to bypass these restrictions and gain full access to these files.
To illustrate the vulnerability of applications to modified devices, I will extract and modify content within a counting application developed with Flutter. In this example, the value of the last count is stored in a text file. It’s important to note that this lack of security is not specific to Flutter — the same issue would occur if the application were developed natively for Android.
The following example was adapted directly from Flutter’s official documentation on how to write and read files.
For our experiment, it will be necessary to use an emulator with root permissions and the Total Commander application — This will help us access the Android file system. However, it’s worth mentioning that Total Commander is just one of many tools that can be used for this purpose.
The first step is to access our application and add some numbers to the counter. In this way, the application will write and store the count value in a text file.
Next, we will open Total Commander. We will navigate to the data/data/ directory and access the folder named Installed apps. After that, simply locate the counting application we developed.
With that, we will have access to the internal files of our application. To view the created text file, just navigate to app_flutter/counter.txt.
In this way, we can view all the content stored in this file and, in addition, we have the possibility to modify the value as desired. If we change the value, it will be necessary to save the file and reopen the counting application. After this procedure, the modified value will be displayed in the application.
Although we have already discussed data insecurity at length, we have also covered essential fundamentals such as encryption, access control, and hardware-based security solutions. In this section, we will explore storage methods that use these fundamentals to ensure greater data security. However, before we move forward with the content, it will be important to review in more detail the concept of hardware-based security, as this will be crucial to understanding what happens under the hood.
For software to maintain its security, it must rely on hardware that is designed with robust security features. For this reason, Both Android and iOS devices have their own hardware-based security technologies.
In most cases, Android and iOS implement different Hardware-based security solutions. However, they always follow the same principle: creating an environment isolated from the operating system that is secure for performing cryptographic operations. This ensures that even if the main processor is compromised, critical information, such as cryptographic keys and authentication data, like biometric data, remains protected.
On Android, the primary mechanism is the Trusted Execution Environment (TEE), an isolated area within the main processor. However, more recent models are also integrating the Secure Element (SE), a chip separate from the processor and specifically designed for enhanced security.
On iOS, although the Secure Element exists, it is more related to Apple Pay technologies. The primary focus for developers is the Secure Enclave. This hardware component is integrated into the main processor and acts as a coprocessor exclusively focused on security.
I know, all this talk about hardware may seem distant from our reality as app developers. However, have you ever heard of Android's KeyStore or Apple's Keychain?
Tools like the Android's KeyStore and the Apple's Keychain bridge the gap between complex security-dedicated hardware and everyday development. However, these tools differ in how they operate.
The KeyStore present on Android devices is responsible for ensuring the security of the cryptographic keys used. It also allows developers to restrict when and how the keys can be used by applying access control concepts, such as requiring authentication before the data is utilized. As mentioned, the KeyStore system integrates directly with security hardware components to ensure that cryptographic keys are generated, stored, and handled in a secure and isolated environment.
While the KeyStore was designed to store cryptographic keys used by applications, Apple's Keychain is more comprehensive, also handling passwords and other small but sensitive data, such as keys and login tokens. Due to this wider scope, the requirements—and consequently the implementation—differ. The Keychain is implemented using an SQLite database stored in the file system. The security of the stored data is guaranteed by encryption, whose key is stored in the Secure Enclave, ensuring protection through both encryption and dedicated hardware. Additionally, access control policies can be applied to the data, further reinforcing security.
After all this overview on data security in mobile applications, it’s time to show in practice how we can improve the security of the data we store.
Remember the counting app we highlighted at the beginning of the article? Using this, we will demonstrate a step-by-step guide on how to apply the concepts we presented throughout the article.
As mentioned, encrypting is transforming readable information into protected formats. But how does this actually happen? The explanation depends on the type of cryptography: it can be symmetric or asymmetric. In the tutorial below I will only be focusing on symmetric algorithms, if you are curious about asymmetric algorithms feel free to do your own research.
Symmetric encryption algorithms use only one key to encrypt and decrypt data. Their two main advantages are speed and efficiency, which makes them very advantageous for large volumes of data. One of the most well-known symmetric encryption algorithms on the market is AES, and it is precisely the one we will use in our tutorial.
Our goal, therefore, will be to encrypt the file in which we store the information from the counting app. For this, we will use a package known by the Flutter community, encrypt. It will provide us with the necessary functions to use the AES algorithm.
To get started, we will import the encrypt package.
Then, we will add a key property to the CounterStorage
class; this key will be used to encrypt and decrypt our data.
And to generate the key, we can do this using the functions from the encrypt
package.
Next, we will create the encryptData function.
And finally, the decryptData
function.
Now that we have functions capable of encrypting and decrypting data, the following question arises: if the keys in symmetric encryption are unique, and only the key used for the encryption process is capable of decrypting, how will we store the key in a secure way?
One effective solution is to utilize hardware-based security mechanisms, such as Keychain and Android. They will help us store the cryptographic keys we generate for encrypting our files in a secure location. To integrate our counting app with Keychain or KeyStore, we will need a package capable of bridging with these native operating system technologies, and for this task, we chose flutter_secure_storage.
Returning to the application we are developing, we will add a new property to the CounterStorage
class.
In addition, we will add one more function: this will be responsible for retrieving from the Keychain or Keystore the key used for encryption, if it exists.
In this way, we have implemented a secure data storage solution that ensures the protection of the stored data. The complete code is as follows:
To boost the security of your applications even more, we have decided to introduce another concept in this article: RASP (Runtime Application Self-Protection). This technology helps us create security strategies based on the context in which the application is running. Through various real-time security checks, RASP can identify changes in the devices used to run the application or modifications to the application itself.
To implement RASP in Flutter applications, we recommend using freeRASP, as it offers a comprehensive range of security checks, including:
Detection of VPN usage;
Identification of developer mode enabled on the device;
Checking for special permissions, such as root or jailbreak;
Detection of execution on simulators;
Verification of installations from unofficial app stores, ensuring the application's legitimate origin;
Additionally, when any security violation is detected, freeRASP allows you to implement countermeasures through a fallback mechanism, ensuring your application responds appropriately to potential threats. It also offers the option to receive weekly security reports via email, providing valuable insights.
In the counter app example, freeRASP could be used to delete any data stored in the application or even close the app upon identifying whether the user's device has root or jailbreak. This would help prevent unauthorized access to the data and ensure that the application runs only on secure devices.
If you are interested in freeRASP and want to learn how to implement it in your applications, we recommend taking a look at the official website of Talsec, the company's owner of this project.
Sensitive data is present in all applications, whether it's credit card information, user tokens, or other types of confidential information. It is important to emphasize that security is an ongoing process. In this article, we explored some security concepts, such as Cryptography, Access Control, Hardware-Based Security, and RASP, and how they assist us on the journey to make our application environments increasingly secure.
As developers, we must always prioritize best practices in data security and stay attentive to the latest updates of our environment and possible vulnerabilities, continuously strengthening the strategies we adopt in our applications.
We encourage you to implement the strategies discussed in this article and to continue delving into topics related to data storage insecurity. If you have comments about this article, we would be happy to receive an email from you.
A Brazilian Software Engineer specializing in native Apple technologies and Flutter development. Skilled in project leadership, open-source contributions, and developer mentorship. and