The Covid-19 pandemic has changed the way businesses, public services, and users engage with the digital world. Business activities have moved online and added to the volume of transactions being done on mobile apps, especially on Android devices, since it is an extremely popular operating system (OS). This fact has also raised concerns about android app security and made developers and security experts spend more time exploring ways to tackle existing and emerging threats.
Android App Security
Security teams know that user data leakage can prove to be expensive for businesses, as the threat of loss of revenue is very real. At the same time, it compromises brand value and destroys user trust in the app-owning company. This is particularly true of the fintech and similar sectors because the very technology that has made vital services like banking more accessible and affordable to common users opens gateways for malware to intrude into user devices.
Android devices have another issue to manage since a large number of devices that run on this OS do not receive security updates regularly due to various factors. This results in the possibility of data theft increasing manifolds. According to the BBC, two in five Android users worldwide are no longer receiving security updates.
In light of such challenges, developers need to focus on android app security issues more than ever.
Android App Security Best Practices
Improving app security helps build user trust for a business, which goes a long way in establishing device integrity. Below we have compiled a list of best practices that are followed by the developer community and security experts.
Ensuring Secure Communication
The foremost step to protect data on a device is to enforce secure communication. Safeguarding the data exchanged between apps or from apps to websites aids in improving the stability of the app. Here are a few methods of how it can be done:
Use implicit intents and non-exported content providers
Use app chooser
Android OS uses intents to communicate within the system among its various components, like services, content providers, activities, and broadcast receivers. As message objects, intents also transfer data between activities. The app that uses implicit intent does so to invoke another app within Android for performing a specific action which the intent-issuing app cannot or does not do. So, instead of invoking a specific component, the app uses implicit intent to call an action that can be performed by more than one app.
At this juncture, the developer should deploy an app chooser and let the user decide which app to use to perform the action invoked by implicit intent. The user tends to choose the more secure app among the options available on their device.
Invoke an implicit intent the way it is shown below:
Apply for signature-based permissions
This approach is seamless and preferable but works only when two or more apps that exchange data are owned by the same developer group. If the developer can apply the same signing keys to these apps, data can be exchanged among them without the user intervention while the data transfer stays secure. Here is an example of applying for signature-based permission:
Disallow unfettered access to content providers
Android uses content providers to allow data transmission between apps. The content provider of an app presents tables to external apps, which the external apps can use for running their operations. The uses the content provider of an app include sharing access to its app data with other apps, letting a widget fetch data of the app, letting the Android search framework create custom search results in the app using the SearchRecentSuggestionsProvider superclass, sync data with the app server using the AbstractThreadedSyncAdapter class, and loading data in the app UI with the CursorLoader option.
App developers tend to forget securing this communication, especially with external apps that use its content provider to use the app data. The development teams should ensure that the default setting for the ContentProvider object is set to false, so that, unless explicitly specified, no other app can access the data tables of their app. In version 4.1.1 and below, the android:exported attribute of the <provider> element is set to true by default. If the app is targeted at this Android version, the developer must state the android:exported value to false. Consider the example below:
Use credentials for sensitive information
While giving access to premium content or sensitive information on your app, there should either be a biometric credential – such as fingerprint or face recognition – or a PIN or a pattern. The developer should declare at the outset what kinds of authentication methods their app supports. For example, BiometricManager.Authenticators interface can help the developer declare three types of authentication, namely BIOMETRIC_STRONG, BIOMETRIC_WEAK, and DEVICE_CREDENTIAL. The DEVICE_CREDENTIAL type allows the developer to create screen lock credentials, like PIN, password, or pattern. They should use the setAllowedAuthenticators() method to allow one or all of the authentication methods. In the enrollment flow, the app should use the canAuthenticate() method to prompt the user to start the process of setting, say, PIN, or pattern. The app can declare allowed authentication methods in the ACTION_BIOMETRIC_ENROLL intent action. The developer can check which method the user has used to authenticate by calling getAuthenticationType().
Apply network security measures
Connecting Android devices with the internet and other networks is a basic requirement of apps, but it opens up the device and apps to a multitude of vulnerabilities. Weak network security may result in losing confidential information. Therefore, it is essential to ring-fence your app against harmful spyware that may make its way in via the internet. Let us explore the protection methods below:
Use a secure sockets layer
Using a secure sockets layer (SSL), a networking protocol designed for securing the connection between web clients and web servers, is how a developer can go about protecting their network. For instance, your app gets in touch with a web server that bears a certificate issued by an established certificate authority (CA), then the HTTPS request is can look like:
URL url = new URL(“https://www.google.com“);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
Add a network security configuration
But if the app uses custom or relatively unknown CAs, the developer should declare the security system of the network in a configuration file separately without incorporating it in the app code. Below are the steps that should be followed:
1. Declare the configuration in your app:
<manifest … >
<application android:networkSecurityConfig=”@xml/network_security_config” … >
<!– Place child elements of <application> element here. –>
2. Once manifest is declared, add an XML resource file with the path res/xml/network_security_config.xml and disable clear-text to force all network traffic via HTTPS.
It is possible to override network security configuration to allow users to install less secure certificates for the purpose of debugging and testing. The secure way to allow this intended breach without impacting the release configuration is shown below:
<certificates src=”user” />
Create a trust manager
Not all traffic that comes with SSL certificates is safe. The app should be made to trust certificates issued by well-known authorities via a trust manager, which should handle SSL warnings as and when it detects suspicious activities. Such warnings may occur if:
- The app pings a server that has a certificate signed by a custom certificate or new CA which is not included in popular online repositories of CAs.
- The device user distrusts and blocks a CA.
- The developer is unable to use a network security configuration.
The trust manager should be configured to cause the SSLHandshakeException if these warnings are detected by the app. Often, a private CA can also cause this exception. Private CAs are not always untrustworthy, as they include government departments, corporations, non-profits, educational institutions, etc. The best way to deal with these CAs is initialize TrustManager. This process involves creating a KeyStore using the targeted CA from an InputStream and invoking the TrustManager. The TrustManager tallies CA from the web server and the KeyStore to allow secure traffic. The particular TrustManager will only store one or more CAs meant for the traffic between the targeted app and the relevant web server. A self-signed server certificate can also be accommodated in a custom TrustManager in a similar way.
Consider the following example which uses a certificate from the University of Washington:
Use WebView objects with discretion
Android WebView class allows the developer to embed web pages inside their apps, give them control over the user interface, and let them restrict the web content that can be called within an app. Typically, it has less features than a fully functional mobile browser, like Chrome or Firefox, but its popularity lies in the control it allows the developer, which enhances app security. The developer can restrict web requests a WebView object can call and disallow every other type of web traffic. Allowing only listed content in WebView increases the app’s security.
Security through Appropriate Permissions
Not giving appropriate permissions or giving more permissions than needed changes the way an app behaves in an Android device. While the former can impact its performance, the latter can compromise its security. The golden rule, then, is to ensure that an app seeks only a minimum number of permissions required for its smooth functioning. With frequent usage and roll-out of newer versions, the need for certain permissions may become unnecessary. The developer should ensure that permissions are readily surrendered when required.
Android pre-defines permissions for internal tasks, like network access, notifications, etc. that apps must request at an appropriate time. The time of allowing such requests has changed from the earlier installation time to the current runtime, which occurred with Android Marshmallow onward. The concept of user and group IDs for creating permissions – which is common to Linux systems – ensures that running applications communicate with each other or the system tasks only after appropriate permission structure is created.
Android defines two types of permissions: Normal and dangerous. In the former category, the system assumes that the user data will not be compromised. Therefore, they are generated automatically for the app. These permissions allow the app to access data and resources outside the app but are not likely to easily disturb the user’s privacy (however, malware can exploit even normal permissions; therefore, the developer should not assume that they are free of exploitation). If the app manifest seeks a normal permission, for example, the ability to set the time zone, Android grants it automatically during the app installation process without seeking the user’s approval. At the same time, the user does not have the option of revoking normal permissions.
On the other hand, dangerous permissions include permissions where the app seeks access to user data or system resources, for example list of stored contacts, which have a direct impact on user privacy. Android does not grant a dangerous permission to an app unless the user of the app specifically permits it at runtime. The developer, therefore, must prompt the user at runtime to seek a dangerous permission, and, unless it is granted, that feature cannot be available to the app.
- Intents for deferred permissions
Another security feature that the developer must use is deferred permissions. If there already exist apps that can complete a set of tasks, then the developer should not seek to complete those tasks within their app. Instead, they should use one of these apps to complete the tasks using intents, which allow deferring of requests to apps with appropriate permissions. For example, an app can direct the user to the contacts app rather than seeking READ_CONTACTS and WRITE_CONTACTS permissions. Consider the following code for completing a task using a contacts app:
- Data sharing permissions
The app ecosystem in Android works efficiently when apps build on each other’s functionalities to give the user a seamless experience. But, sharing data is also fraught with privacy and security dangers. A sound practice for sharing data across apps and with system processes is to set the correct permissions. The developer should choose between read-only or write-only permissions as per the needs. They should adopt the URI permission method to grant single-use permissions to clients. Using the FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION flags helps protect data. The most secure way to grant content permissions is to use content:// URI instead of a file:// URI. This is implemented through the FileProvider sub-class of the ContentProvider class. The advantage of using content:// URI is that when an intent is written to share data with a client app, the provider app can call Intent.setFlags() to set temporary read and/or write permissions, something that file:// URI does not allow since it depends on the system permissions of the relevant file. Thus, the content:// URI offers a better permission granularity.
An easy way to understand the logic and need of setting temporary permissions is to consider the example of an email app. A PDF reader may not have permission to read content of the email app but when the user wants to open attachments, it may be required. Therefore, an overarching, system-level permission cannot be set for the PDF reader app to access the content of the email app. Perforce, it has to be a temporary permission, which nullifies the values set in the readPermission, writePermission, permission, and exported attributes.
Storing Data Safely
App users are increasingly becoming aware of protecting their privacy. Data breaches are all around us, and users prefer apps that offer to protect their data and user credentials. The developer can use Android’s following suggestions for ensuring that their app offers the best possible privacy protection:
Private data should be stored within the device’s internal storage for the app, as the operating system allows sandboxing per app. This prevents other apps from reading user data without the host app allowing explicit permission, while the rights of the host app to access this stays seamless. This also means that the user can easily remove this data from the device at the time of uninstalling the app.
For extra security, the developer should use Android’s Security library, which allows more fine-grained security permissions than the File objects. It makes EncryptedFile objects available to the app, which inturn uses FileInputStream and FileOutputStream to allow secure streaming read/write operations. The Android Security library is based on Google’s open source Tink crypto library. This library allows two-part encryption key management within Android. The first part involves creating a keyset that contains one or more keys to encrypt targeted files or shared data. The second part contains a primary/master key that encrypts keysets and is stored in the Android keystore system.
The following code explains how to use EncryptedFile to read contents of a file securely:
Similarly, the following code example can be used to understand EncryptedFile to write a file:
- An app should use external storage to store and share large files that are not essential to its functioning and may not store the private information of the user. Typically, they include large media files that need to be shared across apps. The other category of such files includes app-specific files that may not be required by other apps but still do not contain the private information of the user. Such files should be stored in external storage on an app-specific dedicated folder. For shared files, the developer can use either the Media Store API for storing shareable media files or the Storage Access Framework for storing non-media files, including the downloaded ones.
The developer should use external storage strictly for using non-essential files since many devices allow the removal of external storage at the will of the user. The files which are essential to the app’s functioning should always be stored in internal storage, preferences, and/or databases. External storage, like SD cards, are typically made available at /sdcard location within an Android device, which may not always be readable. However, the new generation of devices come with a large internal storage volume, and, therefore, may allow apps to store even large files in internal storage. With time, the use of external storage may become minimal and allow apps to protect even larger files with better permissions. However, as long as the data is stored in external storage, it needs to be verified against corruption and modification. The developer can consider the following example of hash verifier to accomplish this task:
- An app can also cache a part of its data that is non-sensitive in nature. Just like other data, cached data can also be stored in internal as well as external storage depending on the size of the file which can be called in the code using File objects. The developer should be careful not to leave sensitive information in cached objects.
Updating Third-Party Libraries and Services
Unsecure or old libraries are common leakage points in apps. Hackers are on the lookout for compromised libraries used in apps. It is usual for developers to use third-party APIs to complete various functions within apps. Most of these libraries exist on open platforms and are under the gaze of both developer and hacker communities. Bugs are immediately reported and fixed. If the developer uses a library in their app but does not provide for its constant updates, the app comes under severe risk. Therefore, it is advisable that all dependencies, including Google Play services, are kept up to date. In fact, the app should trigger an authorization error.
Android banks on the Provider class to ensure network security. It represents a provider of the Java Security API which implements Java Security protocols. Whenever a vulnerability is found in this API, Google Play services rush to provide updates to plug them. Patches thus issued by Google Play services need to be integrated into the app before the known exploits can be effectively rendered useless. The developer can use the ProviderInstaller class to update the Google Play services library.
To patch services library synchronously using a sync adapter, the developer can use the following code:
App Security Testing Tools
Designing and developing an app takes most of the developer’s time. At times, security takes a backseat. Below, we discuss ways in which we can ring-fence apps from malicious malware by using popular security testing tools:
Androguard is a Python-based reverse-engineering tool. This testing tool can diagnose the app for the presence of malware. Some of the useful features include checking the “diff” of two Android Package Kit (APK) files, measuring the efficiency of various obfuscators (such as ProGuard and DexGuard), and checking if the app under test has been illegally modified or tampered with.
ApkTool is another reverse-engineering tool for Android which can enable developers to open any closed third-party Android app and convert it to its near-original form. With the help of Apk Tool, developers can not only analyze an application but also rebuild it with custom modifications. Developers tend to prefer this tool as it also streamlines and removes the redundant actions that are involved in the reverse-engineering process.
It statically analyzes the code binaries for potential security flaws and vulnerabilities in an app. Appknox is known for using ethical hackers who manually test apps to find deep security loopholes, sensitive data leakages, or any other potential risk factors that may render the apps vulnerable to malware.
It helps proxy and debug all sorts of HTTP-related items in the app. It is not specific to Android and can be used in other operating systems, such as iOS, as well. The tool inspects and changes if required, the way the app interacts with a network.
ClassyShark is a binary-inspection tool for Android. A user can browse all the classes, members, and dependencies and check the method count of any app with ease. It also has a convenient package-like file structure for easy browsing. And the “Methods Count” tab tells the number of methods each package is contributing to the app.
This tool assists a developer in improving the security of code. It auto-corrects several known security issues directly in the IDE. DevKnox works well in Android Studio, giving users real-time security suggestions and one-click fixes to improve productivity.
Dex2Jar is used to read and write DEX files and translate or modify them. Besides, users can convert all your DEX files to class files and get them all zipped as a Java Archive (JAR) file format.
QARK stands for Quick Android Review Kit and is another useful security tool. It helps find common security vulnerabilities not only in the source code but also in the packaged APK of the app. It provides users with lots of in-depth explanations for the vulnerabilities.
Dealing with an App Flagged for Security Reasons
Android assumes that developers want to give the most secure experience to their users and follow best practices in app security before submitting them on the Google Play platform. This platform boasts of around 2.8 million apps, which are part of a USD 189 billion app ecosystem. It is no mean task to ensure the security of user data and codes on an infrastructure of this magnitude. To automate a large part of this aspect, Android launched the App Security Improvement Program in 2014, which, according to Google, has updated the security of around one million apps developed by around 300,000 developers.
App Security Improvement Program
Under this program, Google offers a service to developers to update the security of their apps after scanning them thoroughly after they are uploaded to the Google Play platform. Scanning apps for emerging and existing threats is a continuous process. This service offers recommendations to developers about how they can improve security.
As soon as Android detects a potential security issue, it flags the app and issues a notification to the developer through email and Google Play Console. The notifications contain suggested solutions and tips and links on how to implement the patches if required. It ensures that all updates are applied in a time-bound manner. If the security loophole is critical, Google Play may not allow you to upload updated versions of an app unless the loophole is plugged with best practices.
The developer is encouraged to use community resources on the Google Play platform where other developers identify attacks and vulnerabilities and offer potential solutions.
When a user chooses to use a developer’s app, they put their trust in the app and feed their data. It is every developer’s foremost responsibility to do their best to safeguard customer data.
Android is one of the most-used mobile operating systems and hence is also the center of attention for hackers and malicious programmers. By ensuring secure network connections and the use of testing tools listed above, a developer can vastly minimize the risk posed to applications.