April 28, 2026

A Guide to Android Keystore and Hardware-Backed Cryptography

Quick jump

Use these links to jump to sections of the blog that are of particular interest to you. The blog proper starts afterwards.

  1. Should I use the Android Keystore?
  2. How do I check Android Keystore support on the user’s device at runtime?
  3. How do I use the Android Keystore?
    1. Runtime performance considerations
    2. Don’t call the Android Keystore on the main thread
    3. Handle KeystoreExceptions gracefully
  4. Use StrongBox for extremely sensitive keys
  5. Additional ways to secure my keys?
    1. Require biometric authentication and/or lock screen credentials
    2. Verify hardware backing through key attestation
    3. Apply static and runtime protections to your code

Introduction

The number of mobile apps that handle sensitive information is significantly higher than it was several years ago. And as mobile devices increasingly become the primary means of interacting with digital services, such as banking or government services, there will only be more such apps in the future. Developers are keenly aware that the user information they handle needs to be properly protected. Encryption is a common technique for protecting such information, for example through the KeyStore API.

Despite this awareness, research shows that in the particular case of Android, they tend to rely on software-backed cryptography. This may not even be a conscious choice on their part, since to this day, software-backed cryptography is still the default on Android.

The Android platform has offered hardware-backed cryptography implementations ever since Android 7. The documentation refers to these implementations collectively as the “Android Keystore system”. By isolating cryptographic keys in hardware, it meets rigorous security standards like OWASP MASWE-0014.

This blog explores:

  • When you should use the Android Keystore system.
  • What exactly it is.
  • How to use it.
  • Pitfalls you need to be aware of.
  • What else you can do to safeguard your users’ data.

Should I use the Android Keystore?

You need to judge this based on:

  1. The kind of user data your application handles, examples being:
    1. Payment credentials.
    2. Personally identifiable information (PII), such as social security numbers.
    3. Privacy-sensitive information, like health data.
  2. Sensitive business assets that you consider as part of your threat model.
      For example, if your application generates an asymmetric keypair (which the Android Keystore can do, without ever storing the private key in memory).
  3. Regulatory requirements, such as those imposed by the GDPR.
  4. Your application’s minimum supported Android version is 9 (API 28), see minSdkVersion.

To elaborate on the API level constraints:


User device API level (Android version) Considerations
>= 28 (Android 9) You can be confident a hardware-backed keystore is available: Our threat monitoring data indicates that, in practice, the vast majority of devices running Android 9 or later support hardware-backed key attestation.
24 - 27 (Android 7.0)
18 - 23 Devices are not required to provide a hardware-backed keystore.
< 18 (Android 4.3) Android Keystore APIs are not available at all.

Starting from Android 6 (API 23), you can check at runtime if a key is actually stored in hardware when you opt in to the Android Keystore, which we will show how to do a bit later.

Why is using the software Java Keystore not secure?

Software implementations such as Java Keystore expose cryptographic keys in application memory. Attackers with the necessary know-how can extract your keys from memory, and then decrypt data as they please.

The diagram below shows a hypothetical scenario where you:

  1. Generate a key server-side.
  2. Send it to a user device.
  3. Store it in the Java Keystore on the device.
  4. Retrieve it from the Java Keystore at a later point.

It also shows where attackers can attack the Keystore API to extract the key:

GUARDSQUARE_1_In-Blog_A-Guide-to-Android-Keystore-and-Hardware-Backed-Cryptography_1200

Appendix A fleshes out a code example and demonstrates practically how attackers can read the key.

Why is using the Android Keystore more secure?

The security benefits of Android Keystore are twofold:

  1. By design, key material never leaves the dedicated hardware component, and it never enters the application process. This makes extracting keys from the device significantly harder, since you cannot dump the keys from memory.
  2. It reduces the risk of unauthorized use of keys within the Android device. Applications must specify the authorized uses of their keys. The Android Keystore enforces the specified restrictions. Even if attackers manage to spoof cryptographic requests on behalf of the application, this principle limits the exact ways they can do so.

Which types of hardware keystore components exist?

