There’s a formula that you can use to calculate maximum RSA message length. For a n-bit RSA key (with PKCS1 padding) direct encryption works for arbitrary binary messages up to:

floor(n/8)-11 bytes

For a 1024-bit RSA key (128 bytes), you can use messages up to 117 bytes. So, the longest message we can get with RSA key can contain maximum 468 bytes (using 4096-bit key).

What to do

Pretty bad situation here, no possibility to use asymmetric key, and symmetric is available only from API 23+. And not so many options to choose from, to escape it:

Create symmetric key with one of default Java Providers. Encrypt / decrypt message with it. Then encrypt this key raw data with RSA public key and save it somewhere to the disk. On decryption, get encrypted raw key data, decrypt it with RSA private key and use it for message decryption. Separate large message on parts and encrypt / decrypt each of the part individually.

I know what you just thought, about second option: “But we are using ECB mode already, it shell do this automatically, no ?” (see Encryption, Modes & Paddings).

Unfortunately, even with ECB mode (in Android Key Store provider implementation) RSA algorithm can process only one block of data, and if message is longer than one block size — it will crash.

To workaround this, you can manually separate data and work with it parts (simulate the ECB mode). But again, RSA is not designed for tasks like this.

It will be more secure to continue with first option.

You can check the implementation of second option here.

Default Providers

And the first thing we are going to do is to: generate symmetric key with one of default Java Providers.

The most common default Java provider in android is the cut version of BC provider created by the popular third party Java cryptographic library provider — Bouncy Castle.

KeyGenerator class is responsible for symmetric keys generation. Use one of its getInstance() methods to create an instance of the AES key.

You can fetch all available providers by calling Security.getProviders() method:

Security.getProviders().forEach { logi(it.name) }

Note: available providers may vary across different platforms and vendors. If provider, that you are looking for, doesn’t exist on device, you can register your own (or some third party) provider by calling one of:

Security.addProvider(provider)

// your application. See

Security.insertProviderAt(provider, position) // With this you are able to control the most preferred provider for// your application. See Encryption in Android (Part 2) Security.insertProviderAt(provider, position)

Or you can use getInstance(algorithm) , that will return you the most preferred implementation, basing on your providers list.

val keyGenerator = KeyGenerator.getInstance("AES") // Providers List:

// 1. Android Key Store

// 2. Custom Provider

// 3. BC // Will output BC, if Android Key Store and Custom Provider do not // have AES algorithm.

keyGenerator.provider.name

Symmetric Keys

Basically, we do not have symmetric keys up to 23 API. But starting from M, we can use just one symmetric key from Android Key Store provider.

Use KeyGenerator in pair with KeyGenParameterSpec to create one. Also do not forget about Cipher transformation that we will need for symmetric encryption:

Full source code is available here.

Key Wrapping

To protect our AES key and safely save it on device public persistent storage (such as preferences, files or databases), we will use RSA key, that is stored in Android Key Store, to encrypt and decrypt it. This process is also known as key wrapping.

For this, Cipher has separate WRAP_MODE and wrap() method. To decrypt the key, use UNWRAP_MODE and unwrap() method.

Full source code is available here.

Usage Example

Lets try to encrypt and decrypt large message, using new approach:

Running on M and later, use one symmetric key :

Before M, use two, asymmetric and symmetric, keys:

And its not enough again, we’re able to encrypt message, but not to decrypt it.

Full source code is available here.

Whats Next

In next “Initialization Vector” article from “Secure data in Android” series:

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.

Security Tips