Secure data in Android — Initialization Vector

Yakiv Mospan
ProAndroidDev
Published in
4 min readApr 1, 2018

--

This article is a part of “Secure data in Android” series:

  1. Encryption
  2. Encryption in Android (Part 1)
  3. Encryption in Android (Part 2)
  4. Encrypting Large Data
  5. Initialization Vector
  6. Key Invalidation
  7. Fingerprint
  8. Confirm Credentials

Those describes the “Secure data in Android” workshop topics. Sample application with full code snippets is available on GitHub.

In previous “Encrypting Large Data” article we spoke about cryptographic key sizes, default Java providers, symmetric keys generation and different approaches of encrypting large data on different API levels. This article will show you what is Initialization Vector, why do we need it and how to use it.

Table of Contents

  • Initialization Vector
  • Default Value
  • Custom Value
  • Empty Value
  • Randomized Encryption
  • Usage Example
  • Whats Next
  • Security Tips

Initialization Vector

Initialization Vector is a fixed-size input to a cryptographic primitive. It is typically required to be random or pseudorandom. The point of an IV is to tolerate the use of the same key to encrypt several distinct messages.

And it is required for block algorithm modes (like CBC in AES) in most of providers, including the AndroidKeyStore provider as well as BC provider.

On API 18, with default Java’s BC provider key, if IV wasn’t specified during decryption, Cipher will fall into IllegalArgumentException:

On API 23, with AndroidKeyStore provider key, InvalidKeyException will be thrown:

InvalidKeyException: IV required when decrypting. Use IvParameterSpec or AlgorithmParameters to provide it.

Default Value

The easiest way to implement the Initialization Vector support, is to use byte array data that is generated by the Cipher during encryption. It can be retrieved with cipher.getIV() method:

cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv // returns automatically generated IV value
...
// Encrypt data with Cipher

Note, Default value is generated during Cipher initialization, so cipher.init() must be called first, otherwise cipher.getIv() will return empty data.

Then, during decryption, initialize Cipher with IvParameterSpec created from generated IV value :

val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
...
// Decrypt data using Cipher, initialized with IV

Like a Salt value, an Initialization Vector can be stored in the public storage, along with encrypted data.

And one of the possible ways to store it, is to add IV data to the encryption result :

And parse it before decryption, from encrypted data:

Full source code is available here.

Custom Value

As Dorian Cussen’s mentioned in his blog, default IV value, provided by Cipher, mostly depends on the Provider implementation.

It is possible to face the situation when cipher.getIV() is not implemented — returns “empty” or null array.

To be on the safe side and to avoid such an unpleasant moment — declare an Initialization Vector explicitly, using SecureRandom class.

To create custom IV value, use nextBytes(byte[] key) method, from SecureRandom class :

val iv = ByteArray(ivLength)
SecureRandom().nextBytes(iv)

The ivLength depends on the mode of operation. And for most modes, including CBC, the IV must have the same length as the block in it.

AES algorithm uses 128-bit blocks, so Initialization Vector’s length is equal to 128 bits (ivLength = 16// bytes).

When custom IV value is generated, simply initialize Cipher instance with it:

cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))

Note, generated value must be passed into init() method for both encryption and decryption modes.

Empty Value

It is possible, but highly not recommended, to cheat on Cipher. Instead of generating new random Initialization Vector’s data each time before encryption, and then saving and parsing it before decryption, IV can be initialized once, as an array, with fixed length of static values and used everywhere, all the time:

// Create an array of 16 bytes, filled with 0 [0, 0, 0, 0 ..0]
val iv = ByteArray(16)
// Use this array during encryption and decryption as IV data
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))

Keep in mind that this implementation kills the essence of the IV.

Randomized Encryption

To protect users data from using incorrect (not random or empty) IV, by default, AndroidKeyStore Provider is not allowing to use custom IV values :

val iv = ByteArray(16)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
// will throw InvalidAlgorithmParameterException: Caller-provided IV // not permitted
cipher.doFinal(data.toByteArray())

On API 23, the setRandomizedEncryptionRequired method was added to KeyGenParameterSpec class, that should allow you to control whether IV may be custom or not. From documentation:

Sets whether encryption using this key must be sufficiently randomized to produce different ciphertexts for the same plaintext every time.

When IND-CPA is required: in block modes which use an IV, such as GCM, CBC, and CTR, caller-provided IVs are rejected when encrypting, to ensure that only random IVs are used.

val builder = KeyGenParameterSpec.Builder()// Forces to use only Default Generated, by Cipher, IV.
// Default value.
builder.setRandomizedEncryptionRequired(true)
// Enables to use Custom Generated IV's.
// Not working.
builder.setRandomizedEncryptionRequired(false)

But it is not working, (at least for AES and CBC). Even after disabling randomization, Cipher still crashes with InvalidAlgorithmParameterException and continue to require the Default Initialization Vector.

Usage Example

Lets encrypt and decrypt message using symmetric AES keys with Initialization Vector:

Full source code is available here.

Whats Next

In next “Key Invalidation” article from “Secure data in Android” series:

Just try to change Lock Screen type and all of your AndroidKeyStore keys will gone…

Security Tips

Be careful when writing to on-device logs. Especially if logs are a shared resource and are available to other applications, such as it is on Android .

Even if log data is temporary and erased on reboot, inappropriate logging of user information could inadvertently leak user data to other applications.

In addition to not logging Personally Identifiable Information, production apps should limit log usage.

More about encryption in Android, and overall, you can learn at:

--

--

Android Developer at Temy. Author. Contributor. Love what I do, working hard to become better and, of course, not forgetting to make some fun.