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)
2

There are 2 answers

1
Maarten Bodewes On

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).

import java.security.Key;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;

public class KCVGenerator {

    public static byte[] generateKCV(String keyAlias) throws Exception {
        // Load the Android Key Store
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);

        // Retrieve the AES key
        Key key = keyStore.getKey(keyAlias, null);
        if (key == null) {
            throw new Exception("Key not found");
        }

        // Create a Cipher instance and initialize it for encryption
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        byte[] iv = new byte[16]; // 16-byte IV (initialized to zeros)
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

        // Encrypt a block of 16 zeros
        byte[] encrypted = cipher.doFinal(new byte[16]);

        // Extract the first 3 bytes as KCV
        byte[] kcv = new byte[3];
        System.arraycopy(encrypted, 0, kcv, 0, 3);

        return kcv;
    }

    public static void main(String[] args) {
        try {
            byte[] kcv = generateKCV("your_key_alias");
            System.out.println("KCV: " + bytesToHex(kcv));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Utility method to convert byte array to hex string
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }
}

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.

0
therock24 On

The purpose of Android KeyStore is to generate a pair of keys and store them safely, without exposing the PrivateKey or SecretKey.

This means that you will never be able to extract the bytes (secretKeyEntry.secretKey) of the SecretKey as it will always return null.

So instead of trying to build a SecretKeySpec from the secretkey, the correct thing to do is to just use the secretKeyEntry.secretKey for your encrypt and decrypt functions.

private fun getKey(): SecretKey {
    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 does not exist")
        generateKey()
        // or just return
    }

    val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry
    return secretKeyEntry.secretKey
}