Let me start this “Sharing public keys between iOS and the rest of the world” post by saying that I love developing for iOS. Cocoa has a great set of libraries, and Swift, even though not perfect, is a lovely language. Besides, I really like working with Xcode (when SourceKitService doesn’t crash), and I have to admit that I’ve never had that much fun while developing than for this particular platform, but…

Apple deprecated and then discontinued OpenSSL for its platforms and OS some years ago, leaving us with access to cryptography only through the Security framework. Unless, of course, you want to do some nasty hacks with custom built OpenSSL libraries. Unfortunately, the Security framework is terrible, developer-unfriendly and unintuitive to use.  That’s kind of surprising, giving the fact that iOS is (in)famous for its strong cryptographic nature. Indeed, that’s one of the main selling points that make it stand out from other platforms such as Android. There are several reasons for this:

  • The APIs are obscure and badly documented (I mean, the parts that are documented at all). There is little to no indication of how to use them in a coherent, comprehensive manner. Apple’s Security, Trust and Certificate guide is obsolete and not very clear for anything outside of the three typical uses of the framework, and the Crypto Exercise was last updated more than 6 years ago. In fact, it crashes when executed in current Xcode versions. Of course, no swift code on sight.
  • The key representation is arbitrary and inconsistent. EC keys are stored in its raw binary form, without any kind of ASN.1 treatment. Conversely, RSA keys are stored in a basic ASN.1 sequence wrapping layer.
  • The management API for the keys works well if you just want to use them inside iOS. However, it’s a real nuisance if you plan on sending them to an external server (like a backend). Its raw output is not supported by OpenSSL, and PHP won’t read it either, so basically you are out of luck unless you transform it to a standard format like PKCS#1.
  • The APIs won’t accept public keys imported from the outside world. I’m talking about simple PEM formatted RSA keys. To import public keys, you need to have a valid certificate and go through a bizarre method in order to get the reference to the public key contained inside. If you don’t have a certificate, only a public key, then it’s impossible importing that key inside iOS.

Recently, I embarked on a project to build a PasswordLess secure authentication protocol and framework. I plan on releasing it soon. One of the main requisites was being able to verify signatures and encrypt-decrypt in an asymmetric cryptography environment.

When I started to work with the iOS frontend side, I discovered that I couldn’t use the public keys from iOS outside, neither could I import my backend keys and make them work inside iOS.

After writing in the Apple developer forums, registering a bug report (that to this day remains unanswered) and contacting  the Apple engineer in charge of cryptography about the subject (who was not helpful at all), I decided to delve inside the cryptographic mess of iOS and try to understand how it deals with keys and formats.

These are my findings. However, for those of you willing to get the code, you can download it right now from my Github repository:

Importing public keys to iOS

Don’t try to import public keys contained in a DER or PEM file inside iOS. You won’t succeed.

After a lot of research, I came to the solution in a really old post in the developer forums, written by the Apple engineer I had contacted regarding the key export issue:

Cryptography In IOS (II): Sharing public keys between iOS and the rest of the world

That answer put me on the right track. I would have never thought that I needed the full certificate to perform basic cryptographic verification operations instead of just a public key (like the rest of cryptographic systems out there).

Generating The Certificate

First, we need to generate a valid certificate (probably in the backend). We can do it with some simple OpenSSL commands:

  • Generate the private key and the certificate
openssl req -newkey rsa:2048 -nodes -keyout private_key.key -x509 -days 3650 -out certificate.pem
  • Convert the certificate from PEM to DER
openssl x509 -outform der -in certificate.pem -out certificate.der
  • Check the certificate and private key
openssl x509 -text -noout -inform DER -in certificate.der
openssl rsa -check -in private_key.key

Importing The Certificate

Once we have our certificate ready, we can embed it in our iOS project and use it.  The process is somewhat bizarre, is composed of the following steps:

func importPublicKeyReferenceFromDERCertificate(certData: NSData) -> SecKeyRef? {
   // 1
   guard let certRef = SecCertificateCreateWithData(nil, certData) else { return nil }
   // 2
   var secTrust: SecTrustRef?
   let secTrustStatus = SecTrustCreateWithCertificates(certRef, nil, &secTrust) 
   if secTrustStatus != errSecSuccess { return nil }
   // 3
   var resultType: SecTrustResultType = UInt32(0) // ignore results.
   let evaluateStatus = SecTrustEvaluate(secTrust!, &resultType)
   if evaluateStatus != errSecSuccess { return nil }
   // 4
   let publicKeyRef = SecTrustCopyPublicKey(secTrust!)
   return publicKeyRef
}
  1. First, let’s suppose we have read our certificate in DER format from the file as a raw NSData sequence. Then, we need use SecCertificateCreateWithData to create a SecCertificate reference to the certificate.
  2. Next, we need to create a SecTrust reference from that certificate by calling SecTrustCreateWithCertificates.
  3. Then, we have to evaluate the SecTrustRef item we just created from the certificate reference. It’s unclear to me why this step is needed, but the whole process doesn’t seem to work unless the SecTrust item is properly evaluated. The results of the evaluation are retrieved in resultType. However, you can safely ignore this result.
  4. Finally, we can extract the public key reference from the SecTrust item by calling SecTrustCopyPublicKey.

