Android 12 introduced some breaking changes on its security stack. Google is slowly dropping Bauncy Castle java security stack in favor of Conscrypt security java layer.
At one of my clients I had some strange situation: on the latest Android 12 (API level 31) testing, some apps stop working and start throwing following exception:
Key size must be either 128, 192, or 256 bits
After reading some documentation, e.g. https://developer.android.com/about/versions/12/behavior-changes-all#bouncy-castle and observing the code, I found out that application is generating AES secret keys of 384 bits in size. Obviously, Bauncy Castle java security provider handle this situation, but Conscrypt is failing at runtime.
Let’s take a look what is going on.
The problem
I created simple Android Java application which generates AES key of 384 bits in size, and checked what is going on Android 12 (API 31) and on Androids older then 12 (API level <31).
Here is my simple code extract:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
final TextView label = (TextView) findViewById(R.id.output_label); try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(384); SecretKey key = keyGen.generateKey(); // drop output label.setText("All ok, One-Step AES key generation, key length: " + key.getEncoded().length + " Key as Base64: " + Base64.getEncoder().encodeToString(key.getEncoded()) ); } catch (Exception e) { label.setText("Ops, exception: " + e.getMessage()); } |
The output on Android 12 (API 31) compared with some older Android API level was different, as depicted below:
I isolated the problem, so let’s start working on solution!
The solution
I need quick and easy solution, which will be backward compatible. Anyway, solution is quite straightforward: I created 3 secret keys of 128 bits (128+128+128 = 384) and combine them (or 256+128 = 384, or 192+192 = 384).
My quick and dirty solution is something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
final TextView label = (TextView) findViewById(R.id.output_label); try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128); SecretKey key1 = keyGen.generateKey(); SecretKey key2 = keyGen.generateKey(); SecretKey key3 = keyGen.generateKey(); byte[] key = new byte[48]; // 48*8 -> 384 System.arraycopy(key1.getEncoded(), 0, key, 0, 16); System.arraycopy(key2.getEncoded(), 0, key, 16, 16); System.arraycopy(key3.getEncoded(), 0, key, 32, 16); // drop output label.setText("All ok, Multi-Step AES key generation, key length: " + key.length + " Key as Base64: " + Base64.getEncoder().encodeToString(key) ); } catch (Exception e) { label.setText("Ops, exception: " + e.getMessage()); } |
Finally, my tests on different Android API devices/emulators are working just as expected:
If useful for someone, here s the complete code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package si.jenx.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.TextView; import java.util.Base64; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView deviceloTextView = (TextView) findViewById(R.id.runtime_info); deviceloTextView.setText("Running on: Android version: " + Build.VERSION.RELEASE + ", SDK version: " + Build.VERSION.SDK_INT); } public void onOneStepAesKeyGenerationButtonClick(View view) { final TextView label = (TextView) findViewById(R.id.output_label); try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(384); SecretKey key = keyGen.generateKey(); // drop output label.setText("All ok, One-Step AES key generation, key length: " + key.getEncoded().length + " Key as Base64: " + Base64.getEncoder().encodeToString(key.getEncoded()) ); } catch (Exception e) { label.setText("Ops, exception: " + e.getMessage()); } } public void onMultiStepAesKeyGenerationButtonClick(View view) { final TextView label = (TextView) findViewById(R.id.output_label); try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128); SecretKey key1 = keyGen.generateKey(); SecretKey key2 = keyGen.generateKey(); SecretKey key3 = keyGen.generateKey(); byte[] key = new byte[48]; // 48*8 -> 384 System.arraycopy(key1.getEncoded(), 0, key, 0, 16); System.arraycopy(key2.getEncoded(), 0, key, 16, 16); System.arraycopy(key3.getEncoded(), 0, key, 32, 16); // drop output label.setText("All ok, Multi-Step AES key generation, key length: " + key.length + " Key as Base64: " + Base64.getEncoder().encodeToString(key) ); } catch (Exception e) { label.setText("Ops, exception: " + e.getMessage()); } } } |
Summay
If you have java Android app which generates AES keys different as 128, 192 or 256 in size – or if your app is using 512 key size – or you initialize Galois/Counter Mode (GCM) ciphers using a size other than 12 byte: your app will not work or it will behave strangely on Android 12 devices.
This is due to braking changes on Android 12 java security layer. To be more precise, Google is replacing Bauncy Castle security layer with Conscrypt.
I really hope you don’t have such problems, but if you do, start fixing them as soon as possible!
Happy coding.