Android KeyStore Compat
The story of a guy that ran into a KeyStore
Context
This story is non fictional, but for security reasons, name of people and places have been changed to ensure anonymity. We don't laugh about security around here!
Also this whole story takes place in an Android app, which makes sense as there is "AndroidKeyStore" in the title... but I make that clear just in case.
In order to keep this short, I will not go into all the details of how a certificate work. So I guess you're supposed to know at least what they are and what they do.
Yes, I will attempt to keep this short, which is going to be difficult as I love to talk!
spoiler: check the solution/conclusion at the end of the article if you don't want to read the whole thing!
Story
So a little while back, I ran into this issue where I needed to store a few keys
(read here "public certificates which are combined with a private sibling hidden somewhere on the vast land of the interwebs") received by this amazing channel called "an API".
The use I have for those keys (certificates) is irrelevant for this post, let's say I was doing some highly classified processing, way above your pay grade. So don't ask as if I tell you, I'll have to kill you.
Anyway, I received those certificates through an API and I needed to store them so that I can reuse them even if the app is restarted. For that, I use ... well ... a KeyStore. Sure enough, testing this out on my great Nexus 5, big Nexus 6, awesome Nexus 6P and trusty Nexus 9 (see a pattern here?), everything was working just fabulously. Then I got a bug report, a very detailed one, saying, and I quote:
it doesn't work on my phone.
Well it took me a while to figure out that:
1. I was using it wrong
2. I was not handling errors and exceptions properly (sometimes I'm lazy!)
3. The user's phone was running with Android 4.2 JellyBean (Level 17)
My Mistake
Well I didn't know much about KeyStore back then, I was young and inexperienced ... so I did what everyone would have done:
* I found a tutorial online (StackOverflow anyone?) that seemed to be doing what I wanted to do
* I did not read the whole instructions and just copy pasted the code
* I noticed that it was working on my Nexi, I was happy and we went ahead with the release
Well, it turns out that when you google Android KeyStore
, you will find a bunch of tutorials that explain how to use the "AndroidKeyStore", which is nice, but through my personal research (and lack of reading the fine prints), I couldn't find one that actually mentioned that the "AndroidKeyStore" didn't exist before Android SDK Level 18 (also known as Android Jelly Bean MR2). And that's when my story really begins.
Note: the official Android KeyStore System page actually says very clearly:
the Keystore system is used by the KeyChain API as well as the Android Keystore provider feature that was introduced in Android 4.3 (API level 18).
So I should have seen this coming... yep, I should have...
The Android KeyStore System
And here is what I have learned since then:
So a KeyStore
object is doing all sorts of security magic that I will not (read here cannot ;) ) explain right now. However I can show you some things.
Instantiation
You do not just new
a Keystore, you summon its presence.
Keystore keystore = KeyStore.getInstance("AndroidKeyStore");
This is what you will see in most of the tutorials, which basically says "give me the AndroidKeyStore instance"
... for the untrained eye. For the others, you will read "give me an instance of a Keystore of type AndroidKeyStore"
, which is a little bit different.
This on devices running Android versions below 4.3 will return actually barf a very lovely exception such as this:
java.security.KeyStoreException: java.security.NoSuchAlgorithmException: KeyStore AndroidKeyStore implementation not found
at java.security.KeyStore.getInstance(KeyStore.java:119)
...
Caused by: java.security.NoSuchAlgorithmException: KeyStore AndroidKeyStore implementation not found
at org.apache.harmony.security.fortress.Engine.notFound(Engine.java:177)
...
This is quite clear isn't i? Well it is when you pay attention to the exceptions and you treat the handling properly. It says very clearly "sorry, but I don't know this AndroidKeyStore you speak of". Run the same code on a Level 18+, and it will be just fine and return an actual instance.
The AndroidKeyStore Provider
To make a long story short, the "AndroidKeyStore" string you pass to the KeyStore.getInstance()
method tells the system to return the/an instance of that particular provider, which for my little 4.2 device has not been invented yet!
In short, the provider allows to declare some certificate storing mechanism in a way that is opaque to the client. some will like this, others won't. In this case, this provider is very secure
, is app specific
, handles its own persistance
, maybe more?
In code, when you do something like the following, you don't have to worry about a thing.
Certificate cert = getMyVerySpecialCertificateFromSomeVerySecretePlace();
String alias = generateAnAliasThatMakesSense();
KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(null);
keystore.setCertificateEntry(alias, cert);
Well actually, you do have to worry about the potential exceptions that can be thrown, such as KeyStoreException
, CertificateException
, NoSuchAlgorithmException
, IOException
... Ho yeah baby, exceptions! Don't you love them?
But all in all, once you have done this, your certificate is stored in a very secure place that only this particular app can obtain.
So what about Level 17 and below?
Well I still had my issue that my app was to work on Level 16 and up, and I had code that could only work on Level 18 and up. So I had to find the "old way" of doing things, and here it is. For the same 3 lines shown above (I'm not counting the retrieval of the certificate or the alias), you have to do a little bit more.
First, you need to decide on what type of KeyStore to get
. The type basically represents the kind of encryption algorithm used to encrypt the data in the store. you can find a list of the available types on the KeyStore reference page.
KeyStore keystore = KeyStore.getInstance("BKS");
Or
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
Then you need to open it properly like so:
FileInputStream fis = context.openFileInput(KEYSTORE_FILE);
keyStore.load(fis, PASSWORD);
fis.close();
And finally, you actually need to store the data, because this one won't do it for you... (don't be greedy now!).
FileOutputStream fos = context.openFileOutput(KEYSTORE_FILE, Context.MODE_PRIVATE);
keyStore.store(fos, PASSWORD);
fos.close();
... And everything else (containAlias
, setCertificateEntry
, deleteEntry
, etc) works the same way.
The Conclusion / Solution
So basically, if you're lucky enough to only make minSdkVersion=18
apps, you really don't care about this. Otherwise, please understand that it is best to use the AndroidKeyStore whenever you can, and when you can't then go back to the dark ages. In other words you need some kind of a KeyStore
wrapper that will handle that logic for you.
And surprise: I did it for you and it's free of charge!!! And you'll wonder how you could have ever survived without it (ok it's maybe a little bit too much there !) --> check this gist out.
I hope this article was useful, leave a comment if you have any remarks, enjoy the rest of your day.
Over and out!