I'm working on an app that needs to be quite secure, so we want to use the Android Keystore to help make certain things, like accessTokens to the backend, safer and more secure.
I've tried implementing some of it, and it seems like it works, but I worked mainly off of examples, primarily Securely Storing Keys in Android Keystore
Here is some of my code:
object AndroidKeyStore {
const val ANDROID_KEY_STORE = "AndroidKeyStore"
const val AES_MODE = "AES/GCM/NoPadding"
private var iv: ByteArray = byteArrayOf(55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44)
//IS IT SAFE TO HAVE THIS HERE?
const val SECRET_ALIAS = "TEST"
private fun generateSecretKey(keyAlias: String): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
val spec = KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build()
keyGenerator.init(spec)
return keyGenerator.generateKey()
}
private fun getSecretKey(keyAlias: String): SecretKey {
val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) }
if (keyStore.getEntry(keyAlias, null) != null) {
val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey ?: generateSecretKey(keyAlias)
}
return generateSecretKey(keyAlias)
}
fun encrypt(data: String): String {
val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(SECRET_ALIAS), GCMParameterSpec(128, iv))
iv = cipher.iv
val encodedBytes = cipher.doFinal(data.toByteArray())
return Base64.encodeToString(encodedBytes, Base64.NO_WRAP)
}
fun decrypt(encrypted: String): String {
val cipher = Cipher.getInstance(AES_MODE)
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(SECRET_ALIAS), spec)
val encodedBytes = Base64.decode(encrypted, Base64.NO_WRAP)
val decoded = cipher.doFinal(encodedBytes)
return String(decoded, Charsets.UTF_8)
}
}
This is my keystore code, that seems like it encrypts/decrypts stuff okay. But I have a hard time understanding exactly what the Secret alias is and where to put it, safely.
At the same time, I'm also using a secure preference store. So my idea was, to keep the accessToken in the secure preference store, but encrypt it with this keystore code.
Here is the code for the Secure pref store.
object SecurePreferenceUtils {
enum class SecurePreferenceKeys {
AccessToken, Test
}
fun putString(key: SecurePreferenceKeys, value: String) {
SecurePrefs.securePreferences.edit().putString(key.name, value).commit()
}
fun getString(key: SecurePreferenceKeys, defaultValue: String): String {
return SecurePrefs.securePreferences.getString(key.name, defaultValue) ?: ""
}
object SecurePrefs {
lateinit var securePreferences: SecurePreferences
}
In the end, I would like to be able to do like this when I get the accessToken from the backend.
SecurePreferenceUtils.putString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt(""))
val token = AndroidKeyStore.decrypt(SecurePreferenceUtils.getString(SecurePreferenceUtils.SecurePreferenceKeys.AccessToken, AndroidKeyStore.encrypt("")))
Hopefully I make some sort of sense on where I want to go, I'm just not quite sure I'm on the right path here.
Any help is appreciated!