isInsideSecureHardware() возвращает false для Android ‹= 7.1.1. Как заставить ключ храниться внутри оборудования?

Я работаю над использованием KeyStore для безопасного хранения секретов приложений в Android. Однако я обнаружил, что для устройств с Android 7.1.1 и ниже сохраненный ключ с использованием KeyStore не имеет аппаратной поддержки? всякий раз, когда я вызывал метод isInsideSecureHardware() для KeyInfo, он всегда возвращал мне «ложь».

Насколько я проверял, это происходит, даже если устройство имеет «Тип хранилища = Аппаратное обеспечение» в «Настройки» -> «Безопасность» -> «Хранилище учетных данных» -> «Тип хранилища». Но это не относится к Android 7.1.2 и более поздним версиям, которые всегда возвращают мне «true» для isInsideSecureHardware(), если тип хранилища учетных данных поддерживается аппаратно; никогда не пробовал с другими типами.

  1. Есть ли способ заставить ключ храниться внутри защищенного оборудования для Android 7.1.1 до 4.3 (насколько я понимаю, именно здесь представлен HSM в KeyStore; пожалуйста, поправьте меня, если я ошибаюсь)?

  2. Может ли кто-нибудь прояснить, что произойдет, если сохраненный ключ не находится внутри защищенного оборудования; где хранится ключ? насколько это безопасно? Чтобы упростить задачу, в чем разница между isInsideSecureHardware == true и isInsideSecureHardware == false?

Вот фрагмент моего кода для ввода ключа и проверки KeyInfo (да, я прошу до Android 4.3... пожалуйста, игнорируйте проверку версии в коде):

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

person Cousin Roy    schedule 17.06.2020    source источник


Ответы (1)


Версии не совсем совпадают с вашими выводами, хотя аппаратное хранилище ключей Android поддерживало только хранение ключей AES, начиная с API 23 — вы могли бы добиться большего успеха, используя ключи RSA, поскольку они поддерживались, начиная с API 18.

Ссылка на соответствующие документы на сайте developer.android.com

Вот почему в Интернете существуют примеры того, как шифровать с помощью аппаратного хранилища ключей, рекомендуется создать пару ключей RSA в хранилище ключей, использовать SecureRandom или аналогичный для создания 256-битного ключа AES, а затем использовать закрытый ключ RSA для шифрования ключа AES, и сохранить результат в SharedPreferences. Это работает на API 18+ без изменений.

Так что попробуйте с ключами RSA, и вам повезет больше, но все же не до API 18.

person Terry Kay    schedule 29.01.2021