Sf developers android najafzadeh storage header

Secure Storage in Android

Kayvon covers different ways to persist data on Android devices through examples and offers suggestions for a more secure way to do it.


Introduction

My name Kayvan Najafzadeh Ilkhechi. I am a UC Berkeley alumni and am a fan of the Android platform - I started developing for Android around 2010.

I started working at Doximity in 2013. Doximity is the largest professional network for U.S. healthcare professionals. We have more than 70% of physicians in U.S. in our network and we love to hire talented people; feel free to check us out at doximity.com/jobs.

Persisting Data

In general, there are two main ways to store data.

One is on memory, or RAM, which has a faster read and write. Your data lives on the RAM as long as your objects do in your code. If your object gets disconnected, that means it doesn’t exist in the RAM anymore.

On the other hand, is writing to disk. This is slower to read and write on. The advantage of writing on disk is that it can persist after your application terminates.

Imagine the scenario where Alice loses her Android device. Eve finds Alice’s device and gets root access and all the data that resides on the phone. The goal of the talk is to make it as difficult as possible for Eve to extract any useful or sensitive data Alice’s device.

Overall, Android provides four different types of storage.

Shared Preferences

The first is Shared Preferences. Per the Android documentation, Share Preferences a small collection of key values, and can be private or shared.

Get more development news like this

Here is a Java snippet of code that stores sensitive data on shared preferences.

	SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
	SharedPreferences.Editor editor = sharedPreferences.edit();
	editor.putString("DATA", "my very sensitive data");
	editor.commit();

It’s easy to root an Android device. With an emulator, you can use adb root to gain root access. Then you can use adb shell to gain shell access to the device.

Once you enter the path to the shared preferences XML file, you will find the sensitive data.

You may wonder how Shared Preferences is even private? Private in this instance only means that the information is not exposed to other applications. Each application has its own user ID on the system level, and no two user IDs can share the same private data or stored file.

SQLite

SQLite is a database on the device that is shipped with Android.

	MySqlHelper mySqlHelper = new MySqlHelper(this);
	SQLiteDatabase db = mySqlHelper.getWritableDatabase();
	ContentValues values = new ContentValues();
	values.put("input_text", "my very sensitive data");
	db.insert("mydata", null, values);

Here, a user can still gain root access to the data, but the difference is that it’s not in plain text or an XML file. Though it’s a database file, a user can still open it with SQLite and do a query to view the sensitive data.

Internal Storage

The third option for persistence is internal storage. Here, I’m creating a new file called myfile.txt.

	String filename = "myfile.txt";
	FileOutputStream outputStream;
	try {
	    outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
	    outputStream.write("my very sensitive data".getBytes());
	    outputStream.close();
	} catch (Exception e) {
	    e.printStackTrace();
	}

Like with SQLite, you can still go in the shell to read the data.

Realm

Realm is another database not based on SQLite. Realm stores all of its data on a file, and its content is accessible by the root.

Android Keystore System

The conclusion is that there is no safe place to store data on disk. You can use encryption to prevent an attacker from being able to understand the data when they gain access to it.

An encryption algorithm will require a key, but where should the key be stored? Fortunately, the Android Keystore System can help with this. Its sole purpose is to store keys, so it does not work like a database. You cannot store whatever data you want. It’s not a database.

The way Android does it is Android Keystore System doesn’t give us the keys. We are asking Android Keystore System to do some operations for us.

The following creates a key and stores it in the Android Keystore System.

	KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
	keyStore.load(null);
	boolean containsAlias = keyStore.containsAlias("com.your.package.name");

	if (!containsAlias) {
	    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
	    Calendar start = Calendar.getInstance(Locale.ENGLISH);
	    Calendar end = Calendar.getInstance(Locale.ENGLISH);
	    end.add(Calendar.YEAR, 1);
	    KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
	            .setAlias("com.your.package.name")
	            .setSubject(new X500Principal(CA))
	            .setSerialNumber(BigInteger.ONE)
	            .setStartDate(start.getTime())
	            .setEndDate(end.getTime())
	            .build();
	    kpg.initialize(spec);
	    kpg.generateKeyPair();
	}
	return keyStore.getEntry(ALIAS, null);

