Checking Device Cryptography Capabilities on Android and iOS

Jim Hawkins
VMware 360
Published in
13 min readSep 6, 2023

Fifth in a series comparing the cryptographic programming interfaces of Android and iOS.

The first four in the series were about

Each of those posts suggested that you should check the capabilities of a mobile device before attempting to use its cryptographic programming interfaces. They also each promised that a method for doing that would be covered later. Now is later.

Illustration with a person, circles, and a padlock

You can check device cryptography capabilities at a couple of stages in the development cycle.

Some capabilities on some platforms can be checked at an early stage when you are designing your application, at design time. You might need to know the makes and models of devices that you will support, or you might only need to know the operating system versions that you will support. A design-time check could mean reading the reference documentation on a developer website for example.

Other capabilities cannot be checked until run time. Those checks will be made by your app calling the operating system’s programming interfaces. The programming interfaces can be utilized in Kotlin on Android, or in Swift on iOS.

Both design-time and run-time checks are discussed here. Some sample code is also included.

Series Disclaimer: IANAC

Before you follow my advice be warned that I Am Not A Cryptographer (IANAC).

My understanding of cryptography is based on some reading, some coding, and on years of standing next to security engineers who themselves stand next to cryptographers.

Cryptographic terms are explained briefly in context as they appear in this discussion.

Cryptographic capabilities of mobile devices

The cryptographic capabilities of mobile devices that should be checked can be divided into these categories.

  • What types of hardware security module (HSM) protection are available on the device.
  • What cryptographic processing does the device support.

These types of HSM protection may be available on any given mobile device.

  • Trusted Execution Environment (TEE) that is isolated within the main processor.
  • Secure element (SE) that is separate to the main processor but still part of the system-on-chip (SoC).

Those are well-known technologies that you can read about for background.

In my experience, all recent smartphones have either or both of those types of HSM. However, the scope of this discussion is the Android and iOS operating systems, both of which can run on devices that don’t have any type of HSM, or don’t make it available to the app layer. The checks that can be made for HSM availability are discussed and compared below.

The category of cryptographic processing support includes all of these aspects.

  • Supported cryptography algorithms, such as Rivest–Shamir–Adleman (RSA) and Advanced Encryption Standard (AES).
  • Supported block cipher modes, such as Electronic Code Book (ECB), Cipher Block Chaining (CBC), and Galois/Counter Mode (GCM).
  • Supported padding modes, such as none or Optimal Asymmetric Encryption Padding (OAEP).
  • Supported hash functions, such as those in the standard Secure Hash Algorithm families like SHA-1 or SHA-256.
  • Supported combinations of the above, such as AES encryption with GCM and no padding.

Any of those aspects could be important for your app. How they can be checked on Android and iOS devices is discussed below.

Checking hardware security module types on Android

The HSM types available on an Android device could be

  • TEE only.
  • TEE and SE.
  • neither TEE nor SE.

There is an SE solution branded Android StrongBox that is generic to Android. There might also be proprietary SE or other HSM solutions available from some Android device vendors. Proprietary solutions would typically require an additional license agreement, integration of a proprietary library, or other extras. I haven’t considered those here.

You can read about the Android generic HSM types, TEE and SE, on their developer website for example here and here.

(Tip o’ the hat to a 2018 blog post on proandroiddev.com although the official documentation seems to have been much improved since it was written.)

In my experience of developing for Android smartphones every phone has a TEE and a few phones also have an SE. It’s difficult to find out in advance which device models have the SE. For example that information typically won’t be in the device’s technical specifications. That means the capability can’t be discovered at design time.

On paper, there is a specific Android programming interface by which you should be able to discover the HSM type at run time. Code in Kotlin would be like this.

val pm = activity.packageManager
val hasTEE = pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE)
val hasSE = pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
// Above is for illustration only and isn't reliable. DO NOT USE.

Reference documentation can be found on the Android developer website, for example in the PackageManager class reference.

However, some mainstream Android 12 smartphones that have a TEE on which I ran the above code returned hasTEE false. I instead had to confirm the devices' HSM types by creating a key and then checking the characteristics of its stored location.

Code to create keys for the purpose of confirming the HSM type could look like this.

private val providerName = "AndroidKeyStore"

@TargetApi(Build.VERSION_CODES.Q)
fun hasHardwareSecurity() = keyGeneratorGeneric(
UUID.randomUUID().toString(), providerName
).generateKey().run {
val keyInfo = SecretKeyFactory.getInstance(algorithm)
.getKeySpec(this, KeyInfo::class.java) as KeyInfo
KeyStore.getInstance(providerName).apply { load(null) }
.deleteEntry(keyInfo.keystoreAlias)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
when (keyInfo.securityLevel) {
KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT,
KeyProperties.SECURITY_LEVEL_STRONGBOX -> true
else -> false
}
else keyInfo.isInsideSecureHardware
}

@TargetApi(Build.VERSION_CODES.Q)
fun hasStrongBox() = try {
val alias = UUID.randomUUID().toString()
keyGeneratorStrongBox(alias, providerName).generateKey()
KeyStore.getInstance(providerName)
.apply { load(null) }
.deleteEntry(alias)
true
}
catch (exception: StrongBoxUnavailableException) {
false
}

@TargetApi(Build.VERSION_CODES.Q)
private fun keyGeneratorGeneric(alias: String, providerName: String) =
KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, providerName
).apply { init(
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).run {
setKeySize(256)
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
build()
}
) }

@TargetApi(Build.VERSION_CODES.Q)
private fun keyGeneratorStrongBox(alias: String, providerName: String) =
KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, providerName
).apply { init(
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).run {
setKeySize(256)
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setIsStrongBoxBacked(true)
build()
}
) }

The code is compatible with Android version 10, aka Android Q, which corresponds to an API level of 29.

Notes on the code.

  • The informative functions are hasHardwareSecurity() and hasStrongBox().
  • Each makes use of a utility function to instantiate a KeyGenerator with suitable key specification. The key specification for StrongBox has setIsStrongBoxBacked(true) but is otherwise the same as the specification for basic hardware security.
  • A random universally unique identifier (UUID) is used as the alias, or name, of the confirmation key. That should avoid possible collision with any actual key already in the store.
  • The basic hardware-security confirmation works by first using a SecretKeyFactory instance to create a KeyInfo representation of the generated key. The presence of hardware security is then determined by checking a property of the KeyInfo representation, either the securityLevel or the isInsideSecureHardware flag. The securityLevel property is preferred but is only available at API level 31 or greater.
  • The StrongBox confirmation code works by checking if a specific exception is raised when key generation is attempted.
  • If key creation succeeded then the key is deleted straight away.
  • More detailed description of each code step in the key generator functions is provided in the earlier post AES key generation. Look for the heading Generate an AES Key on Android.
  • However you check it, an Android emulator always seems to present itself as having no HSM, regardless of the capability of the corresponding real device.

The above code appears to represent a reliable method for checking HSM type on Android devices.

Checking hardware security module types on iOS

The only HSM type that may be available on an iOS device is the Apple Secure Enclave processor (SEP). You can read about the SEP on the Apple website, for example here. support.apple.com/…/security/…
Based on the diagram on that page at time of writing, the SEP is an SE although Apple don’t as such say so.

That page also lists which devices have the SEP. So you can discover at design time what HSM type will be available on a device you intend to support with your app.

The presence of the SEP on an iOS device can be confirmed at run time. Swift code to do that could look like this.

import CryptoKit

let hasSE = SecureEnclave.isAvailable

Reference documentation is here. developer.apple.com/…/cryptokit/secureenclave/isavailable

At time of writing I don’t have access to a device on which that code returns false. On an iPod Touch simulator running iOS 13.7 the code still returns true for example, and same goes for the Apple Watch simulator.

Checking cryptographic processing support on Android

Some aspects of the cryptographic processing support of an Android device can be checked at design time, but all must be confirmed at run time.

For example, at design time Android developer pages like these could be read.

Those pages have lists of algorithms and cipher modes, and the Android API levels at which they are supported. However, supported there doesn’t mean available at run time. For an algorithm and cipher mode to be available there has to be a security provider that supports it on the device. Providers are a concept from the Java Security system, which is discussed in the earlier RSA key generation post.

Information about the security providers that are available on an Android device, and the cryptographic processing that they support, can be obtained at run time. One way is to call the Security.getProviders() function. Reference documentation is here for example.
developer.android.com/.../Security#getProviders()

Code like the following will generate a list of the cryptographic processing that the current device supports, in JavaScript Object Notation (JSON).

val jsonObject = JSONObject(Security.getProviders().map {
it.name to it.toMap()
}.toMap())

