Securely Storing Keys in Android Keystore

2019-07-13 07:37发布

问题:

I am making an android application that communicates with a server. I'm using token based authentication on my server, and to pass information to the client from the server, I am using asymmetric encryption.

This is how the process goes

  1. Generated public and private key already exists before hand
  2. Public key is used to encrypt information, and then passed from server to client
  3. App uses private key to decrypt information

However, I do not know how to securely store the private key in the keystore. If I store it during runtime, the key will be out in the code, and if I send the private key during the REST connection, then there's no point of having the encryption because a hacker can find both keys. Can anyone help me on creating the best possible solution? THX in advance!

回答1:

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.