If everything went as planned, we will end up with a SecKeyRef for the public key contained in the certificate. This key won’t be added to the KeyChain, so we must directly use it to any of the operations we need it for, including SecKeyEncrypt, SecKeyRawVerify, etc…

Using the Crypto Export-Import Manager

In order to import the key from the certificate using CryptoImportExportManager, you just need to call its importExportManager.importPublicKeyReferenceFromDERCertificate method:

let importExportManager = CryptoExportImportManager()
if let publicKeyRef = importExportManager.importPublicKeyReferenceFromDERCertificate(certData) {
   // use publicKeyRef to sign, decrypt, etc..
} else { ... handle error ... }

You can, of course, extract your public keys from the certificate using OpenSSL, to have a more manageable public-private key files.

Exporting iOS-generated public keys to the outside world.

Getting iOS keys to work outside of iOS has really been a nightmare for me. Actually, it took me several weeks of hard work to get it right. I hope that this article may help you, shall you find yourself in this same situation.

Some theory about key representation and formatting

As I said before, the keys generated by SecKeyGeneratePair and retrieved by means of SecItemCopyMatching are:

  • Not exportable by default to other platforms, and
  • Not consistent between different key types inside iOS

In particular, I’m not referring here to its representation in binary (DER) or base64 form (PEM). I’m talking about its DER binary structure.

About ASN.1

Generally speaking, all keys should contain a set of parameters or metadata with important information. This information includes what kind of key it is, what are its parameters, key length, type, and other relevant information that will allow a tool to properly identify, read and handle that key.

This metadata is expressed in a notation called ASN.1, concretely in the DER (Distinguished Encoding Rules) X.690 notation. This notation works by specifying a list of consecutive objects, from INTEGERS to full SEQUENCES.

Each object is identified by a starting mark and generally a length. The length in ASN.1 is codified as a single byte for values up to 128 (because of sign conversion), and for values greater than 128, it’s codified with an initial mark that’s 0x80+the number of bytes used to codify the length, followed by those bytes. For example, to codify a length of 0x1A0, you will need two bytes (0x01 + 0x1A), so the length will get encoded as 0x82 0x01 0x1A).

RSA keys

RSA keys have a module and an exponent. The modulus is shared between a pair of private-public keys, and the exponent is what differentiates them. As a result, and depending on how you wrap the modulus and exponent of a key in ASN.1 notation objects, you have different formats like PKCS#1, PKCS#7, PKCS#8, etc…

Unfortunately, Apple cryptographic tools return a basic representation of the key, just a sequence (30+length), followed by the modulus and exponent as integers (02+length+bytes) one after the other.

 0:d=0 hl=4 l= 266 cons: SEQUENCE 
 4:d=1 hl=4 l= 257 prim: INTEGER : ... (large integer number here in hex) ...
 265:d=1 hl=2 l= 3 prim: INTEGER :010001

This is a graphical representation of the data structure:

Cryptography In IOS (II): Sharing public keys between iOS and the rest of the world

If we want to make it work with OpenSSL, we need to wrap it inside a full PKCS#1 structure. From Wikipedia:

In cryptography, PKCS #1 is the first of a family of standards called Public-Key Cryptography Standards (PKCS), published by RSA Laboratories. It provides the basic definitions of and recommendations for implementing the RSA algorithm for public-key cryptography. It defines the mathematical properties of public and private keys, primitive operations for encryption and signatures, secure cryptographic schemes, and related ASN.1 syntax representations.

Building a PKCS#1 Structure

In order to wrap our key in a PKCS#1 structure, we need to add a “rsaEncryption” ASN.1 object. Its OID (object identifier) is 1.2.840.113549.1.1.1. Then, we need to build a structure around it to turn it into a valid PKCS#1 object:

 0:d=0 hl=4 l= 290 cons: SEQUENCE 
 4:d=1 hl=2 l= 13 cons: SEQUENCE 
 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
 17:d=2 hl=2 l= 0 prim: NULL 
 19:d=1 hl=4 l= 271 prim: BIT STRING: (bit string of the key here)

