Stay Connected

PERSONAL BLOG

Storing Secret Keys in Android

Let’s say that you are building an Android app, that connects to a third-party service. Almost any API worth implementing uses an API key as part of basic authentication and security. Often your app will have secret credentials or API keys that you need to have in your app to function but you’d rather not have easily extracted from your app.

If you are using dynamically generated secrets, the most effective way to store this information is to use the Android Keystore API. You should not store them in shared preferences without encrypting this data first because they can be extracted when performing a backup of your data.

The alternative is to disable backups by setting android:allowBackup in your AndroidManifest.xml file:

<application android:allowBackup="true">
</application>

You can also specify which files to avoid backups too by reviewing this doc.

Using the Android Keystore API

To understand the Android Keystore API, you must first understand that encrypting secrets requires both public key and symmetric cryptography. In public key cryptography, data can be encrypted with one key and decrypted with the other key. In symmetric cryptography, the same key is used to encrypt and decrypt the data. The Keystore API uses both types of cryptography in order to safeguard secrets.

A public/private key RSA pair is generated, which is stored in the Android device’s keystore and protected usually by the device PIN. An AES-based symmetric key is also generated, which is used to encrypt and decrypt the secrets. The app needs to store this AES symmetric key to later decode, so it is encrypted by the RSA public key first before persisted. When the app runs, it gives this encrypted AES key to the Keystore API, which will decode the data using its private RSA key. In this way, data cannot be decoded without the use of the device keystore.

Read this Medium blog for more information about how to use the Keystore API. Do not use the Qlassified Android library because it introduces an additional 20K methods to your Android program. You can use the Android Vault library, which will also help facilitate the rotation of RSA keys, which usually have an expiration date of 1-5 years.

See also the official Google sample for using the Android Keystore.

Storing Fixed Keys

For storing fixed API keys, the following common strategies exist for storing secrets in your source code:

  • Embedded in the resource file
  • Hidden as constants in source code
  • Hidden in BuildConfigs
  • Obfuscating with Proguard
  • Disguised or Encrypted Strings
  • Hidden in Native Libraries with NDK

However, none of these strategies will ensure the protection of your keys and your secrets aren’t safe. The best way to protect secrets is to never reveal them in the code in the first place. Compartmentalizing sensitive information and operations on your own backend server/service should always be your first choice.

If you do have to consider a hiding scheme, you should do so with the realization that you can only make the reverse engineering process harder and may add significant complication to the development, testing, and maintenance of your app in doing so. Check out this excellent StackOverflow post for a detailed breakdown of the obfuscation options available.

The simplest and most straightforward approach is outlined below which is to store your secrets within a resource file. Keep in mind that most of the other more complex approaches above are at best only marginally more secure.

Secrets in Resource Files

The simplest approach for storing secrets to keep them as resource files that are simply not checked into source control. Start by creating a resource file for your secrets called res/values/secrets.xml with a string pair per secret value:

<!-- Inside of `res/values/secrets.xml` -->
<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
<resources>
<string name=&quot;parse_application_id&quot;>xxxxxx</string>
<string name=&quot;google_maps_api_key&quot;>zzzzzz</string>
</resources>

Once these keys are in the file, Android will automatically merge it into your resources, where you can access them exactly as you would your normal strings. You can access the secret values in your Java code with:

// inside of an Activity, `getString` is called directly
String secretValue = getString(R.string.parse_application_id);
// inside of another class (requires a context object to exist)
String secretValue = context.getString(R.string.parse_application_id);

If you need your keys in another XML file such as in AndroidManifest.xml, you can just use the XML notation for accessing string resources:

@string/google_maps_api_key

Since your secrets are now in an individual file, they’re simple to ignore in your source control system (for example, in Git, you would add this to the ‘.gitignore’ file in your repository) by entering this on the command line within your project git repository:

echo "**/*/res/values/secrets.xml" > .gitignore

Verification: To make sure this worked, check the .gitignore file within your git repository, and make sure that this line referencing secrets.xml exists. Now, go to commit files to Git and make sure that you do not see the secrets.xml file in the staging area. You do not want to commit this file to Git.

This process is not bulletproof. As resources, they are somewhat more vulnerable to the decompilation of your application package, and so they are discoverable if somebody really wants to know them. This solution does, however, prevent your secrets just sitting in plaintext in source control waiting for someone to use, and also has the advantage of being simple to use, leveraging Android’s resource management system, and requiring no extra libraries.

Hidden Constants in Source Code

Assuming you are happy with obscure rather than secure, there a number of mechanisms you could use, but obfuscaters like proguard are not going to be able to help you.

