Introduction
The Android operating system has lots of built-in security features, such as application sandboxing, protection against buffer and integer overflow attacks, and segregated memory areas for program instructions and data. As a result, simple Android apps that don't perform any file system or networking operations can often be considered secure by default.
If you are developing a more complex app, however, it is your responsibility to make it secure and protect the privacy of your users. In this article, I'm going to list some of the best practices you can follow to build a secure Android app that doesn't leak data or permissions, and is, in general, less vulnerable to malicious apps that might be installed on the user's device.
1. Use Internal Storage for Sensitive Data
Every Android app has an internal storage directory associated with it whose path is based on the package name of the app. Files inside this directory are very secure because they use the MODE_PRIVATE
file creation mode by default. This means the files cannot be accessed by any other app on the device. Therefore, it is a best place to store all the sensitive data of your app in the internal storage directory.
To determine the absolute path of your app's internal storage directory, it is recommended that you use the getFilesDir()
method. Once you know its path, referencing files inside it is as simple as referencing files inside any other directory. For example, here's how you could reference a file called myfile.dat in the internal storage directory of your app:
File myFile = new File(getFilesDir(), "myfile.dat");
2. Encrypt Data on External Storage
The internal storage capacity of an Android device is often limited. Therefore, at times, you might have no choice but to store sensitive data on external storage media, such as a removable SD card.
Because data on external storage media can be directly accessed by both users and other apps on the device, it is important that you store it in an encrypted format. One of the most popular encryption algorithms used by developers today is AES, short for Advanced Encryption Standard, with a key size of 256 bits.
Writing code to encrypt and decrypt your app's data using the javax.crypto
package, which is included in the Android SDK, can be confusing. Therefore, most developers prefer using third party libraries, such as Facebook's Conceal library, which are usually much easier to work with.
3. Use Intents for IPC
Experienced programmers who are new to Android application development often try to use sockets, named pipes, or shared files to asynchronously communicate with other apps installed on an Android device. These approaches are not only hard and inelegant, but also prone to threats. An easier and more secure approach to interprocess communication on the Android operating system is to use intents.
To send data to a specific component of an app, you must create a new instance of the Intent
class and use its setComponent()
method to specify both the package name of the app and the name of the component. You can then add data to it using the putExtra()
method.
For example, here's how you could send the string Hello World to an Activity
called MyActivity, which belongs to an app whose package name is my.other.app:
// Create an intent Intent intent = new Intent(); // Specify the component name intent.setComponent( new ComponentName("my.other.app","my.other.app.MyActivity") ); // Add data intent.putExtra("DATA", "Hello World!"); // Send the intent to the activity startActivity(intent);
To send data to multiple apps at once, you can send the intent as a broadcast using the sendBroadcast()
method. However, by default, a broadcast can be read by any app that has an appropriately configured BroadcastReceiver
.
Therefore, if you want to send sensitive information as a broadcast, you must use a custom permission whose protectionLevel
is set to signature
. By doing so, the Android operating system makes sure that only apps that were signed using your signing key can receive the broadcast.
Here's a code snippet that shows you how to send the string Hello World as a secure broadcast:
// Create an intent Intent intent = new Intent(); // Add data intent.putExtra("DATA", "Hello World"); // Specify an action name for // the receiver's intent-filter intent.setAction("my.app.receive"); // Send as a broadcast using a custom permission sendBroadcast(intent, "my.custom.permission");
Note that the above code works as expected only if the custom permission is declared and used in the manifest files of both the sender and receiver apps.
<permission android:name="my.custom.permission" android:protectionLevel="signature"/> <uses-permission android:name="my.custom.permission"/>
4. Use HTTPS
All communications between your app and your servers must be over an HTTPS connection, preferably using the HttpsURLConnection
class. If you think using HTTP for data that is not confidential is fine, think again.
Many Android users connect to several open Wi-Fi hotspots in public areas every day. Some of those hotspots could be malicious. A malicious hotspot can easily alter the contents of HTTP traffic to make your app behave in an unexpected manner, or worse still, inject ads or exploits into it.
By using HTTPS, as long as the server is configured with a certificate issued by a trusted certificate authority, such as DigiCert or GlobalSign, you can be sure that your network traffic is secure against both eavesdropping and man-in-the-middle attacks.
If your app has a lot of networking code and you are afraid that you might unwittingly be sending some data as cleartext, you should consider using nogotofail, an open source tool built by Google to find such mistakes.
5. Use GCM Instead of SMS
Back when GCM, short for Google Cloud Messaging, didn't exist, many developers were using SMS to push data from their servers to their apps. Today, this practice is largely gone.
If you are one of those developers who still hasn't made the switch from SMS to GCM, you must know that the SMS protocol is neither encrypted nor safe against spoofing attacks. What's more, an SMS can be read by any app on the user's device that has the READ_SMS
permission.
GCM is a lot more secure and is the preferred way to push messages to an app because all GCM communications are encrypted. They are authenticated using regularly refreshed registration tokens on the client side and a unique API key on the server side. To learn more about GCM, you can refer to this tutorial about push notifications.
6. Avoid Asking for Personal Data
User privacy is given a lot of importance these days. In fact, there are laws, such as the European Union's Data Protection Directive and Canada's Personal Information Protection and Electronic Documents Act, which mandate the protection of the privacy of a user. Therefore, unless you have a good reason and a very secure infrastructure to collect, store, and transmit personal user information, you must avoid directly asking for it in your apps.
A better approach to user authentication and user profile information look up on Android is through the Google Identity Platform. Google Identity Platform allows users to quickly sign in to your app using their Google account. After a successful sign in through the platform, whenever necessary, your app can easily look up various details about the user, such as the user's name, email address, profile photo, contacts, and more. Alternatively, you could use free services like Firebase that can manage user authentication for you.
If you must handle user credentials yourself, it is recommended that you store and transmit them in the form of secure hashes. The most straightforward way to generate different types of hashes using the Android SDK is by using the MessageDigest
class.
Here's a little code snippet that shows you how to create a hash of the string Hello World using the SHA-256 hashing function:
// Initialize MessageDigest to use SHA-256 MessageDigest md = MessageDigest.getInstance("SHA-256"); // Convert the string to a hash byte[] sha256Hash = md.digest("Hello World".getBytes());
7. Validate User Input
On Android, invalid user input doesn't usually lead to security issues like buffer overruns. However, if you allow users to interact with a SQLite database or a content provider that internally uses a SQLite database, you must either rigorously sanitize user input or make use of parameterized queries. Failing to do so makes your data vulnerable to SQL injection attacks.
On a similar note, user input validation and sanitization is also very important if you are using user input to dynamically generate code to run on an embedded scripting engine, such as Mozilla Rhino.
8. Use ProGuard Before Publishing
Security measures built into an Android app can be severely compromised if attackers are able to get their hands on the source code. Before you publish your app, it is recommended to make use of a tool called ProGuard, which is included in the Android SDK, to obfuscate and minify source code.
Android Studio automatically includes ProGuard in the build process if the buildType
is set to release
. The default ProGuard configuration available in the Android SDK's proguard-android.txt file is sufficient for most apps. If you want to add custom rules to the configuration, you can do so inside a file named proguard-rules.pro, which is a part of every Android Studio project.
Conclusion
I hope you now have a better understanding of how to make your Android apps secure. Most of the best practices I mentioned in this article are applicable only if you are using the Android SDK to develop your apps. If you are using the Android NDK instead, you have to be a lot more careful because, while programming in the C language, you are expected to manage low-level details, such as pointers and memory allocation yourself.
To learn more about security on Android, you can refer to the AOSP security documents.
Comments