Search code examples
androidkotlinencrypted-shared-preference

Android API 21 Create Custom Master Key


I'm trying to create an encrypted SharedPreferences implementation but the example given in the Android website is for API 23 and above. Specifically, the problem is creating a master key using this code is MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC).

On the current version of androidx security ('androidx.security:security-crypto:1.1.0-alpha01'), you could technically create the implementation for EncryptedSharedPreferences except that the getOrCreate() function state above is for API 23 and above only. So if I understood it correctly, the only thing missing for me to be able to do the following lines of code:

private fun createSharedPref(context: Context): SharedPreferences {
    return create(
        "secret_shared_prefs",
        masterKeyAlias,
        context,
        PrefKeyEncryptionScheme.AES256_SIV,
        PrefValueEncryptionScheme.AES256_GCM
    )
}

Is to create my own custom MasterKey. Is there a way to do that in API 21?

Here's how I coded it so far:

class SharedPreferencesUtil {
    companion object {
        private val masterKeyAlias = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
        } else {
            TODO("VERSION.SDK_INT < M")
            //I need help here
        }


        private fun createSharedPref(context: Context): SharedPreferences {
            return create(
                "secret_shared_prefs",
                masterKeyAlias,
                context,
                PrefKeyEncryptionScheme.AES256_SIV,
                PrefValueEncryptionScheme.AES256_GCM
            )
        }


        fun saveObject(context: Context, key: String, data: Any) {
            val gson = Gson()
            val json = gson.toJson(data)
            val prefs = createSharedPref(context)
            prefs.edit().putString(key, json).apply()
        }


        fun getJson(context: Context, key: String): String {
            val prefs = createSharedPref(context)
            val json = prefs.getString(key, "null")
            return json!!
        }


        fun clearPreferences(context: Context) {
            val prefs = createSharedPref(context).edit()
            //remove my data
        }
    }
}

Solution

  • I found a working solution (source)

    I replaced my implementation of the master key to as follows:

    private fun createMasterKey(context: Context): String {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
        } else {
            val alias = "your_alias"
            val start: Calendar = GregorianCalendar()
            val end: Calendar = GregorianCalendar()
            end.add(Calendar.YEAR, 30)
    
            val spec =
                KeyPairGeneratorSpec.Builder(context) 
                    .setAlias(alias)
                    .setSubject(X500Principal("CN=$alias"))
                    .setSerialNumber(
                        BigInteger.valueOf(
                            Math.abs(alias.hashCode()).toLong()
                        )
                    )
                    .setStartDate(start.time).setEndDate(end.time)
                    .build()
    
            val kpGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
                "RSA",
                "AndroidKeyStore"
            )
            kpGenerator.initialize(spec)
            val kp: KeyPair = kpGenerator.generateKeyPair()
            kp.public.toString()
        }
    }