Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Import encrypted AES key into Android Keystore and store it under new alias

I am just familiarizing myself with the Android Keystore API. I found out that the following features are available:

  • At least on some devices the Android Keystore is hardware backed, meaning that crypto operations run in a secure environment (TEE).
  • When the keystore is hardware backed, private RSA keys as well as secret symmetric keys that have been created within the Keystore can be configured to never leave the Keystore and the raw keys cannot be read out even with root access.

I am wondering now if the following is possible:

  1. Generate a Public/Private key pair where the private key never leaves the Keystore
  2. Upload the public key of this pair to a server
  3. On the server: create a random symmetric AES key and encrypt it with the public RSA key uploaded by the user
  4. On the device: Download this encrypted AES key
  5. Import it into the hardware backed Keystore such that it is decrypted in there with the private key of the pair and stored under a new alias
  6. Use this new key alias to perform symmetric encryption and decryption

1-4 should be possible, the missing link for me now is point 5. in this list. Can some one help me out and tell me if this is possible at all and/or point me to the correct API reference?

I found this: https://android.googlesource.com/platform/development/+/master/samples/Vault/src/com/example/android/vault/SecretKeyWrapper.java

But it looks to me as if the unwrapping of the secret key happens in the normal environment and the decrypted AES key would be available in the App, which would not satisfy my security requirements.

Update:

I created a small test project using the linked SecretKeyWrapper and here are two code snippets:

The first one does the following:

  1. Create a random AES key (not within in the keystore, this is what would happen on a server later). Obviously the raw key can be retrieved from the generated SecretKey object what isn't a problem since the server can know the key.
  2. Encrypt/wrap the key with a RSA public key that was created in the client's Android Keystore (this would also happen on a server).
  3. Decrypt the key again with the RSA private key (this would happen on the client and actually happens within the TEE in the example).

Snippet 1:

SecretKeyWrapper secretKeyWrapper = new SecretKeyWrapper(this,"testKeyRsa");

// Generate a random AES key (not in the keystore) [1]
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKeyGenerated = keyGen.generateKey();
byte[] secretKeyGeneratedRaw = secretKeyGenerated.getEncoded();

// wrap this key with the RSA key from the keystore [2]
byte[] wrappedKey = secretKeyWrapper.wrap(secretKeyGenerated);

// unwrap it again with the RSA key from the keystore [3]
SecretKey unwrappedKey = secretKeyWrapper.unwrap(wrappedKey);

// the raw key can be read again [4]
byte[] unwrappedKeyRaw = secretKeyGenerated.getEncoded();

What I want to achieve is that the unwrapped key from [3] is stored in the Keystore with a new alias without returning the raw key. Of course I could easily import the SecretKey object into the Keystore here, but the problem is, that at this point the raw key can be retrieved from the object with the statement [4] what induces a security flaw. It is clear that the unwrapping/decryption already happens in the Keystore/TEE, since the private RSA key that is used for the decryption lives in the Keystore and cannot be retrieved.

If I compare this to the situation where a random secret AES key is created in the keystore, I notice that different types (implementing the SecretKey Interface) are returned. In the above example, the type is SecretKeySpec, whereas for keys which are returned from the Android Keystore (see snippet 2 below), "opaque" types are used where the getEncoded() method always returns null. In the following example, the type of keyAesKeystore is AndroidKeyStoreSecretKey.

Snippet 2:

// create a new AES key in the keystore
KeyGenerator keyGenAndroid =  KeyGenerator.getInstance("AES","AndroidKeyStore");
keyGenAndroid.init(
    new KeyGenParameterSpec.Builder("testKeyAes",
         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)                             
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)                             
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build());
        SecretKey keyAesKeystore = keyGenAndroid.generateKey();

// this returns null
byte[] keyAesKeystoreRaw = keyAesKeystore.getEncoded();

So to rephrase the question: Is it somehow possible to securely import a RSA wrapped AES key into the Android Keystore without revealing the secret key to the application?

Update 2:

@Robert makes the absolutely valid in the answer below that it actually does not matter if the unwrapping happens in the TEE or in the Rich OS (App) since the App (or a tampered version) could always later (after intercepting the wrapped key) just "use" the private RSA key from the Keystore to unwrap the AES key (without the need to access the raw private key at all).

Here is another thought though: I found that it is possible to set Key Protection Parameters for keys in the Android Keystore (see here).

The linked implementation for the SecretKeyWrapper does not use such protection parameters. After changing the generateKeyPair method as follows and adding the PURPOSE_DECRYPT and PURPOSE_ENCRYPT properties everything still works.

private static void generateKeyPair(Context context, String alias)
        throws GeneralSecurityException {
    final Calendar start = new GregorianCalendar();
    final Calendar end = new GregorianCalendar();
    end.add(Calendar.YEAR, 100);
    final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT)
            .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)

            .build();
    final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    gen.initialize(keyGenParameterSpec);
    gen.generateKeyPair();
}

I can now protect the RSA key such that it cannot be used for decryption by removing the PURPOSE_DECRYPT property. As expected the Cipher.unwrap method stops working and throws an Incompatible purpose exception then.

So what I would need then is a protection property where the plain decrypt functionality is blocked but which allows such a "secure import functionality" I am looking for. Something like a "PURPOSE_IMPORT" which apparently does not exist.

like image 964
nullwinder Avatar asked Aug 31 '16 12:08

nullwinder


1 Answers

What you're looking for now exists, as of API level 28 (Android Pie). To use you you need to:

  • Create a wrapping key pair, an RSA key pair with purpose PURPOSE_WRAP_KEY. You should also generate an attestation for the public key to verify that the private key is a keystore key, in secure hardware.
  • Send the public key (and attestation) from your app to the server that will provide the wrapped symmetric key.
  • On the server, you need to wrap the symmetric key. This involves more than just encrypting it, because the wrapper needs to contain not just the key material but also the authorization list that defines how the key may be used. This is done by packaging the key and authorization info in an ASN.1 DER-encoded structure, per the schema documented here. There's some sample wrapping code in the CTS test. Note that if this format seems excessively complicated (e.g. the optional "masking key"), it's because in a future Android release there will be a corresponding secure export function and the use cases for that require the additional complexity. The secure export function didn't make it into Q, but will probably make it into R.
  • Send the wrapped key to the app, which must create a WrappedKeyEntry and use Keystore.setEntry() to store it.

This should work on any device with API level 28. However, if the device has a Keymaster version < 4 (see the attestation certificate to find out what version of Keymaster is present), then the unwrapping operation will return the wrapped key material to Android userspace. Keymaster version 4 (or above) will keep the unwrapped material in secure hardware, but because lower versions don't have support for the wrapped key feature, it has to be sort of emulated.

What happens if you have a lower Keymaster version is that when you create a PURPOSE_WRAP_KEY key pair, what is actually requested of the secure hardware is a PURPOSE_DECRYPT key pair. Then when you do the import, the keystore daemon uses this PURPOSE_DECRYPT private key to decrypt the secret from the wrapper, then it imports the secret into the secure hardware and wipes the userspace memory that held it. So, the key material exists in the keystore daemon's memory for a fraction of a millisecond. Again, if the device has Keymaster version 4+ it is only unwrapped inside the secure hardware and never leaves.

like image 121
divegeek Avatar answered Sep 17 '22 12:09

divegeek