Concretely, the hardware component can be either of these:

  1. A Trusted Execution Environment (TEE). It runs on the same processor as the Android OS, but is isolated from the rest of the system as part of ARM’s TrustZone.
  2. A StrongBox Secure Element (SE). This is a dedicated secure processor, entirely separate from the processor on which the Android OS runs.

Using the SE makes your cryptographic keys more resilient to extraction through side-channel attacks when compared to the TEE. However, as the documentation notes, this comes with a performance trade-off: The SE is slower, more resource-constrained, and supports fewer concurrent operations. You may want to use the SE only for extremely sensitive keys. For example, any keys that encrypt information related to financial transactions.

What exactly is different about software-backed vs hardware-backed?

The table below gives an overview of the differences:

  Software-backed Hardware-backed
Runtime performance Fast Noticeable performance overhead for large payloads, especially for asymmetric encryption
Vulnerability to extraction Can be extracted from memory using common reverse engineering tools Requires more advanced side-channel attacks
Enforcement of cryptographically strong configuration Insecure defaults[1] Enforces cryptographically strong configuration
1 For example, Google Play Console explicitly warns you if you encrypt an AES key with Cipher.getInstance(“AES”). This results in AES/ECB mode being used, which is cryptographically weak.

Runtime performance overhead

To illustrate the runtime performance overhead in more detail, we refer back to the research paper cited earlier. One of the experiments the researchers performed was to encrypt a payload of several different lengths with AES-GCM-256 symmetric encryption on a Pixel 8 across 10 iterations. The chart below shows the average durations they observed for these experiments:

GUARDSQUARE_2_In-Blog_A-Guide-to-Android-Keystore-and-Hardware-Backed-Cryptography_1200

We can draw these conclusions from those observations:

  1. It’s very important not to perform cryptographic operations on the main thread when using the Android Keystore. Doing so carries a real risk of introducing Application Not Responding (ANR) errors, since you would be blocking UI operations.
  2. The StrongBox SE does indeed come with a steep performance trade-off, which quickly becomes quite bad as payload size increases. This reinforces the warning from the documentation that you should only use the SE if you need resilience against side-channel attacks.

How can I check at runtime that the user’s device has a hardware-backed keystore?

You can look at the result of PackageManager.hasSystemFeature(“android.hardware.hardware_keystore”). Like the documentation for FEATURE_HARDWARE_KEYSTORE says, this feature is guaranteed to be set for devices that launch with Android 12 (API 31) or later.

If the result is false, then it may still be the case that the device does have a hardware-backed keystore. The only reliable way to find out is to opt in to the Android Keystore, generate a key, and then check if it is indeed bound to hardware. The next section describes how to do that.

How do I actually use the Android Keystore? What are the caveats?

Use it

It’s pretty easy to opt in to the Android Keystore system. You just need to pass “AndroidKeyStore” as the type to KeyStore.getInstance.

Snippet 1 shows how to generate an AES/GCM key with no padding:

public static KeyStore initializeHardwareStore() { try { // Set up key generation. Specify that the Android Keystore must be used. KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); // Specify to generate an AES/GCM key with no padding. keyGenerator.init( new KeyGenParameterSpec.Builder(AES_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); // Generate the key. It now resides in the device's hardware component. SecretKey secretKey = keyGenerator.generateKey(); // Get a reference to the Android Keystore. It already contains the key // we generated above. KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); return keyStore; } catch (Exception exception) { throw new RuntimeException(exception); } }

If you want, you can additionally check at runtime if a key is indeed stored in hardware:

  1. On Android 12 and later (API 31), you can check the return value of KeyInfo.getSecurityLevel() for a particular Key object to see if it’s either SECURITY_LEVEL_TRUSTED_ENVIRONMENT or SECURITY_LEVEL_STRONGBOX. The KeyInfo API reference gives an example of how you can get a KeyInfo instance for a Key object.
  2. On Android 11 and below (API 30), you can use KeyInfo.isInsideSecureHardware(), instead.

As you can see, you do need to use dedicated Android Keystore APIs to actually generate keys, namely the KeyGenParameterSpec class.

Don’t call the Android Keystore on the main thread

The hardware component can only handle so many concurrent operations at once. Your application will potentially contend with other applications on the device that simultaneously request cryptographic operations from the Android Keystore.

We recommend:

  • Always perform Android Keystore operations on a background thread. You are very likely to get Application Not Responding (ANR) errors if you don’t do so.
  • Provide UI feedback, such as a progress indicator, if the operation takes longer than expected.

Handle KeystoreExceptions gracefully

It is possible on some devices that Android Keystore operations fail under specific circumstances. These failures manifest as KeyStoreExceptions. You could implement a retry mechanism when such exceptions do occur. You can fall back to software-backed cryptography as a last resort, but this opens the door for attackers to force a fallback to software and make key extraction easier.

Android 13 (API 33) introduces a dedicated KeyStoreException type for Android Keystore. It captures additional information compared to the standard Java KeyStoreException:

  • If the failure was system-specific or key-specific.
  • If retrying the operation at a later time is likely to succeed.

Does using Android Keystore give me perfect security?

No. Attackers can potentially use keys that are stored on the device, even if they are unable to actually extract the keys from the device. Therefore, encrypted data that’s stored on the device itself can still be vulnerable. You can mitigate this by specifying during key creation that the user needs to authenticate themselves before use (e.g. through biometrics).

Opting in to the Android Keystore currently results in using the TEE by default. The TEE is more vulnerable to key extraction using side-channel attacks than StrongBox (SE). The SE is more isolated from the rest of the system, making it more resistant to side-channel attacks. Naturally, using the SE still doesn’t prevent attackers from potentially using keys that are stored inside the SE; it’s just much more difficult to extract the keys from the SE to then use them outside the device itself.

How do I use StrongBox?

StrongBox was first introduced in Android 9. Even then, you first need to check that the device has a StrongBox implementation and indicate whether you want to use it if it does. If you request StrongBox on a device that does not support it, you will get an exception at runtime.

Taking all that into account, you can adapt Snippet 1 into Snippet 2 like so:

public static KeyStore initializeStrongBoxHardwareStore(Context context) { try { // Check if the device supports StrongBox. boolean isAndroid9OrAbove = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; boolean requestStrongBox = isAndroid9OrAbove && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); // Pass the other specification arguments. // Specify to generate an AES/GCM key with no padding. KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(AES_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE); // If the device does support StrongBox, request it now. if (requestStrongBox) { specBuilder.setIsStrongBoxBacked(true); } // Set up key generation. Specify that the Android Keystore must be used. KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); keyGenerator.init(specBuilder.build()); // Generate the key. It now resides in the device's hardware component. SecretKey secretKey = keyGenerator.generateKey(); // Get a reference to the Android Keystore. It already contains the key // we generated above. KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); return keyStore; } catch (Exception exception) { throw new RuntimeException(exception); } }

We already mentioned above that StrongBox-backed cryptographic operations can be orders of magnitude slower than TEE-backed operations. You will need to be careful about where exactly to request StrongBox backing, especially if you expect to encrypt payloads that are several megabytes large.

Financial transactions are a good candidate for StrongBox backing, even if the payloads involved are relatively large.

Anything else I can do to secure my keys?

We have three concrete suggestions which we will describe in more detail:

  1. Make the user authenticate to their device before they can use your keys.
  2. Use key attestation where available.
  3. Apply static and runtime protections to your code.

Require biometric authentication and/or lock screen credentials

Starting from Android 11 (API 30), you can require that the user authenticate before using a key by calling setUserAuthenticationParameters. You can require biometric credentials, lock screen credentials, or both. Doing this makes it more difficult for attackers to use keys to decrypt data stored on the device itself.

Verify hardware backing through key attestation

Modern Android devices are also capable of performing key attestation. It allows your application to verify that the keys you use in your application really are backed by hardware, and to verify the properties of those keys.

Key attestation also tells you whether a full chain of trust exists that verifies the device’s boot image itself has not been tampered with. For example, an attacker might try to use an OS-level or kernel-level tool that intercepts requests to create keys and then falsely claim that the created keys are stored in secure hardware. If the key attestation result says that a full chain of trust does indeed exist, you can be confident that such a tool is not being used.

Apply static and runtime protections to your code

Finally, you can add static and runtime protection to your application to:

  1. Hide where and how you’re using cryptographic operations.
  2. Make it more difficult for attackers to use your application’s keys, even if they compromise a device.

Guardsquare solutions offer both static protection, through code obfuscation and encryption techniques, and runtime protection, which defends against common threats such as application repackaging and hooking.

Conclusion

While software-backed cryptography remains the Android default, it leaves keys vulnerable to memory extraction and often uses weaker cryptographic defaults. With broad support on devices running Android 9 (API 28) and later, it provides strong security benefits, as long as you properly manage potential pitfalls.

Key takeaways:

  1. Cryptographic key isolation: Using a hardware-backed keystore ensures your keys are never exposed in memory, which defeats memory extraction attacks, even if the OS is compromised.
  2. Performance considerations: Android Keystore operations can have significant runtime overhead. Never call the Android Keystore on the main thread.
  3. Handle failures gracefully: Under rare circumstances, correct use of Android Keystore operations can throw KeystoreExceptions.
  4. Resilience against side-channel attacks: The Android Keystore uses the TEE by default. StrongBox is more resilient against side-channel attacks than the TEE, but is significantly slower than the TEE. Consider using it for particularly high-risk operations, such as financial transactions.
  5. Be aware of remaining security gaps: The Keystore prevents key extraction, not key usage. Pair Keystore operations with user authentication (e.g. biometrics).
  6. Hide what you’re using cryptographic keys for in the first place: Throw attackers off by applying both static and runtime protections to your application code.

Appendix A: Key generation and interception example

We’ll demonstrate that using the defaults to initialize a software Java KeyStore into which we import an encrypted key leaves that key vulnerable to extraction from memory, as long as that key remains in active use in the application.

Specifically, this example:

  1. Creates an AES key.
  2. Exports it to a file, in JSON Web Encrypted (JSE) format.
  3. In an Android application, it reads the key from the file, decrypts it, and loads it into a Java KeyStore.

Create the encrypted AES key and export it

This Python Snippet 3 generates an AES key and stores it in a file in JSE format. At time of writing the used jwcrypto version is 1.5.6.

def generate_jwe(): # Generate the raw 32 key bytes. Pass 31 to urandom, since a later conversion from hex string to bytes # adds an extra byte. aes_key_bytes = os.urandom(31).hex() print(f"aes_key_bytes: {aes_key_bytes}") # Create a JWK (JSON Web Key) from a password string. password = "mysecretpassword" my_jwk = jwk.JWK.from_password(password) # Configure the JWE. # alg: PBES2-HS256+A128KW (Password-based key wrapping) # enc: A128CBC-HS256 (AES-CBC with HMAC for content encryption) my_salt = "8_byte_salt" iterations = 4096 protected_header = { "alg": "PBES2-HS256+A128KW", "enc": "A128CBC-HS256", "p2s": my_salt, "p2c": iterations, } # Encrypt and write out the token to a file. jwetoken = jwe.JWE(aes_key_bytes.encode('utf-8'), recipient=my_jwk, protected=protected_header) encrypted_token = jwetoken.serialize() with open('encrypted_key.jwe', 'w') as f: f.write(encrypted_token)

We ran it locally and got an encrypted_key.jwe file. For this particular run, the print statement reported:

aes_key_bytes: c5c1e8f1b82ee5ef924466b76dfea77c5a8a8c1d5749af8b31013e83d4493c

The contents of encrypted_key.jwe are:

{ "ciphertext": "cSi3KevofTbF4bBy_cA3ACeZ5m4OVDNNpaZBbUKgasEl4rFM1kWHoqWfIGdsuVEk57wH7l6ChDAHfmJQdhJ2jw", "encrypted_key": "DRpPYDqEbZGhdrC25Q9Y4duHyFslDbps60go5K46fd1537Xz5eIlCg", "iv": "yjDo2tZPsB-wu9sjRMmTLQ", "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwicDJjIjo0MDk2LCJwMnMiOiI4X2J5dGVfc2FsdCJ9", "tag": "AQoN3mRcghyNI7N5f9Afnw" }

Load the encrypted key into a KeyStore

Now, we read in the file in an Android application and load the key into a Java KeyStore. We use a few third-party dependencies to make the example a bit easier to write. These are:

  1. Gson, version 2.13.2.
  2. Nimbus JOSE + JWT, version 10.8.

We declare the Gson object structure as follows in Snippet 4:

import com.google.gson.annotations.SerializedName; public class JWEJson { @SerializedName("protected") public String header; @SerializedName("encrypted_key") public String encryptedKey; @SerializedName("iv") public String iv; @SerializedName("ciphertext") public String ciphertext; @SerializedName("tag") public String tag; }

We now push encrypted_key.jwe to an Android device’s /data/local/tmp directory and import it into a Java KeyStore like this in Snippet 5:

package com.example.softwarekeydemo; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.nimbusds.jose.JWEDecrypter; import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.crypto.PasswordBasedDecrypter; import com.nimbusds.jose.util.Base64URL; import java.io.FileReader; import java.lang.reflect.Type; import java.math.BigInteger; import java.security.KeyStore; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class LoadJweKey {     public static final String AES_KEY_ALIAS = "my-secret-aes-key";     public static final String PASSWORD = "mysecretpassword";     private static final Type JWE_JSON_TYPE = new TypeToken<JWEJson>() {}.getType();     private LoadJweKey() {}     private static String readKeyHexStringFromFile() throws Exception {         // Use Gson to read and parse the file containing the JWE key.         Gson gson = new Gson();         JsonReader reader = new JsonReader(new FileReader("/data/local/tmp/encrypted_key.jwe"));         JWEJson json = gson.fromJson(reader, JWE_JSON_TYPE);         // Initialize a JWEObject based on the read JSON object.         Base64URL header = new Base64URL(json.header);         Base64URL encryptedKey = new Base64URL(json.encryptedKey);         Base64URL iv = new Base64URL(json.iv);         Base64URL ciphertext = new Base64URL(json.ciphertext);         Base64URL tag = new Base64URL(json.tag);         JWEObject jweObject = new JWEObject(header, encryptedKey, iv, ciphertext, tag);         // Create the decrypter and set it up to use the password.         JWEDecrypter decrypter = new PasswordBasedDecrypter(PASSWORD);         // Decrypt the JWE key.         jweObject.decrypt(decrypter);         // Extract the hex-encoded key bytes from the payload.         return jweObject.getPayload().toString();     }     public static KeyStore loadJweKeyIntoSoftwareKeyStore() {         try {             String keyHexString = readKeyHexStringFromFile();             // Initialize a KeyStore.             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());             keyStore.load(null);             // Add the parsed key to the KeyStore.             byte[] keyMaterial = new BigInteger(keyHexString, 16).toByteArray();             SecretKey secretKey = new SecretKeySpec(keyMaterial, "AES");             KeyStore.ProtectionParameter protectionParam = new KeyStore.PasswordProtection(PASSWORD.toCharArray());             KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);             keyStore.setEntry(AES_KEY_ALIAS, secretKeyEntry, protectionParam);             return keyStore;         } catch (Exception exception) {             throw new RuntimeException(exception);         }     } }

Note that for the sake of the example, we load the key material we decrypt into a SecretKeySpec object. It’s a class that holds the raw bytes of a key. This is still a necessary step to register the AES key we decrypted from the file into our KeyStore object. We do specify password protection through a PasswordProtection object, meaning that the KeyStore will store our key in encrypted format.

Then, we retrieve the imported key from the keystore like this in Snippet 6:

KeyStore softwareKeyStore = LoadJweKey.loadJweKeyIntoSoftwareKeyStore(); try { Key softwareBackedKey = softwareKeyStore.getKey(LoadJweKey.AES_KEY_ALIAS, LoadJweKey.PASSWORD.toCharArray()); } catch (Throwable t) { throw new RuntimeException(t); }

Attack the key

Now that we have an Android application, we can see how it behaves at runtime. Since we have the source code, it’s pretty easy for us to do so: We can simply step through the code with a debugger. Let’s see what we find when we do so on a physical Samsung A34 5G (Android 16) device.

GUARDSQUARE_3_In-Blog_A-Guide-to-Android-Keystore-and-Hardware-Backed-Cryptography_1200

We see that KeyStore.getInstance(KeyStore.getDefaultType()) resulted in the KeyStore being initialized with a BouncyCastleProvider. Bouncy Castle offers APIs for software-backed cryptography. This demonstrates that using the defaults indeed results in us getting a software KeyStore.

We see also that keyHexString matches what Snippet 3 gave us, so the decryption of the key from the JSE contents indeed happened correctly.

Make note of the hash code of the secretKey object (31993).

We now step into the part where we load the key from the KeyStore:

GUARDSQUARE_4_In-Blog_A-Guide-to-Android-Keystore-and-Hardware-Backed-Cryptography_1200

We see that under the hood, the Key object is also a SecretKeySpec object. Importantly, it’s a different object from the SecretKeySpec object we used as a medium to import the key into the key store (hash code 32003 vs the earlier hash code 31993). Indeed, if you look a little deeper into the Android platform’s Bouncy Castle code, it ultimately instantiates a new Key object based on what the entry from the KeyStore contains. That Key object contains the actual key material. As a result, when we print the hex string from the Key’s underlying byte array, we see our original hex string again.

Attackers don’t have the source code. They will therefore only have a release version of the application available, which isn’t debuggable. However, attackers can still modify the application to become debuggable, or they can instrument the application with a tool such as Frida to achieve the same thing.

This Frida script (Snippet 7) prints the key bytes in hex string format:

Java.perform(function () { const KeyStore = Java.use('java.security.KeyStore'); const BigInteger = Java.use('java.math.BigInteger'); KeyStore.getKey.overload('java.lang.String', '[C').implementation = function (alias, password) { // Execute the original method to get the Key object var result = this.getKey(alias, password); if (result !== null) { console.log('[+] Key retrieved for alias: ' + alias); // Get the encoded byte array from the Key object var encodedBytes = result.getEncoded(); if (encodedBytes) { // Equivalent to: new BigInteger(1, encodedBytes).toString(16) // The '1' ensures the BigInteger is treated as a positive number (magnitude) var bigInt = BigInteger.$new(1, encodedBytes); var hexString = bigInt.toString(16); console.log(' [-] Key Hex (BigInteger): ' + hexString); } else { console.log(' [-] Key encoding is null (common for some hardware-backed keys).'); } } return result; }; });

Example usage after you install the demo application:

$ frida -U -f com.example.softwarekeydemo -l script.js ____ / _ | Frida 17.3.2 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at https://frida.re/docs/home/ . . . . . . . . Connected to Android Emulator 5554 (id=emulator-5554) Spawned `com.example.softwarekeydemo`. Resuming main thread! [Android Emulator 5554::com.example.softwarekeydemo ]-> [+] Key retrieved for alias: my-secret-aes-key [-] Key Hex (BigInteger): c5c1e8f1b82ee5ef924466b76dfea77c5a8a8c1d5749af8b31013e83d4493c

We initially started from an encrypted key. However, we now see that every time the key is used, it is plainly visible in the application’s memory. The key is either entirely unencrypted, or it is still encoded but holds the necessary information to decode it. An attacker with the necessary skills can therefore dump the application’s memory and recover the key from the memory dump if they have full control over the device the application runs on!

Appendix B: Closer look at Android Keystore internals

Let’s take a look at what we see when we debug Snippet 1 on the same Samsung A34 5G device:

GUARDSQUARE_5_In-Blog_A-Guide-to-Android-Keystore-and-Hardware-Backed-Cryptography_1200

The SecretKey object we get is an AndroidKeyStoreSecretKey. It has no fields that contain the key material. Indeed, its getEncoded implementation just returns null. It therefore is just a handle to a key entry that is stored inside the device’s Android Keystore — the key material itself is not visible in the object.

Tag(s):

Thomas Vochten

Discover how Guardsquare provides industry-leading protection for mobile apps.

Request Pricing

Other posts you might be interested in