To achieve this you will need to do encoding or encryption of the string yourself, the approach you use depends on what you are trying to defend against, if you are just trying to hide from obvious inspection, then encoding may be sufficient (see android.util.Base64, http://developer.android.com/reference/android/util/Base64.html). Note that encoding is in NO WAY SECURE and all it will do is remove the obvious reference to your site.

If you are trying to defend against something more, then you could move to actually encrypting the string, to do this you would use a symmetric cipher like AES via javax.crypto.Cipher, http://www.androidsnippets.org/snippets/39/index.html provides a decent usage example. Again this is more annoying then secure to would be hackers, as you will need to store the key somewhere in your jar thus negating any cryptographic security.

To make this clearer, the basic steps would be:

  1. Manually create an encrypt your string using a known key.
  2. Convert your code to use a decrypted version of this string, example:

Before:


public class Foo {
private String mySecret = &quot;http://example.com&quot;;

}

Becomes:


public class Foo {
private String encrypted = &quot;<manually created encrypted string>&quot;;
private String key = &quot;<key used for encryption&quot;;
private String mySecret = MyDecryptUtil.decrypt(encrypted, key);

}

A (good) alternative to all of this is considering using a third party DRM solution such as the licensing server google provides http://android-developers.blogspot.com/2010/07/licensing-service-for-android.html. This may be more secure than something you roll your self but is subject to very similar limitations to what described above.

Hidden Secrets in BuildConfigs

Another suggestion to manage the key in the BuildConfig from the Android Gradle plugin. It can minimize the risk of leaving secrets exposed in your version control system (especially important if you use a public DVCS like GitHub):

buildTypes {
  debug {
    minifyEnabled true
    buildConfigField "String", "hiddenPassword", "\"${hiddenPassword}\""
  }
}

You can then set this value in a .gitignore’d local.properties or a checked-in gradle.properties as shown here:

hiddenPassword=My_S3cr3t_P@$$W0rD

Unfortunately, this doesn’t improve on the secret-in-source-code situation described above since these values are emitted as BuildConfig code. It can be inspected and extracted exactly in the same manner.

Protect Secrets with Proguard

So we’re losing the battle with strings. Okay, no problem! We can just throw a little proguard at our app, have it obfuscate our source code, and it should solve our little strings problem. Right?

Not quite. Let’s take a look at proguard-rules.pro in our project:

# Just change our classes (to make things easier)
  -keep class !com.apothesource.** { *; }

We’re already telling proguard to obfuscate all of the code in our package (com.apothesource.**). I can also say with confidence that Proguard worked as instructed. So why are we still able to see the passwords?

Proguard explicitly does not do anything to protect or encrypt strings. The reason makes sense too- It can’t just change the value of a string that your app depends on without the risk of significant side effects. You can see exactly what proguard did by reviewing the mapping.txt file in our build output:

com.apothesource.hidingpasswords.HidingUtil -> com.apothesource.hidingpasswords.a:
    java.lang.String hide(java.lang.String) -> a
    java.lang.String unhide(java.lang.String) -> b
    void doHiding(byte[],byte[],boolean) -> a
com.apothesource.hidingpasswords.MainActivity -> .hidingpasswords.MainActivity:
    byte[] mySlightlyCleverHidingKey -> a
    java.lang.String[] myCompositeKey -> b

So you can see that it renamed our classes, methods, and member/field names as expected. It just didn’t help us at all when it comes to our strings problem. You can also look at the output of the compiler to see the effect of proguard. Here are the normal vs. proguard outputs on our MainActivity static fields, for example:

Normal Output:

#static fields
.field private static final TAG:Ljava/lang/String; = "HidingActivity"
.field private static final myCompositeKey:[Ljava/lang/String;
.field private static final myNaivePasswordHidingKey:Ljava/lang/String; = "My_S3cr3t_P@$$W0rD"
.field private static final mySlightlyCleverHidingKey:[B

Proguard Output:

#static fields
.field private static final n:[B
.field private static final o:[Ljava/lang/String;

Proguard does a good job here of detecting that it can replace variable names and even inline our password to make it a local variable. When you inspect the generated method implementation, though, our password is still there in raw form:

.method public b(Ljava/lang/String;)V 
  
  move-result-object v0
  const-string v1, "My_S3cr3t_P@$$W0rD"

While not a silver bullet, Proguard is still an important tool if you intend to prevent reverse engineering. It is highly effective in stripping valuable context like variable, method, and class names from the compiled output, making detailed analysis tasks much more difficult. If you’d like to compare the decompiled outputs of a proguard vs non-proguard protected application, we’ve included both version of our app on Github.

Adnan Sattar

A self-driven productive Software Engineer who thrives in highly pressurized and challenging working environments by constantly improving skills. Eager to grasp new ideas and concepts from the environment.

Leave a Reply

Recent Comments

    Stay Connected

    Instagram Feed

    Categories

    ×
    %d bloggers like this: