Obfuscating Shared Preferences in Fire TV Apps
What's the best way to keep secure data in our local Android or Fire TV shared preferences? Well, don't do it. If someone's curious enough, it's not too hard to dig into the Android file system to look at a particular app's preferences.
What could we do to keep curious users from breaking our app by tampering with the settings? Perhaps we could make it a little less convenient to inspect or modify the settings.
In our suite of Fire TV apps recently published in the Amazon Appstore, we store and secure almost all of our data server-side. However, we keep some data locally, such as the list of videos we've already watched or the list of our favorite shows. We prefer our users use the app to change settings, so we sometimes obfuscate our local settings by encrypting them.
A Fire TV app is just an Android app at its core, which lets us use the convenient Android Keystore provider introduced in API level 18. However, the Keystore supports only the RSA algorithm, which limits payloads to its key size (minus padding). For larger payloads, we need an algorithm like AES. Although the Keystore expands to include AES in API level 23, our Fire TV devices aren't at that level (yet). We need to combine elements of each algorithm in our solution!
Combining RSA with AES, a.k.a. the Allegory of the Pantry
I like to use AES to encrypt my settings, then I encrypt the AES key with RSA. I think of this approach like having a lock on my pantry door, but I keep the key in a small lock box nearby.
In this allegory, RSA encryption is the lock box and AES encryption is the locking pantry (and for me, the encrypted data is probably baked goods).
Securing the Pantry: AES
Here's a way to implement AES and RSA using Bouncy Castle or Spongy Castle (a repackaging of Bouncy Castle that works on Android).
When we're storing our settings, we need a way to encrypt our [potentially] large payload with AES. AES is a symmetrical algorithm, so there's one secret key that encrypts and decrypts our data.
The code to decrypt our settings is quite similar:
We can use encryptString
above to encrypt and encode our settings; we can decrypt them later with decryptString
and our AES key.
Securing the Pantry Key: RSA
Now we need to encrypt the AES key with RSA. RSA is an asymmetrical algorithm, so we use a public-private key pair. We use the public key for encryption.
And we use the private key from the same key pair to decrypt.
Now we can use the RSA version of encryptString to encrypt our AES key. The RSA private key is compatible with the tools built in to API level 18, like AndroidKeyStore!
Summary & Disclaimers
If you're looking for a quick way to encrypt some local data on API levels 18-22, you may want to try RSA and AES in tandem.
I'll note that although this sample code works, if you want to use it in production code, you'll need to add some proper exception handling. If you're using Bouncy Castle or Spongy Castle, you'll want to call Security.insertProviderAt(new BouncyCastleProvider(), 1)
to be sure you're using your provider before any built-in providers.
If you're not using Bouncy Castle or Spongy Castle, RSA and AES are also available in the Java Cryptography Extension (JCE). JCE isn't too much more code (and the AES code might even be simpler, in my opinion).
I will also note that many cream cheese and butter cream frosting recipes require refrigeration. Please consider proper temperature controls before storing your baked goods in a locked pantry.