Differences Between Text Encryption on Android and iOS

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

Jim Hawkins
VMware 360
14 min readMar 11, 2021

--

The first two in the series were about

The Android and iOS programming interfaces both support use of stored keys to encrypt short texts. The programming interfaces can be utilized in Kotlin on Android, or in Swift on iOS. This post includes sample code and discussion of the platforms’ programming interfaces, separately and comparatively.

Illustration with a person, circles, and a padlock

At this point in the series, the scope is as follows.

  • The stored key could be an asymmetric RSA (Rivest–Shamir–Adleman) key pair, or a symmetric AES (Advanced Encryption Standard) key.
  • If the stored key is an RSA key pair, then the public key will be used for encryption.
  • There is a single snippet for Android. The snippet covers both symmetric and asymmetric encryption.
  • There are two snippets for iOS, one for symmetric encryption and one for asymmetric. In the sample application code to which links are given, there is a single union class, StoredKey, that can represent either type. However, there isn’t much shared code so it has been rearranged into the snippets below for ease of reading here.

Scope will be expanded later in the series.

Series Disclaimer: IANAC

Before you copy and paste the code into your banking app, 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.

In case you understand even less than me about cryptography, a few terms and definitions are included after the code and discussions.

Encrypt a short text with a stored key on Android

Kotlin code to encrypt a short text with a key in the Android KeyStore could look like this:

The above snippet is based on the code here:
github.com/vmware/captive-web-view/…/captivecrypto/StoredKey.kt#L354
The code is compatible with Android version 6, aka Marshmallow, which corresponds to a minimum API level of 23.

Line notes:

  • Line 3:
    The alias could be the alias with which a key was generated earlier, using the RSA or AES key generation samples above.
  • Line 5:
    The constant “AndroidKeyStore” specifies that the key is in the hardware-backed key store on the device.
  • Line 7:
    The key variable will be set to the key to be used for encryption. Different classes are used for public keys and symmetric keys, but both implement the Key protocol. In either case, the instance will be more like a handle than a data structure, because the key material doesn't leave the store.
  • Line 9:
    When the Android KeyStore generates an RSA key pair, the private key will be stored as a key, but the public key will be stored in a certificate.
  • Lines 20 and 21:
    See the note about Android Cipher Specification, below.
  • Line 25:
    The OAEPParameterSpec object constructed here has the default values, except for the hash algorithm. The SHA-1 algorithm is generally deprecated, so SHA-512 is used instead.
  • Line 32:
    This code uses a null specification for AES and doesn’t give an initialisation vector (IV). This should mean that the cipher will generate a random IV itself, from a suitable secure random number generator (RNG).
  • Line 40:
    See the note about Android Cipher Specification, below.
  • Line 44:
    Use of UTF-8 is an assumption.
  • Line 47:
    The encryptedBytes variable has the encrypted ciphertext.
  • Line 48:
    The cipher.iv property will contain the initialisation vector. The IV will be needed in order to decrypt the ciphertext, if an AES key and cipher were used. The IV isn’t a secret and can be stored or sent with the encrypted bytes. If an RSA key and cipher were used, then the IV will be null.

Android Cipher Specification

The Android Cipher class is part of the Java Cryptography extension, and is in the javax.crypto package. The class uses an oblique-separated string for the cipher specification.

The specification is in three parts: algorithm, block mode, padding mode. In the above sample code, the specification will be one of the following.

For an RSA key, the cipher specification will be “RSA/ECB/OAEPPadding” meaning:

  • Algorithm is RSA.
  • Block mode is electronic codebook (ECB).
  • Padding mode is Optimal Asymmetric Encryption Padding (OAEP).

For an AES key, the cipher specification will be “AES/GCM/NoPADDING” meaning:

  • Algorithm is AES.
  • Block mode is Galois/Counter Mode (GCM).
  • Padding mode is none.

These specifications mightn’t be suitable for your application IANAC but the sample code works and can be adapted to your needs. If you want to try different combinations of modes, it’s a good idea to check what combination Android supports first. A method for doing that will be covered later in this series.

Next Steps

The text has now been encrypted. It could be decrypted as a sentinel test, see below.

Encrypt a short text with a stored RSA key on iOS

Swift code to encrypt a short text with an RSA key in the iOS keychain could look like this:

The above snippet is based on the code from two locations:
github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L199
github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L653
The code is compatible with version 10.0+ of iOS.

Line notes:

  • Line 3:
    The alias could be the alias with which a key was generated earlier, using the RSA key generation sample from earlier in the series.
  • Lines 5 and 13:
    The attributes declaration results in a Swift Dictionary object. This is toll-free bridged to CFDictionary at the point of usage.
  • Line 6:
    The value kSecClassKey specifies keychain items that are stored as keys, which RSA key pairs are.
  • Line 8:
    The value kSecMatchItemAll makes the query, on line 13, return an array. The default is to return the first matching keychain item. In general, it seems more flexible to retrieve an array. For example, the number of matching items could be checked, although it isn’t in the code here.
  • Line 9:
    Setting kSecReturnRef:true makes the returned array have items that are, in this case, instances of SecKey. A different kSecClass attribute value would result in an array of different items.
  • Line 13:
    This is the retrieval of the stored key from the keychain. The stored key will be the private key.
  • Lines 16, 22, and 32:
    The StoredKeyError class is a custom exception subclass with convenience initialisers. The code is here: github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L735. You have to make your own custom exceptions in Swift. See under Some Assembly Required in the previous post.
  • Line 21:
    This is the retrieval of the public key from the private key.
  • Line 29:
    The SecKeyIsAlgorithmSupported function is used as a filter to select a cipher specification from a list. This is for future flexibility. Note that the shorter dot notation can be used here for the operation parameter, .encrypt in this case. This is because SecKeyOperationType is an enum class.
  • Line 36:
    This is the actual encryption.
  • Line 37:
    Use of UTF-8 is an assumption.
  • Line 42:
    The encryptedBytes variable contains the encrypted ciphertext.

The text has now been encrypted. It could be decrypted as a sentinel test, see below.

Encrypt a short text with a stored AES key on iOS

Swift code to encrypt a short text with an AES key in the iOS keychain could look like this:

The above snippet is based on the code from two locations:
github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L199
github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L628
The code is compatible with version 10.0+ of iOS.

Line notes:

  • Line 3:
    The alias could be the alias with which a key was generated earlier, using the AES key generation from earlier in the series.
  • Lines 5 and 13:
    The attributes declaration results in a Swift Dictionary object. This is toll-free bridged to CFDictionary at the point of usage.
  • Line 6:
    The value kSecClassGenericPassword specifies keychain items that are stored as generic passwords, which a symmetric key will have been.
  • Line 8:
    The value kSecMatchItemAll makes the query, on line 13, return an array. The default is to return the first matching keychain item. In general, it seems more flexible to retrieve an array. For example, the number of matching items could be checked, although it isn’t in the code here.
  • Line 9:
    Setting kSecReturnData:true makes the returned array have items that are, in this case, instances of the CFData class. A different kSecClass attribute value would result in an array of different items. A CFData instance can be toll-free bridged to a Swift Data instance.
  • Line 13:
    This is the retrieval from the keychain of the stored key, as raw data at this point.
  • Lines 16 and 27:
    The StoredKeyError class is a custom exception subclass with convenience initialisers. The code is here: github.com/vmware/captive-web-view/…/CaptiveCrypto/StoredKey.swift#L735. You have to make your own custom exceptions in Swift. See under Some Assembly Required in the previous post.
  • Line 20:
    The retrieved data is used to construct a CryptoKit SymmetricKey instance.
  • Line 23:
    This is the actual encryption, which CryptoKit refers to as sealing. The block mode, GCM, is explicit but also the only option in CryptoKit for AES. An initialisation vector (IV) isn’t given, so the system should generate one from a suitable secure random number generator (RNG).
  • Line 24: Use of UTF-8 is an assumption.
  • Line 25:
    The combined property represents an encapsulation of the ciphertext and IV, converted to raw data.
  • Line 31:
    The data variable contains everything necessary for decryption except the key.

The text has now been encrypted. It could be decrypted as a sentinel test, see below.

Comparison of short text encryption programming interfaces

The above Android and iOS code snippets for text encryption are quite different to each other because the native programming interfaces that they utilize are quite different.

Android Cryptography Programming Interface

