I have an Encryption class which generates a key during encryption and store it in AndroidKeyStore but when I try to retrieve the key it is null. NT: Im calling the encrypt function and decrypt function in 2 separate class and storing the encrypted info a a sharedperefrence. A PIN that user enter is being encrypted and stored, then when they open the application and enter a PIN the stored PIN is decrypted and compared.
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Encryption() {
// Alias for the key in the Keystore
private val keyAlias = "yourKeyAlias"
// Generate a key in the Keystore
private fun generateKey() {
try {
val keyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setKeySize(256) // Adjust the key size based on your requirements
.build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
Log.d("Encryption", "Key generation successful")
} catch (e: Exception) {
Log.e("Encryption", "Key generation failed: ${e.message}")
}
}
private fun getKey(): SecretKeySpec {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
if (!keyStore.containsAlias(keyAlias)) {
// If the key entry doesn't exist, generate the key
Log.d("Encryption", "Key dont exist ")
}
val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry
val secretKey = secretKeyEntry.secretKey
// Log key information for debugging
Log.d("Encryption", "Key Algorithm: ${secretKey.algorithm}")
Log.d("Encryption", "Key Format: ${secretKey.format}")
Log.d("Encryption", "Encoded Key Material: ${secretKey.encoded?.contentToString()}")
// Ensure that the key material is non-null before creating SecretKeySpec
requireNotNull(secretKey.encoded) { "Key material is null" }
// Convert the SecretKey to SecretKeySpec
return SecretKeySpec(secretKey.encoded, "AES")
}
fun encrypt(strToEncrypt: String): Pair<ByteArray, ByteArray> {
generateKey()
val key = getKey()
val plainText = strToEncrypt.toByteArray(Charsets.UTF_8)
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv
val encryptedData = cipher.doFinal(plainText)
return iv to encryptedData
}
fun decrypt(dataToDecrypt: String, encryptedIv: String): String {
val key = getKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
val iv = Base64.decode(encryptedIv, Base64.DEFAULT)
val ivParameterSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec)
val decryptedData = cipher.doFinal(Base64.decode(dataToDecrypt, Base64.DEFAULT))
return String(decryptedData, Charsets.UTF_8)
}
}
This is the log I'm receiving with the error: D Key generation successful D Key Algorithm: AES D Key Format: null D Encoded Key Material: null
E FATAL EXCEPTION: main
Process: com.example.identitypal, PID: 8886
java.lang.IllegalArgumentException: Key material is null
at com.example.identitypal.sign_login.Encryption.getKey(Encryption.kt:59)
at com.example.identitypal.sign_login.Encryption.encrypt(Encryption.kt:67)
at com.example.identitypal.sign_login.AppPasswordSetup.savePassword(AppPasswordSetup.kt:71)
at com.example.identitypal.sign_login.AppPasswordSetup.onCreate$lambda$0(AppPasswordSetup.kt:56)
at com.example.identitypal.sign_login.AppPasswordSetup.$r8$lambda$u6wUwfaPzmksbpwedB19ZIAllp8(Unknown Source:0)
at com.example.identitypal.sign_login.AppPasswordSetup$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7792)
at android.widget.TextView.performClick(TextView.java:16112)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1213)
at android.view.View.performClickInternal(View.java:7769)
at android.view.View.access$3800(View.java:910)
at android.view.View$PerformClick.run(View.java:30218)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8751)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
It is not possible to retrieve the bytes from the key if it is in the Android keystore. To verify that the correct key value is loaded it is possible to calculate a KCV or Key Check Value. This is simply the first 3 bytes of an all zero block, encrypted directly using the intended algorithm. For AES this is 16 all zero bytes, and as mode ECB can be used (or CBC or even CTR with an all zero IV).
Security warning: if the KCV value is leaked to an adversary and CTR mode is used with an all zero IV then information may be leaked (!).
It is better to use CMAC over a known string for this, but that would require you to indicate that the key is open to use within a
Mac
.Generally the key should of course not suddenly change value, and validating if the key is valid may not be logical to be included in a protocol.