Android Keystore

The Android Keystore and Hardware-Backed Keys

The Android Keystore generates and holds keys for you. You get a handle, never the key bytes: the cryptography runs in a system process and, on most phones, in a Trusted Execution Environment (TEE), a separate secure OS on the main processor. Android 9 added StrongBox, a dedicated security chip (setIsStrongBoxBacked(true)). A key's allowed uses are fixed at creation and enforced there. BookNest creates one AES-256 key for its token:

security/KeystoreCipher.kt: generating and using a Keystore key (excerpt)Kotlin
object KeystoreCipher {
  private const val ALIAS = "booknest_token_key"
  private const val TRANSFORMATION = "AES/GCM/NoPadding"
  private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
  ...                                 // key() returns the stored key, or create()s it once
  private fun create(): SecretKey {
    val spec = KeyGenParameterSpec.Builder(ALIAS,
      KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
      .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
      .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
      .setKeySize(256)
      .build()
    return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
      .apply { init(spec) }
      .generateKey()
  }
  fun encrypt(plain: ByteArray): ByteArray {
    val cipher = Cipher.getInstance(TRANSFORMATION).apply { init(Cipher.ENCRYPT_MODE, key()) }
    return cipher.iv + cipher.doFinal(plain)          // the Keystore picks a fresh IV
  }
  ...                  // decrypt() reads the IV back with GCMParameterSpec(128, blob, 0, 12)
}

The result is the 12-byte IV, the ciphertext and a 16-byte GCM tag, which makes decryption throw AEADBadTagException if a byte was altered. KeyInfo.securityLevel (API 31) reports where the key lives:

Output of 164
D/BookNestSec( 4257): booknest_token_key: AES-256, software, key.encoded=null

The emulator's KeyMint runs in software; most phones report TEE or StrongBox, which a server can verify through key attestation. key.encoded is null everywhere, and keys die with the app and are never backed up, so treat what they encrypt as a cache you can rebuild by signing in again.