Basic Android Encryption Do’s and Don’ts

This article is meant to be a collection of things I found useful to know while building an encryption layer within an Android app. If you have anything to contribute or to correct please let me know and I’ll make updates accordingly

Before proceeding I highly recommend reading Yakiv Mospan’s article on Android security. I found it to be an excellent foundation for getting started on encryption in Android. *Note that as of writing this there will be some overlap with Yakiv Mospan’s blog post as he is still updating it.

  1. Things to avoid
  2. Things to do
  3. Nice to know

1. Things to avoid

Avoid using ECB block chain (which is applied by default unless specified otherwise) when using Symmetric AES ciphers for encryption. Use GCM or CBC block chaining methods instead. ECB is insecure as it does not output unique encryptions when given duplicate data (See ECB Penguin for a visual explanation).

Do

Cipher.getInstance("AES/GCM/NOPADDING");

Don’t (will apply ECB by default)

Cipher.getInstance("AES");

Don’t use hard coded values for cipher initialization. Integrity of the cipher is compromised if it isn’t given unique information for every operation. Provide a SecureRandom to the init method of ciphers as the default values may not be random for older versions.

Do

SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[IV_LENGTH];
secureRandom.nextBytes(iv);
myCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

Don’t

myCipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = myCipher.getIV();

2. Things to do:

Use larger key sizes when generating keys. (See here for generating RSA Key Pairs, and here for an AES Key) This may be obvious but needs to be said as the default key sizes in each platform might not be acceptable for some people. See here for supported key sizes.

Zero-out sensitive byte arrays. Supposedly, replacing each index in a byte array with 0 helps against in memory attacks. I’m hesitant on the accuracy of this due to all of the moving parts in the Android ecosystem, so take this with a grain of salt.

byte[] decrypt(byte[] dataToDecrypt, byte[] secretKey) {
//Decrypt data using cipher and secretKey
...
//Clear out secret key encoding byte array
Arrays.fill(secretKey, (byte) 0);
return decryptedData;
}

3. Nice to know:

RSA cannot encrypt data larger than it’s key size (padding affects this as well). If data size is unpredictable then use AES. For those of you targeting API 18 and can only use RSA for the Keystore (AES support provided after API 23). Some possible solutions include separating the data into chunks and running the cipher encryption on each section (Although note that this will make the encryption slower). Alternatively, encrypt data using AES and key wrap the AES key using a RSA key backed by the keystore.

Destroy method in SecretKey is unreliable If targeting ≤ API 25 app will crash with “method not found” exception. If api version is above 25, then the method will simply throw DestroyFailedException by default. I don’t have any recommendations outside of zeroing-out secret key byte arrays as the solutions I’ve seen are hacky and use reflection. If you have any suggestions please let me know.

Cipher accepts empty byte arrays but not null. I wasn’t aware that you could encrypt something that was empty. I guess this is more of a fun fact.

Use Base64 (Or others formats like Hex, Base32, etc) when handling byte arrays produced via encryption/decryption. Base64 facilitates transportation of bit data by turning it into intelligible characters that exist in most systems. A good strategy for encrypting things like Strings is to do String.byte(“UTF-8”) -> encrypt -> encode to Base64 String. Decoding on the other hand would be done in the opposite direction.

Encoding:

String encryptAndEncode(String data) {
Charset charSet = Charset.forName(UTF_8);
//encrypt method not implemented
byte[] encrypted = encrypt(data.getBytes(charSet));
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
}

Decoding:

String decodeAndDecrypt(String encrypted) {
Charset charSet = Charset.forName(UTF_8);
//decrypt method not implemented
byte[] data = decrypt(Base64.decode(encrypted, Base64.NO_WRAP));
return new String(data, charSet);
}

Interested in joining our team? WeWork is hiring software engineers in Tel-Aviv and New York. View openings