Security Pipeline for Android Development 🔐

Hero image for 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 and FileOutputStream 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 aforementioned EncryptedFile class to encrypt the files while storing them in the external storage.

Store files that are meant for the application’s use only, either in dedicated directories within an internal storage volume or different dedicated directories within external storage. Use the directories within internal storage to save sensitive information that other applications should not access. Reference: https://developer.android.com/training/data-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 directory app/build/outputs/logs/.
    • Use tools:node="remove" inside the <uses-permission/> tag for the permission that want to remove in the AndroidManifest file. For example:
<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 buildConfigFields, 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

  1. Generate a pair of RSA keys.
  2. Generate a random AES key.
  3. Encrypt the AES key using the RSA public key.
  4. Store the encrypted AES key in Preferences.

Encrypting and Storing the data

  1. Retrieve the encrypted AES key from Preferences.
  2. Decrypt the above to obtain the AES key using the private RSA key.
  3. Encrypt the data using the AES key.

Retrieving and decrypting the data

  1. Retrieve the encrypted AES key from Preferences.
  2. Decrypt the above to obtain the AES key using the private RSA key.
  3. Decrypt the data using the AES key.