Secure data in Android — Initialization Vector
This article is a part of “Secure data in Android” series:
- Encryption
- Encryption in Android (Part 1)
- Encryption in Android (Part 2)
- Encrypting Large Data
- Initialization Vector
- Key Invalidation
- Fingerprint
- 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, otherwisecipher.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: