2
votes

I generated AES key on Android 10 device with StrongBox (Pixel 4) with setIsStrongBoxBacked=true, as follows:

fun generateKey(): SecretKey {
        val generator = KeyGenerator.getInstance("AES", "AndroidKeyStore")
        generator.init(
                KeyGenParameterSpec.Builder("alias", PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
                        .setBlockModes(BLOCK_MODE_GCM)
                        .setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
                        .setIsStrongBoxBacked(true)
                        .build()
        )
        return generator.generateKey()
}

Next, using this key I encrypted some ByteArray. Some time later, during a decryption with the same key and the same IV vector, I got wrong ByteArray as a result with no exception.

Both encryption and decryption methods are pretty standard:

fun encrypt(plaintext: ByteArray, iv: ByteArray, key: SecretKey): ByteArray {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
        return cipher.doFinal(plaintext)
}

fun decrypt(ciphertext: ByteArray, iv: ByteArray, key: SecretKey): ByteArray {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(DECRYPT_MODE, key, GCMParameterSpec(128, iv))
        return cipher.doFinal(ciphertext)
}

Strangely, using the same key generation code but with setIsStrongBoxBacked=false, and the same encryption/decryption logic, everything works as expected.

Official documentation says nothing that could be helpful in my situation. For now, I can disable StrongBox backing logic, but I would like to know where the problem lies - is my code missing something crucial or is it Android 10/Pixel 4 problem?

P.S.

Upon further inspection I realised that something is wrong with the encryption logic as well. With setIsStrongBoxBacked=true, every time I encrypt any ByteArray using AES/GCM/NoPadding algorithm, ciphertext looks the same (only authentication tag part differs) even if IV is generated randomly. From what I know about GCM mode, something is't right.

encryption 1 with setIsStrongBoxBacked=true:

IV=[40, 91, -4, -98, 104, 94, -39, -83, -34, 22, -33, 12]

ciphertext=[13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 12, -86, -16, -38, 93, 100, 82, 108, 101, -27, -42, -107, 36, 49, 109, 47]

encryption 2 with setIsStrongBoxBacked=true:

IV=[-123, -96, -66, -87, 43, -103, -126, -107, 8, -73, 68, -49]

ciphertext=[13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 13, -48, -83, -34, 85, 108, -23, -62, 18, 74, -81, -2, 111, -94, 7, 15, -56, 106, -114, -69]

With setIsStrongBoxBacked=false the same encryption logic generates totally different ciphertexts as long as IV is differs every time, just like it supposed to:

encryption 1 with setIsStrongBoxBacked=false:

IV=[-113, -48, -5, -106, -86, -1, 121, -40, -13, -54, -56, 10]

ciphertext=[126, -97, 118, 42, -110, 67, 77, 9, 120, -79, 74, -101, -123, 106, -21, -18, -49, -123, -10, -33, -4, -99, 58, -125, 69, -37, 111, -66, -114, -47, 17, 41, -3, 72, -52, -36, -67, 47, 39, -108, -46, -113, 114, -46, -114, 24, -51, -95]

encryption 2 with setIsStrongBoxBacked=false:

IV=[-106, 12, -84, -108, -102, -11, 38, 30, 10, 22, -123, -28]

ciphertext=[-51, -85, -125, -34, -72, 47, -13, 6, -111, 14, -85, 53, -8, -68, -17, -81, -106, 50, -94, -22, 66, 74, -39, 12, 12, 74, 48, -41, -41, -55, -28, 2, 61, 11, 120, -3, 124, 8, -46, 78, -93, 97, 87, -97, -26, -42, -105, 74]

P.S.2

This problem occur only when use AES/GCM/NoPadding algorithm. For other AES modes (CBC, CRT, etc.) both encryption and decryption works as expected even for setIsStrongBoxBacked=true

P.S.3 issue - https://issuetracker.google.com/issues/147039506

P.S.4 After applying the newest security patch available for Pixel 4, problem has disappeared.

1
You may want to file a bug report on that (if you cannot find a dupe), that's just too weird and should definitely not be happening. Do note that without encryption / decryption code, we cannot tell if you aren't making a mistake there though. - Maarten Bodewes
@Maarten-reinstateMonica hey, thanks for replay :) I've edited my question by copying&pasting encryption and decryption methods - they are pretty standard as you can probably tell. For now I'm going to use an AES in CRT mode, as it works as expected and for now I don't need authentication upon decryption mechanism. I'm also going to open issue in Google issue tracker as you suggested :) - Lukasz
No, I won't be able to say anything about that particular code. If there is something wrong then it would be in the way auxiliary methods are called. But if you used the exact same code for the other modes then I guess it is what it is. Don't forget to supply a linky down below to the issue once you've opened it! - Maarten Bodewes

1 Answers

1
votes

It looks like you have rediscovered CVE-2019-9465. As you noticed, when using AES-GCM with Strongbox, predictable and bogus ciphertext is produced. This appears to happen if there is a delay between Cipher initialization and usage, but the exact cause of this bug is not known, as it is in the firmware of the Titan M chip.

It was fixed in the Android security update of December 2019 for the Pixel 3 and newer. I wrote an article about this that goes a little more in depth: https://alexbakker.me/post/mysterious-google-titan-m-bug-cve-2019-9465.html.