Some of the comments in the earlier post about the Android programming interface for RSA key pair generation apply equally to text encryption. Here they are, for convenience.

Java Security System
The Android cryptography programming interface is based on the Java Security system. The Java Security system has a broad scope.

A smart card that can respond to challenges can be coded in the Java Security system. So can a derived credentials system. So can a hardware security module in an Android smartphone. So can a certification authority service. The Java Security programming interface has to be quite extensive in order to model all the applications that it supports.

Two significant pieces are the concepts of the key store and the security provider. The Android KeyStore counts as both a store and a provider.

Pitfalls for Android
Although the Android RSA key generation code looks quite simple and robust, many pitfalls had to be fallen into, and climbed out of, in the process of writing it. For example:

You have to know that “AndroidKeyStore” is the magic value that selects the on-device hardware-backed storage of an Android device. Specifying the store explicitly is optional. If you don’t specify it, the Java Security system will select a store based on the key specification. It won’t necessarily select the Android KeyStore because an Android device can have several stores, including stores that only exist in-memory at run time.

There’s another detail you have to know in order to use the text encryption programming interface for RSA cryptography. It’s that you access the public key via the certificate, which can be retrieved from the same store with the same alias. That detail and the method calls to use are documented on the Android developer website. However, they’re in the documentation of a class that doesn’t feature at encryption time, KeyGenParameterSpec. (There’s a lot of useful material on that page, by the way.)

Except for cipher specification, the Android code is object oriented, and works well with Kotlin scope functions and other modern concepts.

iOS Keychain and CryptoKit Programming Interfaces

The iOS keychain and CryptoKit programming interfaces are used for text encryption. Those interfaces are also used for RSA and AES key generation, which were the topics of previous posts in this series. Some of the comments from the previous posts apply equally to the topic of this post. Here’s a summary, for convenience.

The iOS keychain has different capabilities for AES and RSA keys.

RSA keys are generated in the keychain and stored there natively, as keys.
AES keys are generated outside the keychain, then encoded and stored as
generic passwords.

CryptoKit
CryptoKit has a proper modern Swift interface, based on classes and in which, for example, the shorter dot notation can be used for enumerated constants.

As a stand-in for namespaces, Cryptokit has enum classes with associated data.

Some Assembly Required
You have to extend CrpytoKit yourself if you want to store its keys in the iOS keychain. Apple provide some sample code for that, in a playground that can be downloaded from their developer site.

You have to create your own custom exception subclass in Swift, if you want a throw-able that has a message that can be retrieved by a catcher.

iOS Keychain Programming Interface
The RSA key generation programming interface of iOS isn’t modern. Old-time Objective-C coders will be familiar with the kSec constants and the rest of the SecKey family. The programming interface doesn’t seem to have been updated to make use of new Swift syntax.

The shorter dot notation for Swift enum constants can’t be used in a SecKey attributes dictionary, for example.

The shorter notation can be used elsewhere, for example when referring to SecKeyOperationType and SecKeyAlgorithm constants.

Help For Coders

The iOS cryptography interface prevents some coder mistakes that Android coders can still make.

For example, if you code with CryptoKit you don’t need to know that the IV must be retained after AES encryption. Retention is abstract in the CryptoKit sealing process. Not so for Android, where you must retain the IV by hand, and decide how to do that and how to make it available for decryption.

For another example, ECB block mode is deprecated for AES cryptography and can’t be selected in iOS CryptoKit. It still can be in the Android interface.

A cipher specification in iOS is either an enumerated constant, for RSA, or a nested struct, for AES. Either of those can be checked when you’re typing your program into Xcode, and auto-completion can be offered.

A cipher specification for Android is an oblique-separated string. Yes, a string. Android doesn’t provide any helper functions to create the string, not even constants for the parts between the obliques. This means a specifier can’t be checked when you’re typing your program, nor at build time. Your code could get all the way to run time with an end user before failing. (Why strings? I thought it might be a JavaCard limitation but, from what I’ve seen of the specification, JavaCard is all about the bytes and doesn’t have strings.)

Conclusion

Encryption with symmetric and asymmetric keys on Android has quite similar code. That isn’t the case for iOS. Between SecKey and CryptoKit, aspects like stored key retrieval and cipher algorithm specification are completely different, for example.

