Android System Securing, Hardening, & Privacy

Mastering Android Hardware-Backed Keystore: A Step-by-Step Implementation Guide

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android Hardware-Backed Keystore

Securing sensitive data within Android applications is paramount in today’s digital landscape. While software-based cryptographic solutions offer a baseline of protection, they remain vulnerable to sophisticated attacks if the device’s operating system is compromised. Android’s Hardware-Backed Keystore offers a robust solution by leveraging dedicated hardware security modules (HSMs) to protect cryptographic keys.

This expert-level guide will walk you through the process of implementing hardware-backed keys, encrypting data, and understanding key attestation, ensuring your application utilizes the strongest available security primitives on Android devices.

Understanding Android’s Keystore System

The Android Keystore system allows you to store cryptographic keys in a container to make them more difficult to extract from the device. Keys stored in the Keystore are protected from unauthorized use by restricting their access to only the applications that generated them. When a key is ‘hardware-backed’, it means its lifecycle (generation, storage, usage) is managed by a dedicated hardware security module (HSM) or Trusted Execution Environment (TEE) that is isolated from the main Android OS.

Why Hardware-Backed Keys?

  • Tamper Resistance: Keys are stored in secure hardware, making them extremely difficult to extract even if the Android OS is rooted or compromised.
  • Isolation: Cryptographic operations occur within the secure hardware, preventing malware from intercepting keys or plaintext data.
  • Strongbox Integration: On devices supporting StrongBox (Android 9+), keys are protected by an even more robust, dedicated security chip with its own CPU, memory, and storage, offering enhanced resistance to physical attacks.
  • Key Attestation: Provides cryptographic proof of key properties, including whether the key is hardware-backed and what security features it supports.

Setting Up Your Android Project

No special permissions or dependencies are strictly required to use the Android Keystore API, as it’s part of the standard Android SDK. You’ll primarily interact with the KeyStore and KeyPairGenerator classes.

Generating Hardware-Backed Keys

Generating a hardware-backed key involves using KeyGenParameterSpec (available from API level 23, Android 6.0 Marshmallow) and specifying particular properties. For maximum security, we’ll aim for a StrongBox-backed key if available, or fall back to a TEE-backed key.

Step-by-Step Key Generation

First, obtain an instance of KeyPairGenerator for the desired algorithm (e.g., RSA or AES). Then, initialize it with a KeyGenParameterSpec.Builder.

import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException;  public class KeystoreHelper {     private static final String ANDROID_KEYSTORE = "AndroidKeyStore";     private static final String KEY_ALIAS = "my_secure_key";      public void createKey() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException {         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(             KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);          KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(             KEY_ALIAS,             KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT |             KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)             .setBlockModes(KeyProperties.BLOCK_MODE_ECB)             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)             .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)             .setUserAuthenticationRequired(true)             .setUserAuthenticationValidityDurationSeconds(30); // 30 seconds of inactivity  // Try to use StrongBox if available, otherwise fall back to TEE         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {             builder.setIsStrongBoxBacked(true);         }          keyPairGenerator.initialize(builder.build());         keyPairGenerator.generateKeyPair(); // This generates and stores the key pair in the Keystore     }      public boolean isKeyStrongBoxBacked() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);         keyStore.load(null);         return keyStore.entryInstanceOf(KEY_ALIAS, KeyStore.PrivateKeyEntry.class) &&            keyStore.getEntry(KEY_ALIAS, null) instanceof KeyStore.PrivateKeyEntry &&            ((KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getPrivateKey().getProvider().getName().equals("AndroidKeyStore"); // Simpler check for now, actual StrongBox check requires attestation         // More robust check involves KeyInfo, but it's not directly exposed to check isStrongBoxBacked() boolean        // Attestation is the proper way to check this property.     } }

In this example:

  • KEY_ALIAS: A unique identifier for your key within the Keystore.
  • PURPOSE_ENCRYPT | PURPOSE_DECRYPT | PURPOSE_SIGN | PURPOSE_VERIFY: Defines what operations the key can perform.
  • setBlockModes and setEncryptionPaddings: Necessary for RSA encryption.
  • setDigests: For signing operations.
  • setUserAuthenticationRequired(true): Means the user must authenticate (e.g., fingerprint, PIN) to use this key.
  • setUserAuthenticationValidityDurationSeconds(30): The key can be used without re-authentication for 30 seconds after the initial successful authentication.
  • setIsStrongBoxBacked(true): Instructs the system to prefer a StrongBox-backed key. If not available, it defaults to TEE-backed.

Remember to handle exceptions appropriately.

Encrypting and Decrypting Data

Once the key is generated, you can use it to encrypt and decrypt data. You’ll typically use a Cipher instance for this.