First, we start by grabbing an instance of Android Keystore. Then we look up the alias for our key. An alias should be unique, and it’s recommended to use your package name. If the alias exists, we can return the key entry. If not, we have to create the keys. In this case, we are using RSA to create a key pair of public and private keys.

	KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
	keyStore.load(null);
	boolean containsAlias = keyStore.containsAlias("com.your.package.name");

	if (!containsAlias) {
	    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
	    Calendar start = Calendar.getInstance(Locale.ENGLISH);
	    Calendar end = Calendar.getInstance(Locale.ENGLISH);
	    end.add(Calendar.YEAR, 1);
	    KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
	            .setAlias("com.your.package.name")
	            .setSubject(new X500Principal(CA))
	            .setSerialNumber(BigInteger.ONE)
	            .setStartDate(start.getTime())
	            .setEndDate(end.getTime())
	            .build();
	    kpg.initialize(spec);
	    kpg.generateKeyPair();
	}
	return keyStore.getEntry(ALIAS, null);

Now that we’ve created our key pair, we can use our public key and private key in order to encrypt and decrypt our data.

	private static byte[] encryptUsingKey(PublicKey publicKey, byte[] bytes) {
	    Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
	    inCipher.init(Cipher.ENCRYPT_MODE, publicKey);
	    return inCipher.doFinal(bytes);
	}

	private static byte[] decryptUsingKey(PrivateKey privateKey, byte[] bytes) {
	    Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
	    inCipher.init(Cipher.DECRYPT_MODE, privateKey);
	    return inCipher.doFinal(bytes);
	}

These methods are essentially identical except for using encryption mode on one with public key and decryption mode in the other with the private key.

	public static String encrypt(Context context, String plainText) {

	    KeyStore.Entry entry = createKeys(context, null);
	
    if (entry instanceof KeyStore.PrivateKeyEntry) {

	        Certificate certificate = ((KeyStore.PrivateKeyEntry) entry).getCertificate();
	        PublicKey publicKey = certificate.getPublicKey();

	        byte[] bytes = plainText.getBytes(UTF_8);
	        byte[] encryptedBytes = encryptUsingKey(publicKey, bytes);
	        byte[] base64encryptedBytes = Base64.encode(encryptedBytes, Base64.DEFAULT);
	        return new String(base64encryptedBytes);
	    }
	    return null;
	}

With the encrypted bytes, we need to use Base64 encryption because those bytes might not be anything that we can actually see with our key characters. We do a Base64 encoder around it and return it as a string.

	public static String decrypt(String cipherText) {
	    KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
	    keyStore.load(null);

	    KeyStore.Entry entry = keyStore.getEntry(ALIAS, null);
	    if (entry instanceof KeyStore.PrivateKeyEntry) {

	        PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
	
        byte[] bytes = cipherText.getBytes(UTF_8);

	        byte[] base64encryptedBytes = Base64.decode(bytes, Base64.DEFAULT);

	        byte[] decryptedBytes = decryptUsingKey(privateKey, base64encryptedBytes);

	        return new String(decryptedBytes);
	    }
	    return null;
	}

Additional Tools

Now that we have all of these mechanisms in place, we can use all the great tools that database people are creating.

For example, one of the examples is SQLCipher for Android which is an open source library to encrypt all of your data stored in SQLite in a straightforward manner. Using it, you can generate a secret key, and you can store that however you like.

The same thing can be done using Realm. Realm provides a great infrastructure for encrypting and decrypting. Here, we encrypt our key and store it in shared preferences.

The good thing about using these kinds of databases, either SQLite, Cipher or Realm, are that you don’t need to worry about encrypting and decrypting data on the fly every time we want to store. You just pass your keys and it will do the rest for you.

A Warning

About a year and a half ago, I was building an app for my company, Doximity. I used Realm, along with the same mechanism I showed above. A couple of days after pushing to production, the app began to crash, but I was not able to reproduce it.

The issue was when you when you switch devices or log on using another device. The Realm database got encrypted on that device, and the key was not available on the new device.

Realm has a repository that covers much of the above and contains a lot of documentation.

Next Up: Android Architecture Components and Realm

General link arrow white

About the content

This content has been published here with the express permission of the author.

Kayvan Najafzadeh

Kayvan Najafzadeh is an Android engineer with a degree in computer science from UC Berkeley. He started working with Android around 2010 and fell in love with the platform. An advocate for open-source, he has contributed to multiple open-source projects and started many of his own. He is currently working at Doximity to help healthcare professionals save lives.

4 design patterns for a RESTless mobile integration »

close