Encrypting strings in Android: Let’s make better mistakes
If you do a web search for “encrypting Strings in Android”, you’ll find a lot of example code, and they all look pretty similar. They definitely input a String and output gibberish that looks like encrypted text, but they are often incorrect. Crypto is tricky: it’s hard to tell that the gibberish that’s being printed is not good crypto, and it’s hard to tell that the code example you picked up from Stack Overflow has serious flaws.
The problem here is that sites like Google and Stack Overflow rank results based on popularity, but the correctness of crypto isn’t something we can vote about. It’s not a popularity contest. To use it correctly, you have to understand the properties of the algorithm and the security goals of your code. Maybe the bad crypto someone pasted up on the Internet was acceptable for their needs, but there’s a good chance it’s completely unacceptable for yours.
We want to help make things better, but let’s start by pointing out specifically why this is so problematic. Google for “encrypting strings in Android” and the first hit is a great example of how to do it all wrong. By virtue of being the first hit in Google, this code has propagated all over the place, including hundreds of Github projects.
Here are the typical mistakes you’ll see:
- Bad key generation: Ideally, AES keys should be securely generated and random. Alternately, depending on the use case, you can use a standard password-based method to generate a key that’s as strong as the password. Lots of sites recommend using the bytes from a password as the seed to the random number generator. That’s not right.
- Out of date key generation: In 2013, folks realized that the SecureRandom implementation in Android is flawed and developers need to explicitly initialize the PRNG with entropy. This error caused the loss of some bitcoin.
- Use of ECB: The default mode of AES in Android is ECB, which encrypts each block the same, and so is subject to analysis and replay attacks. Here’s an example of why ECB isn’t so great. Or imagine you’re encrypting a bunch of passwords: you couldn’t necessarily decrypt the passwords without the key, but you could tell which sites use the same passwords. A better mode is CBC, and that mode has been available for quite some time in Android.
- Bad Padding: When you’re encrypting data that’s less than the size of an AES block, you need to specify a secure padding scheme. Even then, it’s kinda tricky.
- No integrity: Lots of people think AES has integrity checking built in. The thinking goes, “if it decrypts correctly, it was generated by the person with the private key”. Actually, AES CBC allows an attacker to modify the messages. In our vanilla searches, we didn’t find a single example of people doing integrity checking with AES CBC.
- Incorrect IV: When using e.g. CBC mode, a random IV is required. This makes dealing with the ciphertext a little more complicated since you need to keep the (non-secret) IV around to decrypt it. Since it’s tricky, sometimes people use a fixed IV, which is often bad. Android does some pretty weird things if you don’t specify an IV (and I’ve heard that some Android versions don’t even let you specify an IV):
- In KitKat, Android generates a random looking IV during encrypt, which was different (at least for each new instance of Cipher) and used all zeros during decrypt; between runs, the IV varies, so it will never decrypt the same way twice. This is arguably the “right” behavior if someone doesn’t set the IV, but if you don’t know what you’re doing, you’ll encrypt something and never be able to decrypt it.
- On 4.3, the app used an IV of all zeros for both encrypt and decrypt so it always encrypts and decrypts the same.
- On my rooted CyanogenMod 4.2.2 device, it used a random looking IV that was the same between runs and between encrypt and decrypt. I don’t know where it’s getting / storing this IV.
- Weak algorithms: Use of DES or MD5 is problematic since both of those algorithms have been demonstrated to be weak and should no longer be used.
In fact, misuse of cryptographic libraries is a common source of bugs as this paper on 269 cryptography-related CVEs demonstrates. Check out the appendix for a plethora of bad Android crypto examples.
A Java class that can help[button style=”btn-link btn-lg” icon=”fa fa-github-alt” align=”left” type=”link” target=”true” title=”Download on Github” link=”https://github.com/tozny/java-aes-crypto”]
Although complete crypto libraries like Keyczar are widely available, a lot of developers are looking for a short class they can paste into their code. As much as crypto professionals don’t like this behavior since it’s likely to result in bad code, it’s the reality.
To fix this, we provide a Github repo that’s a short standalone Java class to correctly use the built-in AES libraries. This code can be added to an Android project. We’re sure that it needs work, but that it’s far better than anything currently coming up in Google results. We would very much appreciate review and pull requests if you can improve this class.
Here are the properties of this class. We believe that these properties are consistent with what a lot of people are looking for:
- Paste-ability: A very simple Java class that works across most or all versions of Android. The class should be easy to paste into an existing codebase.
- Works for strings: It should encrypt arbitrary strings or byte arrays. This means it needs to effectively handle multiple blocks (CBC) and partial blocks (padding). It consistently serializes and deserializes ciphertext, IVs, and key material using base64 to make it easy to store.
- Algorithm & Mode: We chose: AES 128, CBC, and PKCS5 padding. We use a 128 bit key size for its widespread support and because it’s not clear that 256 is stronger. We would have picked GCM for its built-in integrity checking, but that’s only available since Android Jelly Bean, which leaves out about 1/4 of active Android devices
- IV Handling: We securely generate a random IV before each encryption and provide a simple class to keep the IV and ciphertext together so they’re easy to keep track of and store. We set the IV and then request it back from the Cipher class for compatibility across various Android versions.
- Key generation: Random key generation with the updated generation code recommended for Android. If you want password-based keys, we provide functions to salt and generate them.
- Integrity: We’ve also added more-or-less transparent integrity checking in the form of a SHA 256 MAC with a constant-time equality check. This is in the form of a “combined key” where 128 bits of the key are used for encryption and 256 bits are used for integrity, then the keys are kept together.
I’m sure it’s not perfect, but maybe over time we can get the bad code taken down and replaced with code that’s at least not completely wrong.
Much credit to Chris Swenson and Thomas DuBuisson for advice and code to make this library better.
Appendix: Let’s look for crypto
A lot of programmers need to depend on getting good search results for building code that’s outside of their areas of expertise. Usually, you can tell that such code is working or not working, but with crypto, not so much. In summary, the top Google results are all either wrong or out of date. Out of the 10 top search results we found (5 each for different queries) only one of them correctly generates keys and IVs, and none of them have the up to date key generation from 2013 and none of them use any integrity checking.
Let’s look at the results from a few specific searches:
Search for: Encrypting strings in Android
Googling for “encrypting strings in Android” here are the top 5 results:[table “” not found /]
Search for: Android AES CBC example
So let’s say you read up a bit and know you should use AES and CBC (instead of DES and MD5). Here are the top results from Googling “Android AES cbc example”:[table “” not found /]