Skip to content

MASTG-KNOW-0055: Keyboard Cache

When users enter information into input fields, the keyboard software often provides suggestions based on previously entered data. This auto-completion feature can be very useful for messaging apps and other scenarios. However, by default, the Android keyboard may retain (or "cache") input history to offer suggestions and auto-completion. In contexts where sensitive data is entered (such as passwords or PINs), this caching behavior can inadvertently expose sensitive information.

Apps can control this behavior by appropriately configuring the inputType attribute on text input fields. There are several ways to do this:

XML Layouts:

In the app's XML layout files (typically located in the /res/layout directory after unpacking the APK), you can define the input type directly in the <EditText> element using the android:inputType attribute. For example, setting the input type to "textPassword" automatically disables auto-suggestions and caching:

<EditText
    android:id="@+id/password"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/password_hint"
    android:inputType="textPassword" />

Using the Traditional Android View System:

When creating input fields in code using the traditional Android view system, you can set the input type programmatically. For example, using an EditText in Kotlin:

val input = EditText(context).apply {
    hint = "Enter PIN"
    inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
}

Using Jetpack Compose:

If you are developing with Jetpack Compose, you do not use EditText directly. Instead, you use composable functions such as TextField or OutlinedTextField along with parameters like keyboardOptions and visualTransformation to achieve similar behavior. For example, to create a password field without suggestions:

OutlinedTextField(
    value = password,
    onValueChange = { password = it },
    label = { Text("Enter Password") },
    visualTransformation = PasswordVisualTransformation(),
    keyboardOptions = KeyboardOptions(
        keyboardType = KeyboardType.Password,
        autoCorrect = false
    ),
    modifier = Modifier.fillMaxWidth()
)

In this Compose example, the PasswordVisualTransformation() masks the input, and keyboardOptions with KeyboardType.Password helps specify the password input type. The autoCorrect parameter is set to false to prevent suggestions.

Internally, the KeyboardType enum in Jetpack Compose maps to the Android inputType values. For example, the KeyboardType.Password corresponds to the following inputType:

KeyboardType.Password -> {
    this.inputType =
        InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
}

Non-Caching Input Types

Regardless of the method used, the app can use the following inputType attributes, when applied to <EditText> elements, instruct the system to disable suggestions and prevent caching for those input fields:

XML android:inputType Code InputType API level
textNoSuggestions TYPE_TEXT_FLAG_NO_SUGGESTIONS 3
textPassword TYPE_TEXT_VARIATION_PASSWORD 3
textVisiblePassword TYPE_TEXT_VARIATION_VISIBLE_PASSWORD 3
numberPassword TYPE_NUMBER_VARIATION_PASSWORD 11
textWebPassword TYPE_TEXT_VARIATION_WEB_PASSWORD 11

Note: In the MASTG tests we won't be checking the minimum required SDK version in the Android Manifest minSdkVersion because we are considering testing modern apps. If you are testing an older app, you should check it. For example, Android API level 11 is required for textWebPassword. Otherwise, the compiled app would not honor the used input type constants allowing keyboard caching.

The inputType attribute is a bitwise combination of flags and classes. The InputType class contains constants for both flags and classes. The flags are defined as TYPE_TEXT_FLAG_* and the classes are defined as TYPE_CLASS_*. The values of these constants are defined in the Android source code. You can find the source code for the InputType class here.

The inputType attribute in Android is a bitwise combination of these constants:

  • Class constants (TYPE_CLASS_*): Input type (text, number, phone, etc.)
  • Variation constants (TYPE_TEXT_VARIATION_*, etc.): Specific behavior (password, email, URI, etc.)
  • Flag constants (TYPE_TEXT_FLAG_*): Additional modifiers (no suggestions, multi-line, etc.)

For example, this Kotlin code:

inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD

Where:

  • TYPE_CLASS_TEXT = 1
  • TYPE_TEXT_VARIATION_PASSWORD = 128

Results in 1 or 128 = 129, which is the value you will see in the decompiled code.

How to decode input type attributes after reverse engineering:

To decode the inputType value, you can use the following masks:

You can quickly decode inputType values using the masks and the bitwise AND operation e.g. in Python:

129 & 0x0000000F  #   1 (TYPE_CLASS_TEXT)
129 & 0x00000FF0  # 128 (TYPE_TEXT_VARIATION_PASSWORD)

How to find cached data:

If you write e.g. "OWASPMAS" in the passphrase field a couple of times, the app will cache it and you will be able to find it in the cache database:

adb shell 'strings /data/data/com.google.android.inputmethod.latin/databases/trainingcachev3.db' | grep -i "OWASPMAS"
OWASPMAS@
OWASPMAS@
OWASPMAS%