1

I'm working on using KeyStore to store my app secrets securely in Android. However, I've found that for devices with Android 7.1.1 and below, the stored key using KeyStore is not 'hardware-backed?'i.e. whenever I called isInsideSecureHardware() method to the KeyInfo, it will always return me 'false'.

As far as I tested, this happen even if the device has the 'Storage type = Hardware-backed' in Settings->Security->Credential Storage->Storage type. But this is not the case for Android 7.1.2 and above which always return me 'true' for isInsideSecureHardware() as long as the Credential Storage type is hardware-backed; never tried with other types.

  1. Is there anyway to force the key to be stored inside secure hardware for Android 7.1.1 down to 4.3 (per my understanding this is where HSM in KeyStore is introduced; please correct me if I'm wrong)?

  2. Can anyone clarify what happen if the stored key is not inside the secure hardware; where is the key stored? how secure it is? To make thing easy, what are the differences between isInsideSecureHardware == true vs isInsideSecureHardware == false?

Here is snippet of my code to inject the key and check the KeyInfo (yes I'm asking down to Android 4.3...please ignore the version check in the code):

private void injectKey(Context context, String keyName){
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        end.add(Calendar.YEAR, 1);

        keyGenerator.init(new KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setRandomizedEncryptionRequired(true)
                .setKeyValidityStart(start.getTime())
                .setKeyValidityEnd(end.getTime())
                //.setUserAuthenticationRequired(true) //need PIN to get key
                //.setUserAuthenticationValidityDurationSeconds(86400) //1-day time can use key from entering PIN
                //.setUnlockedDeviceRequired(true) API level 28
                //.setIsStrongBoxBacked(true) API level 28
                .build()
        );
    }

    SecretKey secretKey = keyGenerator.generateKey();

    //check key info
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), "AndroidKeyStore");
        KeyInfo keyInfo;

        try {
            keyInfo = (KeyInfo) factory.getKeySpec(secretKey, KeyInfo.class);
            boolean insideHW = keyInfo.isInsideSecureHardware();
            boolean authReq = keyInfo.isUserAuthenticationRequired();
            boolean authHW = keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware();
            String[] blockModes = keyInfo.getBlockModes();
            String[] digests = keyInfo.getDigests();
            String[] encryptionPaddings = keyInfo.getEncryptionPaddings();
            int keySize = keyInfo.getKeySize();
            Date keyValidityStart = keyInfo.getKeyValidityStart();
            Date keyEndValid = keyInfo.getKeyValidityForConsumptionEnd();
            String keyAlias = keyInfo.getKeystoreAlias();
            int keyPurpose = keyInfo.getPurposes();
            String [] signaturePaddings = keyInfo.getSignaturePaddings();
            int authType = keyInfo.getUserAuthenticationValidityDurationSeconds();

            MainActivity.showMessage(context, "Key Info",
                    "inside HW = " + insideHW + "\n" +
                            "auth Req = " + authReq + "\n" +
                            "auth HW = " + authHW + "\n" +
                            "keySize = " + keySize + "\n" +
                            "keyValidityStart = " + keyValidityStart + "\n" +
                            "keyEndValid = " + keyEndValid + "\n" +
                            "keyAlias = " + keyAlias + "\n" +
                            "keyPurpose = " + keyPurpose + "\n" +
                            "authType = " + authType + "\n");

            String checkKeyInfo = "";
        } catch (InvalidKeySpecException e) {
            String checkKeyInfo = "";
        }
    }
}
Cousin Roy
  • 191
  • 1
  • 6

1 Answers1

1

The versions don't quite align with your findings, though the Android hardware keystore only supported storing of AES keys from API 23 onwards - you might have more success using RSA keys, as these were supported from API 18 onwards.

Link to relevant docs on developer.android.com

This is why examples exist on the internet of "How to encrypt using the hardware keystore" recommend creating an RSA keypair in the keystore, using SecureRandom or similar to create your 256bit AES key, and then using the private RSA key to encrypt the AES key, and store the result in SharedPreferences. This works on API 18+ without changes.

So try with RSA keys and you'll have more luck, but still not pre API 18.

Terry Kay
  • 11
  • 1