Skip to content

MASTG-DEMO-0058: Use of Insecure ECB Block Mode in KeyGenParameterSpec

Download MASTG-DEMO-0058 APK Open MASTG-DEMO-0058 Folder Build MASTG-DEMO-0058 APK

Sample

The code below generates symmetric encryption keys meant to be stored in the Android KeyStore, but it does so using the ECB block mode, which is considered broken due to practical known-plaintext attacks and is disallowed by NIST for data encryption. The method used to set the block modes is KeyGenParameterSpec.Builder#setBlockModes(...):

public KeyGenParameterSpec.Builder setBlockModes (String... blockModes)

Current versions of Android prohibit the usage of keys with for ECB in some cases. For example, it is not possible to use the key to encrypt data by the default. Nevertheless, there are some case, where ECB can still be used:

  • Decrypt data
  • Encrypt data with a key given setRandomizedEncryptionRequired is set to false
MastgTest.kt
  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
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package org.owasp.mastestapp

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.security.keystore.KeyProtection
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import android.util.Base64
import javax.crypto.spec.SecretKeySpec

class MastgTest(private val context: Context) {

    fun mastgTest(): String {

        val results = mutableListOf<String>()
        var rawKey: SecretKey? = null
        var encryptedData: ByteArray? = null
        var decryptedData: ByteArray? = null

        // Suppose we received a raw key from a secure source and we want to use it for decryption.
        // The following commented-out code is an example of generating a raw key and encrypting data with it.
        // We obtained the raw key and encrypted data from the logs and added them to the code for demonstration purposes.
        try {
            // Suppose we received the raw key from a secure source and we want to use it for decryption.
            val rawKeyString = "43ede5660e82123ee091d6b4c8f7d150"
            val keyBytes = rawKeyString.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            rawKey = SecretKeySpec(keyBytes, KeyProperties.KEY_ALGORITHM_AES)

            // The cipher text is 'Hello from OWASP MASTG!' AES/ECB encrypted using CyberChef:
            // https://gchq.github.io/CyberChef/#recipe=AES_Encrypt(%7B'option':'Hex','string':'43ede5660e82123ee091d6b4c8f7d150'%7D,%7B'option':'Hex','string':''%7D,'ECB','Raw','Hex',%7B'option':'Hex','string':''%7D)&input=SGVsbG8gZnJvbSBPV0FTUCBNQVNURyE
            val encryptedDataString = "20b0eef4e5ad3d8984a4fb94f6001885f0ce25104cb8251f600624b46dcefb92"
            encryptedData = encryptedDataString.chunked(2).map { it.toInt(16).toByte() }.toByteArray()

            val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
            val alias = "importedAesKey"
            val entry = KeyStore.SecretKeyEntry(rawKey)
            val protection = KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build()
            keyStore.setEntry(alias, entry, protection)
            val importedKey = keyStore.getKey(alias, null) as SecretKey
            val cipher2 = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
                init(Cipher.DECRYPT_MODE, importedKey)
            }
            decryptedData = cipher2.doFinal(encryptedData)
            val decryptedString = String(decryptedData)
            results.add("\n[*] Keystore-imported AES ECB key decryption (plaintext):\n\n$decryptedString")
        } catch (e: Exception) {
            results.add("\n[!] Keystore-imported AES ECB key decryption error:\n\n${e.message}")
        }

        // import the raw key into AndroidKeyStore for encryption which would fail unless randomized encryption is disabled (bad practice)
        try {
            if (rawKey == null || encryptedData == null) {
                throw IllegalStateException("Key or data missing for encryption")
            }
            val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
            val alias = "importedAesKey2"
            val entry = KeyStore.SecretKeyEntry(rawKey)
            val protection = KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .setRandomizedEncryptionRequired(false) // For demonstration purposes, we disable randomized encryption
                .build()
            keyStore.setEntry(alias, entry, protection)
            val importedKey = keyStore.getKey(alias, null) as SecretKey
            val cipher3 = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
                init(Cipher.ENCRYPT_MODE, importedKey)
            }
            val encryptedBytes = cipher3.doFinal(decryptedData)
            val encrypted = Base64.encodeToString(encryptedBytes, Base64.DEFAULT)

