Authenticate app data in Flutter
Security! one of the most important and game changing topic in the world of software development. In the world of mobility, apps like Youtube, Spotify, Netflix, Uber, WhatsApp are some of the most popular in the market. They are used by millions of people. These apps have gained popularity not just because their UI is pleasing or the content is great. They are popular because of their reliability. These apps are secure enough to bring that trust factor among their customers. To achieve both reliability and trust you need to work on the security part of your software. So today we will be learning a great security trick that will avoid hackers from hacking your mobile app by manipulating your app data.
Type of Attack
Being a mobile developer you must have heard about app data. If not no problem, let me explain you. All apps (root or not) have a default data directory, which is /data/data/<package_name>
. By default, the apps databases, settings, and all other data go here. If an app expects huge amounts of data to be stored, or for other reasons wants to "be nice to internal storage", there's a corresponding directory on the SDCard (Android/data/<package_name>
). Now you know where data is stored, hackers can easily reach those locations and can manipulate any app’s data if not securely stored. If data of an app is altered in the right way, hackers can easily gain access to some premium content(whatever their intention) without spending a dollar. There is an open source tool in xda-developers forum to play around with this type of hacking .
Type of Solution
There are two type of solution coming in my head which can avoid such type of hacking:
- Don’t store anything on the user’s mobile 😅.
- If you planning to store something. You encrypt the content and store.
Looking at the first solution, it doesn’t work out every time. You don’t want users to be online every time they use your apps. You always need to store some data for offline access. One great example of such apps are mobile games. They do a one time download and later use internet for updating or syncing user’s data. Now the second solution looks great to me, its the most common solution every developer will think. You encrypt the data and store it.
Problem with Encryption
As per my development experience not every thing is encrypted and stored(except user’s personal data). What if you have to download a configuration file(xml or json) and store it when your app is first launched(or download when there is an update from the server) and use it to control some of your business logic e.g changing app’s language as per availability or checking the access limit the user can have for your app’s content.
What will you do if you fall in such a situation? Don’t worry there is a solution for this problem. And the solution is:
Authenticate using HMAC
I know some of the common questions which is running in your head after seeing the title. No problem it will be all clear by the end of this article. Let me tell you in short and simple words what HMAC is and why we are using it. HMAC( hash-based message authentication code ) is an algorithm used to verify (authenticate) that the data has not been altered or replaced. The word data means it can be an API call or content of a file. We will be using this cryptographic algorithm to authenticate the content of the files we have stored in the user’s device. This will help us check if the data in the file is altered manually by a hacker or not. If the authentication fails then user is trying to hack by changing the data in the file.
How HMAC works?
Let’s understand this algorithm through a metaphor. You are going to mail a package to Sarah which contains a photograph. You expect her to open the package and view the photograph. At some point in the near future you expect her to send you back the package with that photograph in it. It’s vital that she put the same photograph back in the package. You need to be absolutely sure she doesn’t send you back an altered photograph even a little bit, or replace it with a different one. You’ve got hundreds of these packages going out daily with different photos; you’d never remember the photo in such detail that you could tell if she changed a small bit of it (like if she airbrushed a small zit off her face).
Here’s what you can do: Before you send her the package, place another copy of the photograph inside a small locked box. Keep the key. Place the small locked box inside the package along with the original photograph you are mailing her. Assume she knows she is not to remove the locked box from the package. When you receive the package back from her, open it, place the photo on the table. Open the locked box, remove the copy, compare the two. If they are the same, then she has not altered the photograph (it is “authentic”). If the locked box is not in the package or your key will not open it, then assume she has done something nefarious and throw the whole package in the trash. The beauty here is that you don’t need to “remember” anything about what you originally sent her; everything you need to ensure legitimacy of the photo comes back inside the package.
In the example above, the small locked box represents an HMAC. Your key is the HMAC’s key. The photograph is the data you are applying the HMAC to. Source : StackExchange
Benefits of using this solution
- You don’t have to encrypt the file.
- You don’t need to copy all the content to an encrypted storage.
- You don’t need to write any complex algorithm to check the authenticity of the file.
- It’s faster to compute and the digest size is small i.e 128bit. See this.
Plan of Attack
Our plan is to use HMAC algorithm to generate a digest or a hash code of the content of the file which is stored on the user’s device at the beginning. The digest generated and the secret key used to generate it will be stored in a database. Later we will be using both(digest and secret key) for the authentication of the local file’s content.
To simulate the above situation. I will be making an app which will first create the file in an external storage and write some data to the file, while in that process I will generate a digest using the HMAC algorithm and will use that digest later to authenticate the content of the file. If the data is altered outside the app(manually changing the data) then the authentication fails. Here is a small video of the app:
In the above video I am storing the user’s current level in a file. When I click the Save button I am generating a digest or hash code using the HMAC algorithm and when I click the Verify button I am reading the content from the stored file and generating a new digest or hash code. Then I comparing the new digest with the old digest to check if both are same or not. If both are not same than someone has altered the data manually in the file and will show a message to the user that you trying to cheat.
Let’s Code
Let me walk you through the creation of the above app.
- Create a new flutter project.
- Remove all the code from the
main.dart
file and paste the below code:
import 'package:flutter/material.dart';
import 'src/app.dart';
void main() => runApp(MyApp());
-
Create a new package under
lib
directory and name it assrc
. -
Inside src directory create a new file and name it as
app.dart
. Below is the code for app.dart file.
import 'package:flutter/material.dart';
import 'secure.dart';
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Secure"),
),
body: SecureScreen(),
),
);
}
}
- Now we need to add few dependencies in our
pubspec.yaml
file.
dependencies: flutter: sdk: flutter
# The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2
crypto: ^2.0.6
path_provider: ^0.4.1
simple_permissions: ^0.1.9
crypto
library is the main heart of our app. It holds the HMAC algorithm. path_provider
will help use to write a file in external storage. simple_permissions
will help to deal with the runtime permissions.
- Currently I am handling permissions for android. So for that you just need to edit the
AndroidManifest.xml
file inside the android directory. Add below permission in theAndroidManifest
file.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- Last and final step of our app. Create a new file under the src directory and name it as
secure.dart
file. Copy paste the below code.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';
import 'package:simple_permissions/simple_permissions.dart';
class SecureScreen extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return SecureState();
}
}
class SecureState extends State<SecureScreen> {
String _content;
Digest _fileDigest;
@override
void initState() {
super.initState();
checkPermission().then((bool value) {
if (!value) {
requestPermission();
}
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[inputField(), writeButton(), verifyButton()],
),
);
}
inputField() {
return TextField(
decoration: InputDecoration(
labelText: "Sensitive Data", hintText: "Sensitive Data"),
onSubmitted: (value) {
_content = value;
},
onChanged: (newValue) {
print(newValue);
},
);
}
verifyButton() {
return RaisedButton(
child: Text("Verify"),
onPressed: () {
readCounter().then((String value) {
Digest digest = hmac(value); //The final digest which will be used for verification if (digest.toString() == _fileDigest.toString()) {
//Content is same snackbar('Data is correct.');
} else {
//Someone changed the content snackbar('You trying to cheat.');
}
});
});
}
void snackbar(String msg) {
final snackBar = SnackBar(
content: Text(msg),
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
// Some code to undo the change! },
),
);
// Find the Scaffold in the Widget tree and use it to show a SnackBar! Scaffold.of(context).showSnackBar(snackBar);
}
writeButton() {
return RaisedButton(
child: Text("Save"),
onPressed: () {
print("Write Button $_content");
String body = """ {"user_details":{"name":"Sagar","age": "25""email":"sagarsuri56@gmail.com","current_level": $_content}} """;
_content = body;
writeCounter(_content);
Digest digest = hmac(_content); //The final digest which will be used for verification _fileDigest = digest;
});
}
Digest hmac(String data) {
var key =
utf8.encode("p@ssw0rd"); //Secret key used for authentication var bytes = utf8.encode(data);
var hmacSha256 = new Hmac(sha256, key);
Digest digest = hmacSha256.convert(
bytes); //The final digest which will be used for verification return digest;
}
}
Future<String> get _localPath async {
final directory = await getExternalStorageDirectory();
print("directory ${directory.path}");
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/config.txt');
}
writeCounter(String content) async {
final file = await _localFile;
// Write the file await file.writeAsString('$content');
}
Future<String> readCounter() async {
try {
final file = await _localFile;
// Read the file String contents = await file.readAsString();
return contents;
} catch (e) {
// If we encounter an error, return empty string return "";
}
}
Future<bool> checkPermission() async {
bool res =
await SimplePermissions.checkPermission(Permission.WriteExternalStorage);
return res;
}
requestPermission() async {
await SimplePermissions.requestPermission(
Permission.WriteExternalStorage);
}
The above code is really very simple. I have created 3 widgets in a Column. The first widget is a TextField
which will take input from the user. In this demo I am writing a JSON
to the file. The text user enter will be the value of the current_level
key of the JSON
. Below the TextField
there are two RaisedButtons
. Save button will write the content to the file and generate the digest or hash code of the content and will save it in a variable(I am not implementing the complete db flow). Verify button will read the content from the file and generate a new hash code or digest. Will then verify if the current digest matches will the old digest or not. If it doesn’t match your app is “hacked”. After that you can do whatever you want. Block the user or download a new file from the server. Choice is yours 😃.
What’s next?
You can try implementing this concept with a complete flow i.e with storing the digest and secret key in a database for further authentication. You can even use this trick for authenticating API calls or any type of messages coming from external source.
I would love to hear your opinion regarding this approach and your solution for this kind of problem.
Here we hit the end for this article. Hope you like the content.Do appreciate my work with loud claps( 50 is max 😅). If you have any doubt or need any clarification regarding Flutter app development. Connect with me at LinkedIn.
The Flutter Pub is a medium publication to bring you the latest and amazing resources such as articles, videos, codes, podcasts etc. about this great technology to teach you how to build beautiful apps with it. You can find us on Facebook, Twitter, and Medium or learn more about us here. We’d love to connect! And if you are a writer interested in writing for us, then you can do so through these guidelines.