Cipher specification for Android is prone to error because strings are used with no helpers.

In conclusion, the Java Security and Cryptography systems win for Android with consistent shape of code. However, CryptoKit wins for iOS with mistake prevention and idiom support.

Some Cryptographic Terms

You can make better use of the interfaces on either platform if you know a few cryptographic terms. The following definitions are a start but don’t claim to be rigorous IANAC.

(Some terms and definitions here are the same as in the previous posts.)

Symmetric and Asymmetric Encryption

Symmetric encryption utilizes the same key to encrypt and decrypt data. The key can be referred to as a symmetric key.

Asymmetric encryption utilizes two dependent keys, sometimes referred to as a key pair. The two keys in a key pair can be referred to as the private key and the public key. If data is encrypted by the private key it can be decrypted by the public key, for example.

Cipher

A cipher is a process for a cryptographic operation, such as encrypting data.

The following could be included in a cipher specification programming interface.

  • Algorithm, such as RSA or AES.
  • Block mode, such as Galois/Counter Mode (GCM) or Cipher Block Chaining (CBC).
  • Padding mode, such as Optimal Asymmetric Encryption Padding (OAEP).

The following could be included in the rest of a cipher programming interface.

  • Set the key to use for the operation.
  • Set parameters, such as the initialisation vector (IV).
  • Prepare the input data, such as encoding text to bytes or vice versa.
  • Do the actual processing, such as encrypting or decrypting some data.
  • Convert the output data, such as encoding text to bytes or vice versa.

There are a number of standard ciphers. So far, this series covers only: usage of AES and RSA algorithms, with one specific block mode and padding mode each. Subsequent posts in the series could cover more algorithms, such as Elliptic Curve (EC), and a range of block and padding modes.

Key Store

Any persistent location for cryptographic keys on a mobile device can be referred to as a key store. Persistence here means through termination of the app or power cycling of the mobile device, for example.

Stored keys each have a key identification. Depending on the capabilities of the store, the identification will be used:

  • To retrieve the key from the store.
  • To request cryptographic processing involving the key and taking place inside the store.

Key stores that support processing requests typically also support generation of new keys inside the store. A key generated inside a store that supports processing requests never has to leave the store, which is a desirable security characteristic.

The text encryption snippets cover the following key stores.

Android Keystore:

  • Supports processing requests.
  • Identifies each key by a simple name, referred to as its alias.

iOS keychain:

  • Doesn’t support processing requests.
  • Identifies each key by a set of attributes.

A future post could also cover the iOS Secure Enclave Processor (SEP), which does support processing requests.

Sample Code Locations

Many individually useful code samples can be found here on Medium, on Stack Overflow, and in GitHub Gists.

For the sample code that appears above, see the following.

For Android:

For iOS:

Use the sample code with care. It is working code but might require modification in order to represent the best cryptography for your application.

The above applications each use an embedded web view for their user interface (UI). The actions that the user can take in the UI correspond fairly closely to low-level cryptographic tasks. Commands are sent from the web view through a bridging layer to the native Kotlin or Swift code. The native code then utilizes the device’s cryptographic programming interface.

For official samples, see the following.

Sentinel Test

The encryption of a short text could form the first half of a test. The second half of the test would be to decrypt the value returned by the first half. The test would pass if the decrypted value was the same as the original text. A text used in this way is sometimes referred to as a sentinel.

The purpose of a sentinel test here isn’t to verify the Android and iOS cryptographic modules. The purpose is to check that our code makes correct use of the modules. For example, if a key could be generated, but then couldn’t be used to encrypt a text, that would be a failed test and show that the generation interface hadn’t been used correctly. In the same way, if the encrypted value couldn’t be decrypted, or the decrypted value didn’t match the input sentinel, that would also represent a failed test.

If there is a round trip to key storage in sentinel encryption, and again in decryption, this tests for correct key retrieval. If a new key can be accessed in memory before it is stored, then encrypting with the in-memory key and decrypting with the stored key tests for correct key storage.

Any sentinel test involves decryption processing. That’ll be covered in the next post in this series.

Next: Short Text Decryption

The next post in this series will cover decryption of a short text with a stored key, either RSA or AES.

Thank you for reading and happy coding.

--

--

Jim Hawkins
VMware 360

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