import javax.crypto.Cipher; import javax.crypto.SecretKey; import java.security.KeyStore; import java.security.Key; import java.util.Base64; // For Android API 26+  public class EncryptionHelper {     private static final String ANDROID_KEYSTORE = "AndroidKeyStore";     private static final String KEY_ALIAS = "my_secure_key";     private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";      public byte[] encryptData(String dataToEncrypt) throws Exception {         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);         keyStore.load(null);          // Get the public key to encrypt         KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);          Cipher cipher = Cipher.getInstance(TRANSFORMATION);         cipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());          return cipher.doFinal(dataToEncrypt.getBytes("UTF-8"));     }      public String decryptData(byte[] encryptedData) throws Exception {         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);         keyStore.load(null);          // Get the private key to decrypt         KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);          Cipher cipher = Cipher.getInstance(TRANSFORMATION);         cipher.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());          byte[] decryptedBytes = cipher.doFinal(encryptedData);         return new String(decryptedBytes, "UTF-8");     } }

Note: When setUserAuthenticationRequired(true), any attempt to use the private key (e.g., for decryption or signing) will trigger the Android system’s biometric/PIN prompt if the authentication validity duration has expired.

Key Attestation: Proving Key Integrity

Key attestation is a powerful feature that allows you to cryptographically verify the properties of a key pair stored in the Android Keystore. This includes verifying that a key is hardware-backed, its security level (TEE or StrongBox), its creation time, and its associated authorization list.

How Key Attestation Works

  1. Your app requests an attestation certificate chain for a specific key alias.
  2. The Keystore generates a self-signed attestation certificate (or a chain if backed by a separate attestation root) that contains an extension with the key’s properties.
  3. Your app sends this certificate chain to a backend server.
  4. The backend server verifies the certificate chain, including checking the Google attestation root certificate and parsing the attestation extension to extract the key’s properties.

Requesting Attestation Certificates

To request attestation for a key, you need to add setAttestationChallenge() when building your KeyGenParameterSpec.

import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.Certificate; import java.security.KeyStore; import java.util.Arrays; import java.util.Enumeration;  public class AttestationHelper {     private static final String ANDROID_KEYSTORE = "AndroidKeyStore";     private static final String ATTESTATION_KEY_ALIAS = "attestation_key";      public void createAttestableKey(byte[] attestationChallenge) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(             KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);          KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(             ATTESTATION_KEY_ALIAS,             KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)             .setBlockModes(KeyProperties.BLOCK_MODE_ECB)             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)             .setUserAuthenticationRequired(true)             .setUserAuthenticationValidityDurationSeconds(30)             .setAttestationChallenge(attestationChallenge); // The unique challenge          if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {             builder.setIsStrongBoxBacked(true);         }          keyPairGenerator.initialize(builder.build());         keyPairGenerator.generateKeyPair();     }      public Certificate[] getAttestationChain() throws Exception {         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);         keyStore.load(null);          Certificate[] certificateChain = keyStore.getCertificateChain(ATTESTATION_KEY_ALIAS);          if (certificateChain != null && certificateChain.length > 0) {             // The first certificate in the chain is the attestation certificate             // The subsequent certificates are the signing certificates,             // culminating in the Google attestation root.             return certificateChain;         }         return null;     } }

The attestationChallenge should be a cryptographically secure random number generated by your backend server for each attestation request. This prevents replay attacks. The backend server then validates this challenge against the one included in the attestation certificate.

Verifying Attestation (Backend)

Verifying attestation is typically done on a backend server. The process involves:

  1. Receiving the certificate chain from the Android device.
  2. Verifying the chain’s trust to a known root of trust (Google’s attestation root).
  3. Parsing the attestation extension in the key certificate (ASN.1 structure) to extract key parameters, device properties, and the provided challenge.
  4. Comparing the extracted challenge with the one issued by the server.
  5. Making a security decision based on the verified properties (e.g., is the key hardware-backed? Is the device rooted?).

Best Practices and Considerations

  • Error Handling: Always wrap Keystore operations in try-catch blocks to handle exceptions like KeyStoreException, NoSuchAlgorithmException, etc.
  • User Authentication: For highly sensitive operations, always require user authentication for key usage. Consider a zero-second validity duration if authentication is needed for every use.
  • Key Invalidation: If a key becomes compromised or needs to be rotated, generate a new key and delete the old one. Remember to re-encrypt any data stored with the old key.
  • Backwards Compatibility: While StrongBox requires Android 9+, TEE-backed Keystore has been available since Android 6.0. Design your application to gracefully handle different security levels based on the device’s capabilities.
  • Key Aliases: Use clear and consistent key aliases.

Conclusion

The Android Hardware-Backed Keystore provides a robust foundation for building highly secure applications. By moving cryptographic operations into isolated hardware, you significantly enhance the protection of sensitive keys and data against software-based attacks. Integrating key attestation further allows your backend services to verify the integrity and security characteristics of the client device, enabling dynamic trust decisions. Mastering these capabilities is essential for any developer serious about application security on the Android platform.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner