Using an RSA public key generated by OpenSSL in iOS

Apple have gone to pains to make cryptography in iOS (and MacOS in general) secure, building a layer between applications and the low-level stuff, like OpenSSL. The principle is to keep these functions in separate address space thus significantly reducing the surface area available for malicious code to find a weakness. In iOS this separation is enforced and, significantly, the documentation is sparse and terse. Public key use without also using certificates is mentioned but only in the context of using keys generated on the device. Posts on the Apple Developer forums indicate that using certificates is suggested because using public key pairs is “involved“. It turns out that the reason it’s involved is because of some odd implementation details and the aforementioned lack of documentation or useful examples.

I was developing a mechanism to verify some data that was generated outside the device with a public key. Using a simple key pair generated by OpenSSL at a command line it was very simple to create scripts in Perl and PHP to produce (and sign) and then decode (and validate) some data using this key pair. The functions to add a public or a private key to the keychain are there in iOS but they don’t work as expected.

Public key: It’s all in the format

There are many ways to transmit a key for use by another system but there are also some defacto standards. For an RSA public or private key, which has a pretty straightforward internal structure, that would be defined by PKCS#1.

There are two common forms to transmitting and using an RSA public or private key. ASN.1 DER – which is a non-displayable (i.e., not ASCII) format, or the same thing but base64 encoded and referred to as PEM – which is 7-bit ASCII and thus displayable. The base64 format is usually wrapped with something like -----BEGIN PUBLIC KEY----- (which is what OpenSSL produces) or -----BEGIN RSA PUBLIC KEY-----.

You can have OpenSSL generate a key pair very easily and you select the output form right on the command line:

Sign here

So far so good. In PHP we can use the PEM encoded keys directly. For example, to acquire the signature of some data, one could do this:

The format being returned is not a standard, it’s something I made up, but it’s easily transmitted and base64 implementations are pretty universal.

How would we verify the integrity of this message? Here’s a crude example:

With this little nugget of code it becomes trivial to find out if someone has been tampering with the data. This is useful, for example, for software licenses. Assuming the private key is kept secure you have a degree of trust that anything signed by it is also secure.

So what’s the problem with iOS?

iOS provides a security framework but if you try to use it for keys you generated elsewhere then it doesn’t work and gives only very limited indications why it doesn’t work.

iOS does not provide documented direct access to the OpenSSL API. If it’s there it wouldn’t matter – being undocumented means that it would face App Store rejection if their static analysis showed use of the undocumented library.

You could statically link an OpenSSL library that you compiled and it would work but in that situation it’s your app providing the cryptography code and thus you will need to jump some hurdles (you may need a CCATS) to get export clearance for your app (although it’s not that simple in general – read the Export Compliance FAQ in iTunes Connect). In my case, I’m using a signed software license to prevent piracy – digital rights management – and thus does not need US Government clearance – but only if I include only enough code to perform that function. If I link against the whole of OpenSSL then what my app is doing will be less clearly defined.

So the key to a simple life is to work out why the provided functions don’t work.

What’s in a key?

It comes down to format.

The first clue is that if you use iOS to generate a key pair then the resulting form is not ASCII. So that rules out PEM and means that if you have a PEM formatted key, you’ll also need a base64 decoder – which iOS does not provide. There’s plenty of them around. I used NSData+Base64.m which if you Google for you will find many varied implementations and derivations of.

But that is not enough, even though an Apple employee confirms on the dev forums that iOS wants a PKCS#1 key and if you’re doing signature verification, that the signature is of the SHA1 hash of the data, as I would expect.

The next clue came when I noticed the keys produced by iOS don’t have the normal binary ASN.1 preamble to identify itself as a PKCS#1 key. This prompted some searching and I came across this post talking exactly about this issue. I have no idea why it did not come up in my earlier searching. In it Berin explains his similar discovery, but also that the payload still looks like a binary PKCS#1 key, just without the preamble. In his application he had to add the header so he could import the key elsewhere. What I wanted was the reverse.

And so I did the reverse. And it worked.

And then I came across this later post by Berin also doing what I wanted. Doh. Our implementations are not identical, but functionally similar.

Code or it didn’t happen

Assuming you have your OpenSSL generated RSA public key in an NSData object, this method will verify that it is in fact a PKCS#1 key and strip the header:

and the result can be fed into the iOS functions in the expected manner. For example, this code adds a key-chain reference to a public key to an array. If you use this, don’t forget to eventually CFRelease() each SecKeyRef you acquire, and don’t forget that tag needs to be unique for every key you add.

Lastly, to verify a message in the same way we did in PHP earlier, the code might look like this:

So, yes. It’s involved. But I think it’s worth it.

Also, for what it is worth, this does work in the Simulator, certainly at least with the current iOS SDK (4.2).