Java – Calling .getEncoded() on SecretKey returns null

Calling .getEncoded() on SecretKey returns null… here is a solution to the problem.

Calling .getEncoded() on SecretKey returns null

I use the following code to generate the AES key:

KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder("db_enc_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);

KeyGenParameterSpec keySpec = builder
                .setKeySize(256)
                .setBlockModes("CBC")
                .setEncryptionPaddings("PKCS7Padding")
                .setRandomizedEncryptionRequired(true)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(5 * 60)
                .build();

KeyGenerator keyGen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
        keyGen.init(keySpec);

SecretKey sk = keyGen.generateKey();

But every time I try to get the byte[] version of the key via sk.getEncoded(), the method returns null. The documentation says it should return the encoded key, and null if the key doesn’t support encoding, but I don’t think the key supports encoding.

I need byte[]

because I want to encrypt a Realm database (for this I need to combine 2 AES-256 keys into a byte array)[ https://realm.io/docs/java/latest/#encryption]

The official documentation uses SecureRandom, but also points out that this is a stupid practice and that keys are never stored. Therefore, I want to use the KeyStore to securely store two separate AES-256 keys.

P.S.: Code is just test code, not final product, so any comment on coding style is useless. I just want to keep a working version running at the moment.

EDIT: So I tried the code below and it successfully generated an AES key (albeit only 16 bytes in length):

SecretKey sk1 = KeyGenerator.getInstance("AES").generateKey();

When I use the getEncoded() method on it, I even get a byte array, so naturally I go ahead and save it to KeyStore:, using the following code

KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(sk1);
KeyStore.ProtectionParameter pp = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT).build();
keyStore.setEntry("db_enc_key_test", entry, pp);

This also works. So I tried to pass KeyStore.Entry entry2 = keyStore.getEntry("db_enc_key_test", null); Reading keys from the keystore also works well. But when I call entry2.getEncoded(), the method returns null again. Is this a keystore issue?

edit2: So I just found out that symmetric keys generated in the keystore (and apparently saved to) are not exportable in Android M, which seems to be intentional, which has caused me some problems because I need the key itself to encrypt the Realm database.

Are there some Best Practices Realm developer recommendations for here?

Solution

The fact that you can’t retrieve the encoded key is by design, because the keystore should be the only one who knows it. However, you can use a two-tier key:

  1. Generate a secret key along with it and store it in the keystore.

  2. Generate the “

  3. real” key used by Realm and encrypt it with the key in the keystore.

  4. Now you have some completely random text that can be stored, for example, in SharedPreferences or in a disk file.

  5. Whenever people want to open Realm,

  6. read the encryption key on disk, decrypt it using the Keystore, and now you can use it to open Realm.

Here this repo uses the same technique to save user data in a secure way: https://github.com/realm/realm-android-user-store

This could be the class you want to learn: https://github.com/realm/realm-android-user-store/blob/master/app/src/main/java/io/realm/android/CipherClient.java It also handles fallback (Keystore) through various Android versions There are a lot of quirks).

Related Problems and Solutions