Security Pipeline for Android Development 🔐
Mobile applications are rapidly becoming the default tool for personal finance, e.g., e-banking, online shopping, etc.; when they get a wide user base and offer access to more resources, they become more attractive to attackers. Without a robust security foundation, a mobile financial platform provides a vast attack surface and the opportunity to compromise individual accounts or the complete infrastructure.
Some attacks attempt to reverse-engineer mobile applications, while others focus on intercepting the communication between the server and the application. If successful, such attacks can result in severe financial and/or reputational damage.
Therefore, security must be integrated and prioritized during the development of Android applications.
Application Data and Credentials
User Data protection
Keeping sensitive user data away from the prying eye is always the priority. The following sections present how to protect user data in data storage as well as from media projection and abusing permissions. It also shows how to hide away credential information in the gradle script and using cryptography.
1. Data Storage
There are many types of data storage for Android e.g., SharedPreferences, Files, and Databases.
The basic SharedPreferences
stores the data in plain text, making it vulnerable to actors with root access, when mounting the device without using the Android OS, bypassing permissions that restrict access, or other security exploits. Similarly, files and databases can also be compromised. This poses a security risk for storing sensitive user data such as credentials, tokens, sessions, etc.
💡 The following best practices must be followed:
- SharedPreferences: to securely store private user data, consider using EncryptedSharedPreferences from the latest security library, part of Android Jetpack.
-
Files: store sensitive application files in the internal storage to prevent other applications from accessing them. To introduce additional security, use EncryptedFile included in the security library, which provides custom implementations of
FileInputStream
andFileOutputStream
for more secure streaming read-write operations to store and access files on the device storage. Store files in external storage only when they are not sensitive. Otherwise, use the aforementionedEncryptedFile
class to encrypt the files while storing them in the external storage.
- Databases: do NOT store sensitive data in databases. If necessary, apply encryption using the Android Keystore system. Use a third-party library such as SQLCipher to integrate Room with SQLCipher.
2. Media Projection
A malicious application can bypass the permission models to capture screenshots or record the screen with audio while the user is using an application that requires strict privacy, e.g, banking, mobile wallet, etc. The same can happen while the application window is visible in the Recent Screen, thus leaking sensitive user information.
💡 To prevent any application, malicious or not, from capturing/recording an application screen, consider applying FLAG_SECURE
on destination activity or fragment.
3. Application Runtime Permission
Permissions provide the applications access to user data and hardware that can be abused by malicious applications. Therefore, requesting more permissions (install-time or runtime) than the application is supposed to need may deter users from using it and reduce user engagement. Also, third-party libraries can sometimes silently add unintended permissions to the final merged manifest file. They must be reviewed carefully and removed before publishing the application.
💡 The following best practices must be followed:
- Minimize the number of permissions that the application requests. Generally, if a permission is not required for the application to function, do NOT request it.
- Ask for runtime permission only when the feature that needs the permission is being used and provide a clear and succinct explanation as to why it is needed.
- If the permission is not granted by the user, allow them to continue using other features of the application.
- Permissions asked by third-party libraries can be found in the merged manifest.
- To view which library asked for the unwanted permission, go through the
merger-staging-debug-report.txt
file located in the directoryapp/build/outputs/logs/
. - Use
tools:node="remove"
inside the<uses-permission/>
tag for the permission that want to remove in theAndroidManifest
file. For example:
- To view which library asked for the unwanted permission, go through the
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
Credentials protection
1. Credential information
When using Gradle to generate signed APK, creating manifest placeholders, or defining buildConfigField
s, hardcoding the credentials such as API secrets, keystore passwords, etc., can expose them via version control or reverse engineering.
💡 The following best practices must be followed:
Create .properties
files to keep the passwords, secrets, and other credentials. Then read them from the files in build.gradle
. After that, add the files to .gitignore
to prevent pushing them to the repository.
// Put this in app module level gradle:
signingConfigs {
release {
try {
storeFile file("myapp.keystore")
storePassword KEYSTORE_PASSWORD
keyAlias "thekey"
keyPassword KEY_PASSWORD
}
catch (ex) {
throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
}
}
}
// And create another `gradle.properties` file to keep the password, do NOT push this to the repo.
KEYSTORE_PASSWORD=real_keystore_password
KEY_PASSWORD=real_key_password
2. Keystore API
At lower API levels (< 21), EncryptedSharedPreferences
is not available. Therefore, Android provides the Keystore API to securely store the encryption keys to encrypt and decrypt secrets.
💡 The following best practices must be followed:
Encrypt and decrypt the data using a randomly generated AES key along with RSA public/private key pair in the following steps:
Key Generation
- Generate a pair of RSA keys.
- Generate a random AES key.
- Encrypt the AES key using the RSA public key.
- Store the encrypted AES key in Preferences.
Encrypting and Storing the data
- Retrieve the encrypted AES key from Preferences.
- Decrypt the above to obtain the AES key using the private RSA key.
- Encrypt the data using the AES key.
Retrieving and decrypting the data
- Retrieve the encrypted AES key from Preferences.
- Decrypt the above to obtain the AES key using the private RSA key.
- Decrypt the data using the AES key.