Android: магазин SecretKey в KeyStore

Я использую SecretKey для шифрования конфиденциальных данных в моем приложении. В настоящее время я храню свой SecretKey в кодированном формате Base64 в DB или SharedPrefs, который не является безопасным местом для хранения Secret на корневом телефоне. Следовательно, я хочу переместить свой SecretKey в Android KeyStore. Проблема, с которой я столкнулся, - это когда я пытаюсь этот пример кода от Google, он ожидает PrivateKey вместо SecretKey. Я не мог найти способ сохранить свой SecretKey в KeyStore и получить его для последующего использования. Я пробовал это:

private static void writeSecretKeyToKeystore(SecretKey secretKey, Context context) {
KeyStore keyStore = null;
try {
  keyStore = KeyStore.getInstance("AndroidKeyStore");
  keyStore.load(null);
  KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);
  keyStore.setKeyEntry("Key", secretKeyEntry.getSecretKey().getEncoded(), null);
} catch (KeyStoreException e) {
  e.printStackTrace();
} catch (CertificateException e) {
  e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
}

Когда я пытаюсь выполнить код выше, он генерирует исключение Operation not supported because encoding is unknown.

Любой образец кода будет очень полезен.

Ответ 1

НЕПРАВИЛЬНО
java.security.KeyStore может хранить как симметричные, так и асимметричные ключи. Вам просто нужно создать экземпляр KeyStore.SecretKeyEntry, передав ему свой SecretKey в конструкторе, а затем используйте метод KeyStore # setEntry для его сохранения:

keyStore.setEntry(
     "key1",
     new KeyStore.SecretKeyEntry(secretKey),
     new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
             .setBlockMode(KeyProperties.BLOCK_MODE_GCM)
             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
             .build());

Чтобы вернуть его, используйте:

SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);

UPDATE
После некоторых исследований я был удивлен, узнав, что AndroidKeyStore не поддерживает симметричные ключи. (см. обсуждение: https://groups.google.com/forum/#!topic/android-developers/gbmIRKRbfq8)

Ответ 2

Обходным решением будет зашифровать ваш SecretKey и сохранить его в SharedPreferences. Затем сохраните ключ для расшифровки ключа в хранилище ключей. Вот реализация с использованием косы.

public static String getBase64EncodedSecretKey(){
    Store store = new Store(context);
    Crypto crypto = new Crypto(Options.TRANSFORMATION_SYMMETRIC);
    SecretKey key = store.getSymmetricKey("key_alias", null);
    String encryptedData = PreferenceManager.getDefaultSharedPreferences(context).getString("myEncryptedSecretKey", "");
    return crypto.decrypt(encryptedData, key);
}

public static void storeKey(String base64EncodedSecretKey){
    Store store = new Store(context);
    if (store.hasKey("key_alias")) {
        store.deleteKey("key_alias");
    }
    SecretKey key = store.generateSymmetricKey("key_alias", null);
    Crypto crypto = new Crypto(Options.TRANSFORMATION_SYMMETRIC);
    String encryptedData = crypto.encrypt(base64EncodedSecretKey, key);
    PreferenceManager.getDefaultSharedPreferences(context).edit().putString("myEncryptedSecretKey",encryptedData).apply();
}

// Usage:
//store SecretKey
byte[] encodedKey = secretKeyEntry.getSecretKey().getEncoded();
String base64EncodedKey = Base64.encodeToString(encodedKey);
storeKey(base64EncodedKey);

//get SecretKey
String base64EncodedKey = getBase64EncodedSecretKey();
byte[] encodedKey = Base64.decode(base64EncodedKey);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");