You can store your private key in shared preferences, but encrypted with generated secret key, which will be stored in Android KeyStore
, which will give much more security in storing the private key.
Please see example below in Kotlin.
First, you need to generate secret key:
fun generateSecretKey(): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val spec = KeyGenParameterSpec
.Builder(secretKeyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
keyGenerator.init(spec)
return keyGenerator.generateKey()
}
It will be automatically stored in the KeyStore
since we're mentioning it as a provider when getting instance of a KeyGenerator
.
Later, when you will need to obtain secret key again you can do it like this:
fun getSecretKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val secretKeyEntry = keyStore.getEntry(secretKeyAlias, null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey
}
Or you can always use getSecretKey()
method, which will generate new one if the obtained from the KeyStore
is null
by changing last line to:
return secretKeyEntry.secretKey ?: generateSecretKey()
When SecretKey
is obtained you can proceed with encryption:
fun encrypt(data: String): ByteArray? {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
iv = cipher.iv
return cipher.doFinal(data.toByteArray())
}
Here, method encrypt
will return a ByteArray
that you can store in the SharedPreferences
.
NOTE: that you should also store initialization vector (IV). Here it is stored to the iv
property.
To decrypt stored data, use this method:
fun decrypt(encrypted: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)
val decoded = cipher.doFinal(encrypted)
return String(decoded, Charsets.UTF_8)
}
Here, you must pass store initialization vector (IV) to GCMParameterSpec
.
Hope it will helps someone.