This is the visual representation of that data structure. The bit string is actually the data structure retrieved from SecItemCopyMatching previously shown.

Cryptography In IOS (II): Sharing public keys between iOS and the rest of the world

You specify an RSA key in the Security framework with the attribute kSecAttrKeyTypeRSA.

EC keys

ECC stands for “Elliptic Curve Cryptography“. From the Wikipedia:

Elliptic curve cryptography (ECC) is an approach to public-key cryptography based on the algebraic structure of elliptic curves over finite fields. One of the main benefits in comparison with non-ECC cryptography (with plain Galois fields as a basis) is the same level of security provided by keys of smaller size.

Indeed, EC keys are way smaller than its RSA counterparts for the same level of security. Probably, that’s one of the main reasons why Apple only allows these keys inside the secure enclave. You specify an EC key in the Security framework with the attribute kSecAttrKeyTypeEC.

There are many different types of EC keys, whose name depends on the mathematical curve that describes the algorithm behind them. Currently, these are the ones Apple supports:

  • kSecECCurveSecp256r1, corresponding to the ansiX9p256r1 curve. This is the only key allowed to be used for private keys contained inside the secure enclave.
  • kSecECCurveSecp384r1, corresponding to the secp384r1 curve.
  • kSecECCurveSecp521r1, corresponding to the secp521r1 curve.

Note: contrary to what’s specified in the documentation for kSecECCurveSecp256r1, the curve that needs to be specified in order for the generated key to be valid is not a prime256v1 (aka secp256r1), but a ansiX9p256r1 (OID 1.2.840.10045.3.1.7).

Building the EC Structure

Apple cryptographic tools again give us just a mere raw representation of the data. This time, as we don’t have any sequence of modulus+exponent, just the raw bits are exposed. Thus, we need to wrap it in the proper ASN.1 object structure. The length of the structure and the key itself depends on the curve chosen.

 0:d=0 hl=2 l= 89 cons: SEQUENCE 
 2:d=1 hl=2 l= 19 cons: SEQUENCE 
 4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
 13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1
 23:d=1 hl=2 l= 66 prim: BIT STRING

Here is a representation of the final wrapping of the data. In this case, the Bitstring is just the raw bits of the public key from SecKeyGeneratePair.

Cryptography In IOS (II): Sharing public keys between iOS and the rest of the world

An export mechanism that just works

CryptoImportExportManager makes it really simple to export the keys from iOS to the right format, ready to use in OpenSSL, PHP, Ruby, etc…

You just need to retrieve the public key bytes in an NSData by using SecItemCopyMatching, and then create an instance of CryptoImportExportManager and call its exportPublicKeyToDER(data, keyType, keySize) or exportPublicKeyToPEM(data, keyType, keySize) methods, depending on the output format you want. keyType can be either kSecAttrKeyTypeRSA or kSecAttrKeyTypeEC. The two key types allowed by Apple. Valid key sizes are 1024, 2048 and 4096 for RSA keys, and 256, 384 and 521 for EC keys (corresponding to the curves ansiX9p256r1, secpr384r1 and secp521r1). Example:

var pubKeyData = ... // data for the public key retrieved by SecItemCopyMatching
let keyType = kSecAttrKeyTypeEC
let keySize = 256
var exportImportManager = CryptoExportImportManager()
if let exportablePEMKey = exportImportManager.exportPublicKeyToPEM(pubKeyData, keyType: keyType, keySize: keySize) {
   // send pem string to server.
} else { ... }

Get the code

Cryptography In IOS (II): Sharing public keys between iOS and the rest of the world
CryptoImportExportManager is under the MIT license. You can get it here. It comes with a sample project to allow you to test both the importing and exporting functionality. If you find something missing or want to add anything, please don’t hesitate to leave a comment.

The project comes also with a sample PHP file that would read a key obtained from the CryptoImportExportManager. You just need to copy the PEM formatted output from the export controller, save it  in a file, and then call readkey.php:

php readkey.php public_key_file.pem

Where To Go Next

In conclusion, in this last episode of “Cryptography in iOS”, you learned how to share public keys between iOS and the rest of the world. This is essential if you want to integrate an iOS app in an Asymmetric Cryptography scenario. I also shared with you a manager to import and export public keys from your iOS application to your backend easily.

By now, you should have a good understanding of how Cryptography works in iOS. However, you might want to have a look at the previous post in the tutorial series: “Asymmetric Cryptography in iOS“.