The above code comes from the sample application here:
github.com/vmware/captive-web-view/…/CaptiveCrypto/…/storedkey/Capabilities.kt#L55

If you run that application, you can generate the list by navigating to Native Key Store and tapping Capabilities, then tap Write or copy the data. If you write the data, you can retrieve the file for example in Android Studio by using the Device File Explorer. You could instead paste the code into your own application and then inspect the jsonObject in the Android Studio debugger.

The supported processing list has a hierarchy.

  • Top level is the security providers on the device. For example: AndroidKeyStore, AndroidOpenSSL, and KnoxAndroidKeyStore are all providers that might be present.
  • Second level is the identification of, and services supported by, each provider. For example: key generation, ciphers, and signatures are all services that might be present.
  • Under the second level there are also more details of specific services.

The following extracts could appear in a JSON version of the list for example.

{
"AndroidKeyStore": {
...
"KeyFactory.RSA": "...",
...
"KeyGenerator.AES": "...",
"KeyPairGenerator.RSA": "...",
...
"SecretKeyFactory.AES": "...",
...
},
"AndroidKeyStoreBCWorkaround": {
"Cipher.AES/GCM/NoPadding": "...",
"Cipher.AES/GCM/NoPadding SupportedKeyClasses": "...AndroidKeyStoreSecretKey",
"Cipher.RSA/ECB/OAEPPadding": "...",
"Cipher.RSA/ECB/OAEPPadding SupportedKeyClasses":
"...AndroidKeyStorePrivateKey|...AndroidKeyStorePublicKey",
...
},
...
}

(Three dots are used as marks of omission, either within a string or within a structure.)

Those extracts can be read like this.

  • The Android KeyStore provider can represent information about RSA key pairs and AES keys. Use a KeyFactory instance for RSA key pair information. Use a SecretKeyFactory instance for AES key information.
  • The Android KeyStore can generate AES keys and RSA key pairs.
  • The AndroidKeyStoreBCWorkaround provider name appears to be a reference to a work around for a bug in the Bouncy Castle (BC) open source library used by Android.
  • AES keys in the Android KeyStore support a cipher specification of GCM with no padding, among others.
  • RSA keys in the Android KeyStore support a cipher specification of ECB with OAEP padding, among others.

Those specifications were used in the sample application, having been found in the list on a couple of Android devices.

Checking cryptographic processing support in Android StrongBox

Some devices have an Android StrongBox SE, which your code can discover at run time, see above under Checking hardware security module types on Android.

The SE has a subset of the algorithm capabilities of the device, and other limitations, according to this page on the Android developer website.
developer.android.com/…/keystore.html#HardwareSecurityModule

The SE subset of cryptographic capabilities doesn’t seem to be represented in the list generated from the Security.getProviders() function, above. For example there isn't a separate provider for the SE, nor is there a naming convention that marks SE capabilities. Any capability in that list could be supported by the SE, or by the Trusted Execution Environment (TEE), or both.

The page linked above does at least document the constraints on SE keys so you can check at design time. To confirm at run time you could, for example, create a key and try various cryptographic operations with various ciphers. Code for that would be too long to include here.

Checking cryptographic processing support on iOS

The cryptographic processing support of an iOS device can be checked at design time. Support is reflected by the structure and naming of the programming interface. That means you can check by reading the programming interface reference documentation.

For example, you can check what ciphers are supported for encryption with RSA keys in the reference documentation here.
developer.apple.com/…/security/seckeyalgorithm#2937149
There you can see that RSA encryption with OAEP mode padding and SHA-512 digest is supported, for example.

Not all processing support is documented on that page, nor even in that section of the reference documentation. For example, to check what ciphers are supported for encryption with AES keys, you would instead look in the CryptoKit documentation near here.
developer.apple.com/…/cryptokit/aes
Obliquely, it looks like you can’t specify a padding mode. Presumably iOS implements best practice, but that could be awkward in a use case requiring interoperation with another platform like a server.

You can check support for iOS key generation in the reference documentation too, with care. There’s a list of supported key types here.
developer.apple.com/…/keychain_services/item_attribute_keys_and_values#1679067
However, you have to open the page for a specific key type to check if an iOS device can generate it, using this programming interface.

