Menu Close Back

Managing and securing your app’s configuration

Managing and securing your app’s configuration

Introduction

In this article, we will cover a simple way to take advantage of the Gradle build system’s build variants. This helps you to easily define different server URLs and API keys for these different environments. Moving your environment configuration into Gradle’s build variants means you are not reliant on manual code changes (which can be error-prone) before releasing your live app version.

We will also cover the use of DexGuard’s asset encryption to ensure the protection of your configuration values from static code analysis.

There is accompanying sample code located here, which illustrates the techniques discussed in this article. We recommend you to read the Android development documents, as it contains useful information on getting started with build variants.

Built-in Gradle configuration options

Environment build variants

When we talk about environment, we mean server environment, data or external application. When developing applications, it’s common practice to define different environments for each stage of the project, for example development, testing and release.

We can apply different configurations to our application to match those environments. For example:

  • debug – the development release, typically with verbose logging and pointing to non-live APIs and/or data sets.

  • qa – the testing release for your testers to sign off. It is nearly as productive as possible except pointing at testing API/data environment.

  • release – the version ready for upload/distribution (also called live or production).

buildTypes { 
  debug { 
    debuggable true 
    applicationIdSuffix ".debug" 
    minifyEnabled false proguardFiles 
    getDefaultProguardFile ('proguard-android.txt'), 'proguard-project.txt' 
  } 
  release { 
    minifyEnabled true 
    proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-project.txt' 
  } 
} 

This code shows standard debug and release build type configurations.

Common build variant attributes

Seasoned Android developers are likely familiar with the following attributes that can be defined within the buildTypes or buildFlavors sections of the application’s gradle.build file.

  • versionNameSuffix '-debug' – this adds ‘debug’ to the end of the versionName, which is useful in the case of occuring crashes, so that you can identify them as originating from a debug build of the app.

  • applicationIdSuffix '.debug' – defining an applicationIdSuffix for the debug/qa build variants alters the applicationId. This is very useful as it allows multiple versions of the same app to be installed on your device.

  • signingConfig signingConfigs.release – signingConfig allows you to define a different signing certificate for each build variant. This can be useful when interacting with certain third party APIs (such as the Facebook Android SDK) that require the signing certificate key hash to be added to the server side configuration.

Using Android resources to differentiate between build variants

As noted in the previous section, you can install multiple versions of the same application on a device using the applicationIdSuffix. This can help during development and testing, in particular with regression testing. However, by default each installation will have the same name and icon, so it can be difficult to identify which app is which on the app launcher draw.

In the next section, we will cover a way to define a different app name per build variant.

Creating a different app name for the debug build variant

To identify debug builds of our example we shall create a new xml resources file called app_name.xml with the below content in debug/res/values/

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
  <!-- differ between each version --> 
  <string name="app_name">Config debug</string> 
</resources> 

Note: The debug folder directly corresponds to the name of the debug build variant.

Creating the release build variant’s app name

For the release build variant we create the same file app_name.xml as above, except this time located under the release variants folder, i.e. release/res/values/

We must define an attribute with the key of app_name, but this time we use the name of the app we want to have when the app is released. For example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <!-- differ between each version -->
  <string name="app_name">Config example</string>
</resources>

Reference @string/app_name in the application element

To ensure that our app uses the app_name we just defined, we have to reference the @string/app_name in our application element in the app’s Android Manifest.xml file.

<application 
  android:icon="@drawable/ic_launcher" 
  @string/app_name 
  android:theme="@style/AppTheme" > 

Changing the app icon

For further customization, we can also change the app icon to include some kind of visual indicator to indicate it’s not a release build. Using the same method as described above we’ve added a bug icon to the corner of the ic_launcher.png.  

android icon

Using properties file based configuration

Now we’ll have a look at adding deeper configuration for your app’s build types. This will allow you to configure your app to point at different API servers depending on the build variant.

For each of your buildtypes you have to create a file called config.properties in the <buildtype>/assets folder within app/src. For our example we create two files with these full paths:

<project root>/app/src/debug/assets/config.properties 
<project root>/app/src/release/assets/config.properties 

Utility class to read config.properties file

The code below defines an AssetPropertyManager class. This is a simple utility class to handle the reading of the properties file from the apps/assets directory and to provide property values to the rest of the app via getters.

private static final String PROP_FILE = "config.properties"; 
private final Properties properties = new Properties(); 

public AssetPropertyManager(Context context) { 
  AssetManager assetManager = context.getAssets(); 
  try { 
    properties.load(assetManager.open(PROP_FILE)); 
  } catch (IOException ignored) { 
  // TODO: Handle error 

You have to create getter methods for each of your config properties.

public String getFlurryKey() { 
  return properties.getProperty(CONFIG_FLURRY); 
} 

Optimization tip

It’s a good idea to initialize the AssetPropertyManager when the application starts up, to avoid blocking any UI code that might need the config data.

public class MyApp extends Application { 
  
  public void onCreate() { 
  assetPropertyManager = new AssetPropertyManager(this); 
  } 

  public AssetPropertyManager getAssetPropertyManager() { 
  return assetPropertyManager; 
  } 
} 

Now you have to use the getApplication().getAssetPropertyManager().getFlurryKey() in your code when you need to pass the api key.

DexGuard protection

Separating your config into a properties file eases maintenance, but it also makes it easier for an attacker to find out if the file decompiles your application’s .apk file. Therefore it’s recommended to use a binary protection tool such as DexGuard.

This is the DexGuard configuration needed to ensure these environment config values are secured.

Define the following in your project’s dexguard-project.txt file:

-encryptassetfiles assets/config.properties

Or you could add the following line to encrypt all asset files in the assets directory:

-encryptassetfiles assets/**

After encryption of our example, the config.properties file looks like this:

When we view the config.properties file from the protected .apk, the source has been encrypted and cannot be easily decrypted.

Additional protection

For additional protection, you may want to encrypt the string constants in the AssetPropertyManager and encrypt the entire class.

-encryptstrings class com.scottyab.configexample.AssetPropertyManager 
-encryptclasses com.scottyab.configexample.AssetPropertyManager 

Conclusion

First, we have covered several useful ways to vary an Android app’s configuration depending on the build type or build flavor. We have utilised built-in Android Gradle plugin variables such as 'applicationIdSuffix' and 'signingConfig'. Next, we have further customised the configuration by extracting our app’s environment configuration into a separate config.properties file. This is illustrated in the code sample that accompanies this article. This change has made it easier to find and maintain the configuration between build types or build flavors, which is ideal for new developers to a project. Finally, we have reviewed how to enable and configure DexGuard to secure those configuration options.

Scott Alexander-Bown

About the author
Scott Alexander-Bown (@scottyab) is an independent Android developer and co-author of the Android Security Cookbook. He is also the founder of SWmobile, a mobile developer-focused Meetup group which runs tech talks, networking and hacking events.