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.
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)?
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 = "";
}
}
}