Swift: Storing key pairs in the keyring

In my last blog post I described how to generate public/private key pairs in Swift and use them to encrypt and decrypt text. But that’s not entirely useful in its own unless you have a place to store the pairs, such as the iOS keychain.

Eagle-eyed readers may have also noticed that I skipped one important detail when discussing generating keys:

var publicKeyPtr, privateKeyPtr: Unmanaged<SecKey>?
result = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)

I never specified the parameters dictionary anywhere.

Unfortunately this involves wading into the murkier parts of the compatibility between Swift, Foundation and Core Foundation: the compatibility between the different array and dictionary types, and the different type strictness required for protocol conformance.

Consider the basic dictionary that we would pass for a 2048-bit RSA key:

// ⚠︎ Type 'CFStringRef' does not conform to protocol 'Hashable'.
let parameters = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeySizeInBits: 2048
]

In Objective-C the CFStringRef type is toll-free bridged to NSString, which confirms to NSCopying so it can be used as the key of a dictionary. But in Swift, the dictionary requires keys to confirm to a Hashable protocol instead, which is completely informal in Objective-C, and thus not declared for anything.

At least in this case we know we can cast them to Swift’s own String type which does conform to that protocol. This feels slightly wrong in that we’re relying on these constants never changing type, but it at least solves the problem:

// ⚠︎ Could not find an overload for 'init' that accepts the supplied arguments.
let parameters = [
    String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
    String(kSecAttrKeySizeInBits): 2048
]

But we’ve now run into the next fundamental problem. Objective-C and Core Foundation APIs frequently deliberately make use of the loose value type strictness of NSDictionary and CFDictionary, the type of the value is inferred by the key and are explicitly mixed.

In Swift all values and all keys must be of the same type.

It turns out there are three ways to work around this:

  1. Explicitly cast the dictionary to use AnyObject as the value type; this means you can also remove the casts to String on each of the keys, since String will also be specified up front.
  2. import Foundation and Swift will use an NSDictionary constructor instead of its built-in Dictionary<T> type.
  3. For the kSecAttrKeySizeInBits key only, use the string "2048" for the value.

The third is the cleanest temporary hack, but unfortunately only works for this one specific key. Other keys we need very shortly need other types, so we’ll need to work around that again.

Since you need Foundation anyway for other things, the second works just fine, but the first also works too and is probably slightly more Swift-y. Either way, Swift will take care of changing 2048 to something that can bridge to NSNumber later.

import Foundation

let parameters = [
    String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
    String(kSecAttrKeySizeInBits): 2048
]

or

let parameters: [String: AnyObject] = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeySizeInBits: 2048
]

This works to generate a temporary key pair for the purposes of encrypting and decrypting. To store the keys in the keychain, we make them permanent by supplying additional values for the kSecPublicKeyAttrs and kSecPrivateKeyAttrs keys.

The values for these keys are dictionaries in their own right, which is a good reason for choosing the first or second solutions above. In these dictionaries we supply a Bool and a String value for keys, so we can follow the same pattern as we did before:

let publicKeyParameters: [String: AnyObject] = [
    kSecAttrIsPermanent: true,
    kSecAttrApplicationTag: "com.example.Test"
]
let privateKeyParameters: [String: AnyObject] = [
    kSecAttrIsPermanent: true,
    kSecAttrApplicationTag: "com.example.TestPrivate"
]

For each of the two keys we specify that we want it to be permanent, and we provide an application-specific tag so that we can find that key again. I use different tags for the public and private keys because that seems to be the most reliable way to find them again.

Now we just need to set these dictionaries as values for the keys, right?

Nope.

The kSecPublicKeyAttrs and kSecPrivateKeyAttrs values are not CFStringRef like the others, they are CFTypeRef. And that is bridged into Swift as Unmanaged<AnyObject>.

And that gives us our first problem, we don’t know the underlying reference pattern. Swift only gives us an option to consume an existing reference via takeRetainedValue or to create a new reference with takeUnretainedValue. Either way it’s going to release a reference later, we have to just hope that something somewhere has a reference to this particular constant so we don’t end up releasing it for everyone.

We still only get an AnyObject type out of this. That doesn’t conform to Hashable so we can’t use it as the key of a Swift dictionary, and it doesn’t confirm to NSCopying so we can’t use Swift’s bridged methods of NSDictionary to add it either.

There are two hacks I found.

The first one is that the NSDictionary(objects:forKeys:) constructor takes an array of AnyObject for the keys, whereas all the other NSMutableDictionary methods take NSCopying for the key. This means you can actually use it to construct a dictionary with the right keys:

let parameters = NSDictionary(
    objects: [
        kSecAttrKeyTypeRSA,
        2048,
        publicKeyParameters,
        privateKeyParameters ],
    forKeys: [
        kSecAttrKeyType,
        kSecAttrKeySizeInBits,
        kSecPublicKeyAttrs.takeUnretainedValue(),
        kSecPrivateKeyAttrs.takeUnretainedValue() ])

This feels wrong though, as I discussed yesterday it’s mixing and confusing Swift and Foundation patterns.

The second is that the actual type contained in kSecPublicKeyAttrs all along is in fact a CFStringRef, so we can use the as operator to cast it:

let parameters: [String: AnyObject] = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeySizeInBits: 2048,
    kSecPublicKeyAttrs.takeUnretainedValue() as String: publicKeyParameters,
    kSecPrivateKeyAttrs.takeUnretainedValue() as String: privateKeyParameters
]

Putting all this together is enough to generate a public/private key pair and have it permanently stored in the keychain with a tag we can use to find it.

 Finding the key in the keychain again

We have to find the public and private key individually, so it’s worth writing a function to do this. We can use the application tag we set in the different key parameters to distinguish them:

func findKey(tag: String) -> SecKey? {

To find a key in the keychain we use the SecItemCopyMatching method, which again takes a dictionary into which we have to mash different types:

    let query: [String: AnyObject] = [
        kSecClass: kSecClassKey,
        kSecAttrKeyType: kSecAttrKeyTypeRSA,
        kSecAttrApplicationTag: tag,
        kSecReturnRef: true
    ]

This query is for an RSA key with the application tag matching that passed into the function. The final query key indicates that we would like a reference to a SecKey object returned; the documentation isn’t specific on this, but we can assume it will be retained.

Issuing this query returns that reference inside the optional unmanaged object type we’ve seen before:

    var keyPtr: Unmanaged<AnyObject>?
    let result = SecItemCopyMatching(query, &keyPtr)

    switch result {
    case noErr:
        let key = keyPtr!.takeRetainedValue() as SecKey
        return key
    case errSecItemNotFound:
        return nil
    default:
        println("Error occurred: \(result)`")
        return nil
    }
}
 
102
Kudos
 
102
Kudos

Now read this

Custom iOS segues, transitions, and animations the right way

Let’s take a simple scenario: you have two UIViewControllers within a UINavigationController and you want the transition between them, in both directions, to be a fade-in effect rather than the usual slide-in/out effect. There’s a lot of... Continue →