Android: Store SecretKey in KeyStore
Asked Answered
G

2

12

I use a SecretKey to encrypt sensitive data in my application. Currently I am storing my SecretKey in Base64 encoded format in DB or SharedPrefs which is not a safe place to store Secret on a rooted phone. Hence, I want to move my SecretKey to Android KeyStore. The problem I am facing is when I try this sample code from Google, it expects a PrivateKey instead of SecretKey. I couldn't figure out a way to store my SecretKey in KeyStore and fetch it for later use. I tried this:

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();
}

When I try above code, it throws an exception Operation not supported because encoding is unknown.

Any sample code would be of great help.

Goudy answered 19/4, 2016 at 6:26 Comment(4)
try the following: keyStore.setEntry("Key", secretKeyEntry, null);Ul
It won't compile, saying Wrong 2nd argument type. Found 'SecretKeyEntry'; required 'byte[]'.Goudy
Please change 'setKeyEntry' to 'setEntry'.Ul
Damn! I changed it to setEntry now and I see this exception java.security.KeyStoreException: Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was SecretKeyEntry: algorithm - AESGoudy
U
7

WRONG
java.security.KeyStore can store both symmetric and asymmetric keys. You just need to instantiate KeyStore.SecretKeyEntry passing it your SecretKey in the constructor and then use the KeyStore#setEntry method to save it:

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());

To get it back out use:

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

UPDATE
After some research I was surprised to find out, that AndroidKeyStore doesn't support symmetric keys. (see the discussion: https://groups.google.com/forum/#!topic/android-developers/gbmIRKRbfq8)

Ul answered 19/4, 2016 at 7:7 Comment(6)
I don't see any method on KeyStore which accepts SecretKeyEntry object. Also, your answer mentions how to get entry, more than how to set entry. Please check my updated question.Goudy
both methods (getEntry and setEntry) deal with the KeyStore.Entry interface (see: developer.android.com/reference/java/security/…). KeyStore.SecretKeyEntry implements this interface, so you can use it everywhere that KeyStore.Entry is expected.Ul
This won't work on API < 23. I am developing an app starting with Lollipop. I use this code keyStore.setEntry( "key1", new KeyStore.SecretKeyEntry(secretKey), new KeyStoreParameter.Builder(context).setEncryptionRequired(true).build()); but it wouldn't work. Upvoted your answer though.Goudy
I tried API 28, getting SecretKeyEntry from a keystore, returns null. Java SE: no problem.Gavan
SecretKeyEntry certainly works for recent Android versions (API 28), here is an example: github.com/fkirc/secure-zip-notes . However, I believe that AndroidKeyStore does not support any symmetric cryptography for API versions below 23.Composed
Update: For API versions below 23, you can fallback to KeyStore.getInstance("BouncyCastle"). Of course, this does not provide the hardware security capabilities of modern devices.Composed
I
0

The work-around would be to encrypt your SecretKey and store it in SharedPreferences. Then store the key to decrypt your key in the Keystore. Here's an implementation using scytale.

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");
Indigo answered 1/4, 2019 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.