ian.shaun.thomas
ian.shaun.thomas

Reputation: 3488

Using AES with AndroidKeyStore

Android M provides AES support via the AndroidKeyStore however I can not find any combination of generating a key that provides a method of full encryption and decryption without a user password/device locking. It appears that my current approach is appropriate for these requirements as the key store is storing my key, I can load the key and perform encryption and in the case that I hold onto the IV from the encryption process I can decrypt the data.

Unfortunately in the real world use case I can not hold onto the IV for decryption at a later time without writing it to disk, maybe this is what I should be doing?

I have glanced at the updated key store and related testing in the SDK but was unable to find any test cases I could use as example. The examples also seem to be devoid of actually using AndroidKeyStore generated SecretKeys without tying them to device locking/finger prints.

I have created a repository to try and highlight what I have done along with some comments explaining where my issue is. The relevant code is also included below.

For clarity, my question is simply how can I generate an AndroidKeyStore backed AES SecretKey that allows me to encrypt and decrypt via cipher input/output streams without writing the IV to disk or using the fingerprint/device locking approaches?

https://github.com/ToxicBakery/AES-Testing/blob/master/app/src/androidTest/java/com/toxicbakery/app/aes/AesTest.java

    final String suchAlphabet = "abcdefghijklmnopqrstuvwxyz";

    KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
    keyStore.load(null);

    /*
    KEY GENERATION
     */

    // Define the key spec
    KeyGenParameterSpec aesSpec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setKeySize(128)
            .build();

    // Create the secret key in the key store
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
    keyGenerator.init(aesSpec);
    keyGenerator.generateKey();

    Cipher cipher;
    SecretKey secretKey;

    /*
    ENCRYPTION
     */

    // Load the secret key and encrypt
    secretKey = ((KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null)).getSecretKey();
    cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
    cipherOutputStream.write(suchAlphabet.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();

    /*
    DECRYPTION
     */

    // Load the secret key and decrypt
    secretKey = ((KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null)).getSecretKey();

    // The following two lines attempt to represent real world usage in that the previous line loaded
    // the key from the store and the next two lines attempt to create the cipher and then initialize
    // the cipher such that an IV can be extracted as it does not seem that you can use the spec or the
    // parameters. Interestingly, the following two lines only 'half' such that a-p fail to decrypt and
    // q-z decrypt successfully 100% of the time. Leaving the lines commented results an in a successful
    // decryption of the alphabet but this is not a usable scenario
    //
    //        cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
    //        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    IvParameterSpec ivParameterSpec = new IvParameterSpec(cipher.getIV());
    cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);

    byte[] in = new byte[suchAlphabet.getBytes().length];
    ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
    CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
    IOUtils.readFully(cipherInputStream, in);
    cipherInputStream.close();

    /*
    VERIFY
     */

    String muchWow = new String(in);
    assertEquals(suchAlphabet, muchWow);

Upvotes: 4

Views: 9631

Answers (2)

divegeek
divegeek

Reputation: 5022

Alex's answer above is the best one, but note that there is another option: Set the IV yourself to a fixed value, or a value you can consistently derive. Encrypting a given piece of data more than once with the same IV is insecure, so AndroidKeyStore discourages it. But, if you are sure you want to, you can use setRandomizedEncryptionRequired, like:

KeyGenParameterSpec aesSpec = new KeyGenParameterSpec.Builder(ALIAS, //...
    // ...
    .setRandomizedEncryptionRequired(false)
    .build()

to allow you to provide the IV, then in the Cipher init call you can add third argument, an IvParameterSpec object. For example:

cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(myIv));

Do the same for decryption, with the same IV value, and it will decrypt correctly.

To reiterate, this approach is NOT RECOMMENDED. Unless you understand exactly why it's a bad idea and have very specific reason to know it's okay in your case, it's better to let the keystore generate a random IV. It's usually not hard to find a place to store the IV with the ciphertext.

Upvotes: 3

Alex Klyubin
Alex Klyubin

Reputation: 5732

Save IV next to the ciphertext and then use the IV to decrypt the ciphertext later. Why is it that in your real world scenario you cannot save the IV persistently?

Upvotes: 2

Related Questions