To check the AES key generation capabilities of iOS you have to know that AES is a symmetric cipher and that symmetric key generation isn’t part of iOS Keychain Services. Instead, symmetric key generation is part of the CryptoKit framework. This means that you check, for example, what key lengths are supported for AES by reading this page.
developer.apple.com/…/cryptokit/symmetrickeysize

It’d be nice if there was a link from the AES key generation documentation in the key chain services section to the AES key generation documentation in the CryptoKit section, but there isn’t.

See the AES key generation post earlier in this series for a discussion about the different iOS programming interfaces for cryptography, and their mix of old-fashioned and modern features.

Checking cryptographic processing support in iOS Secure Enclave

Some devices have an Apple SEP, which your code can discover at run time, see above under Checking hardware security module types on iOS.

The SEP supports a subset of the cryptographic processing that the device supports. You can read about the subset on the Apple developer website, here for example.
developer.apple.com/…/protecting_keys_with_the_secure_enclave
That page has an introduction to the SEP. It also has code snippets for SEP programming that use the older Keychain Services interface. And it has this link to the reference documentation for SEP programming using the newer CryptoKit interface.
developer.apple.com/…/cryptokit/secureenclave

From that CryptoKit page you could check SEP support for cryptographic processing as reflected by the structure and naming of the programming interface. For example, you can see that the only supported cipher is NIST P-256 elliptic curve. (That refers to a particular Prime curve, P-256, standardized by the United States National Institute of Science and Technology, NIST.)

If you navigate down a bit you can discover that the SEP has the capability to, for example, create a shared secret from a private key that it holds and a public key from outside. See this page.
developer.apple.com/…/cryptokit/secureenclave/p256/keyagreement/privatekey/sharedsecretfromkeyagreement(with:)

As noted in the AES key generation post earlier in this series, Swift uses enum classes with associated data to stand in for its lack of namespaces.

So you can check SEP cryptographic processing support by browsing the CryptoKit framework reference documentation.

Something that could be confusing when checking the capabilities is that iOS also supports P-256 cryptography outside the SEP. Note these two links for example.

The first is reference documentation for P-256 within the SEP, the second for P-256 outside the SEP.

Comparison of checking device Cryptography capabilities on Android and iOS

Checking device cryptography capabilities is different for Android and iOS because of the differences between the platforms.

The Android operating system runs on a wide range of devices, not just smartphones. Its cryptographic programming interface is built on the Java Security and Java Cryptography systems, which support an even wider range of devices. By comparison, the iOS operating system runs on a very narrow range of devices. That means there is a smaller range of capabilities to discover for an iOS app and its developer.

Android runs on devices from different manufacturers, including its owner Google. All those manufacturers are more or less in competition with each other and Google. That could contribute to the circumspection about how security and cryptographic capabilities are described in Android devices’ marketing information. Manufacturers with their own branded solutions, like Samsung Knox for example, might be reluctant to include statements about support for Google branded solutions such as Android StrongBox. That ends up meaning that less checking of cryptographic capabilities can be done at design time and more must be done at run time.

There is an extensive Android programming interface for discovering device capabilities, cryptographic and otherwise. However, it doesn’t appear to have been reliably implemented by all manufacturers. In some cases it seems necessary to probe the device at run time to confirm its cryptographic capabilities, for example by attempting to create a key with particular characteristics.

The iOS operating system only runs on Apple devices. Their documentation can be clear and forthright about which of their devices have which cryptographic capabilities. So more checking can be done at design time and less needs to be confirmed at run time. Having said that, Apple’s documentation can be difficult to understand because Apple seem in general reluctant to describe their technologies with words and terms that other people also use. For example, they don’t as such say that their SEP is a type of Secure Element.

Scattered documentation

Documentation of cryptographic capabilities seems fairly scattered for both platforms.

There are lists of supported cryptographic processing for Android on several different pages in the documentation for the javax.crypto and java.security packages. The lists for iOS are split across several different pages in the documentation for Key Chain Services and the CryptoKit framework.

Conclusion

You can check the capabilities of Android and iOS devices before using their cryptographic programming interfaces. You will need a combination of design-time checks and run-time checks.

For Android there seem to be few capabilities that can be checked at design time so expect to code more run-time checks and perhaps include probing of capabilities in case the informative interfaces don’t work. For iOS almost all checks can be made at design time but expect to navigate broad and deep to find all the answers.

--

--

Jim Hawkins
VMware 360

Software developer working on secure mobilisation of enterprise data to Android and iOS devices.