            results.add("\n\n[*] Keystore-imported AES ECB key encryption (ciphertext):\n\n$encrypted")
        } catch (e: Exception) {
            results.add("\n\n[!] Keystore-imported AES ECB key encryption error:\n\n${e.message}")
        }

        // keystore key generation and encryption
        try {
            val keyAlias = "testKeyGenParameter"
            val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
            val spec = KeyGenParameterSpec.Builder(
                keyAlias,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
            )
                .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                // .setRandomizedEncryptionRequired(false) // Disabling randomized encryption would allow the key to be used in ECB mode.
                .build()
            KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES,
                "AndroidKeyStore"
            ).apply {
                init(spec)
                generateKey()
            }

            val secretKey = keyStore.getKey(keyAlias, null) as SecretKey
            val cipher = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
                init(Cipher.ENCRYPT_MODE, secretKey)
            }
            val encrypted = Base64.encodeToString(cipher.doFinal(decryptedData), Base64.DEFAULT)
            results.add("\n[*] Keystore-generated AES ECB key encryption (ciphertext):\n\n$encrypted")
        } catch (e: Exception) {
            results.add("\n[!] Keystore-generated AES ECB error:\n\n${e.message}")
        }

        return results.joinToString("\n")
    }
}

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frida for Android installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn the app with Frida
  4. Click the Start button
  5. Stop the script by pressing Ctrl+C and/or q to quit the Frida CLI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var target = {
    category: "CRYPTO",
    demo: "0058",
    hooks: [
    {
      class: "android.security.keystore.KeyGenParameterSpec$Builder",
      methods: [
        "setBlockModes",
        "setRandomizedEncryptionRequired"
      ]
    },
    {
      class: "android.security.keystore.KeyProtection$Builder",
      methods: [
        "setBlockModes",
        "setRandomizedEncryptionRequired"
      ]
    }
  ]
}
1
2
#!/bin/bash
../../../../utils/frida/android/run.sh ./hooks.js

Observation

The output shows all instances of block modes mode that were found at runtime. A backtrace is also provided to help identify the location in the code.

output.json
  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
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
{
  "id": "523e8eb7-e155-4792-bdae-6c2a728c87ac",
  "category": "CRYPTO",
  "time": "2025-08-01T09:00:07.277Z",
  "class": "android.security.keystore.KeyProtection$Builder",
  "method": "setBlockModes",
  "stackTrace": [
    "android.security.keystore.KeyProtection$Builder.setBlockModes(Native Method)",
    "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:41)",
    "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
    "org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
    "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
    "androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
  ],
  "inputParameters": [
    {
      "type": "[Ljava.lang.String;",
      "value": [
        "ECB"
      ]
    }
  ],
  "returnValue": [
    {
      "type": "android.security.keystore.KeyProtection$Builder",
      "value": "<instance: android.security.keystore.KeyProtection$Builder>"
    }
  ]
}
{
  "id": "a162bca9-454e-4d48-a737-0ac6e73983c7",
  "category": "CRYPTO",
  "time": "2025-08-01T09:00:07.288Z",
  "class": "android.security.keystore.KeyProtection$Builder",
  "method": "setBlockModes",
  "stackTrace": [
    "android.security.keystore.KeyProtection$Builder.setBlockModes(Native Method)",
    "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:65)",
    "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
    "org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
    "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
    "androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
  ],
  "inputParameters": [
    {
      "type": "[Ljava.lang.String;",
      "value": [
        "ECB"
      ]
    }
  ],
  "returnValue": [
    {
      "type": "android.security.keystore.KeyProtection$Builder",
      "value": "<instance: android.security.keystore.KeyProtection$Builder>"
    }
  ]
}
{
  "id": "8dd8050c-dbc0-4662-804a-8bfb2151ca34",
  "category": "CRYPTO",
  "time": "2025-08-01T09:00:07.291Z",
  "class": "android.security.keystore.KeyProtection$Builder",
  "method": "setRandomizedEncryptionRequired",
  "stackTrace": [
    "android.security.keystore.KeyProtection$Builder.setRandomizedEncryptionRequired(Native Method)",
    "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:67)",
    "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
    "org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
    "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
    "androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
  ],
  "inputParameters": [
    {
      "type": "boolean",
      "value": false
    }
  ],
  "returnValue": [
    {
      "type": "android.security.keystore.KeyProtection$Builder",
      "value": "<instance: android.security.keystore.KeyProtection$Builder>"
    }
  ]
}
{
  "id": "394de339-b6c6-485e-babc-672ff5df315f",
  "category": "CRYPTO",
  "time": "2025-08-01T09:00:07.300Z",
  "class": "android.security.keystore.KeyGenParameterSpec$Builder",
  "method": "setBlockModes",
  "stackTrace": [
    "android.security.keystore.KeyGenParameterSpec$Builder.setBlockModes(Native Method)",
    "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:90)",
    "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
    "org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
    "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
    "androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
    "androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
  ],
  "inputParameters": [
    {
      "type": "[Ljava.lang.String;",
      "value": [
        "ECB"
      ]
    }
  ],
  "returnValue": [
    {
      "type": "android.security.keystore.KeyGenParameterSpec$Builder",
      "value": "<instance: android.security.keystore.KeyGenParameterSpec$Builder>"
    }
  ]
}

Evaluation

The method setBlockModes has now been called three times with ECB as one of the block modes.

The test fails, as key used with these KeyGenParameterSpec can now be used used to insecurely encrypt data.

You can automatically evaluate the output using tools like jq as demonstrated in evaluation.sh.

evaluate.sh
1
2
3
4
5
6
7
8
9
#!/bin/bash

jq '
  select(
    .class=="android.security.keystore.KeyGenParameterSpec$Builder"
    and .method=="setBlockModes"
    and (.inputParameters[0].value | contains(["ECB"]))
  )
' output.json

See Broken Symmetric Encryption Modes for more information.