Starting with iOS 10, it is actually possible to import PEM private keys w/o converting them to PKCS#12 (which is a very universal container format for everything related to cryptography) and thus also w/o using OpenSSL on command line or statically linking apps with it. On macOS it's even possible since 10.7 using a different function than the ones mentioned here (but so far it doesn't exist for iOS). Exactly the way described below will also work on macOS 10.12 and later, though.
To import a certificate, it's enough to just strip the
-----BEGIN CERTIFICATE-----
and
-----END CERTIFICATE-----
lines, then run base64 decoding over the data left, the result is a certificate in standard DER format, which can just be fed to SecCertificateCreateWithData()
to get a SecCertificateRef
. This has always been working, also prior to iOS 10.
To import a private key, a little bit of extra work may be required. If the private key is wrapped with
-----BEGIN RSA PRIVATE KEY-----
then it is very easy. Again, the first and last line needs to be stripped, the remaining data needs to be base64 decoded and the result is a RSA key in PKCS#1 format. This format can only hold RSA keys and it is directly readable, just feed the decoded data into SecKeyCreateWithData()
to obtain a SecKeyRef
. The attributes
dictionary just need the following key/value pairs:
kSecAttrKeyType
: kSecAttrKeyTypeRSA
kSecAttrKeyClass
: kSecAttrKeyClassPrivate
kSecAttrKeySizeInBits
: CFNumberRef
with then number of bits in the key (e.g. 1024, 2048, etc.) If not known, this information can actually be read from the raw key data, which is ASN.1 data (it's a bit beyond the scope of this answer, but I will provide some helpful links below about how to parse that format). This value is maybe optional! In my tests it was actually not necessary to set this value; if absent, the API determined the value on its own and it was always set correctly later on.
In case the private key is wrapped by -----BEGIN PRIVATE KEY-----
, then the base64 encoded data is not in PKCS#1 format but in PKCS#8 format, however, this is a just a more generic container that can also hold non-RSA keys but for RSA keys the inner data of that container is equal to PKCS#1, so one could say for RSA keys PKCS#8 is PKCS#1 with an extra header and all you need to do is stripping that extra header. Just strip the first 26 bytes of the base64 decoded data and you have PKCS#1 again. Yes, it's really that simple.
To learn more about PKCS#x formats in PEM encodings, have a look at this site. To learn more about ASN.1 format, here's a good site for that. And if you need a simple, yet powerful and interactive online ASN.1 parser to play around with different formats, one that can directly read PEM data, as well as ASN.1 in base64 and hexdump, try this site.
Very important: When adding a private key to keychain, that you created as above, please be aware that such a private key doesn't contain a public key hash, yet a public key hash is important for they keychain API to form an identity (SecIdentityRef
), as using the public key hash is how the API finds the correct private key that belongs to an imported certificate (a SecIdentityRef
is just a SecKeyRef
of a private key and a SecCertificateRef
of a cert forming a combined object and it's the public key hash, that binds them together). So when you plan to add the private key to keychain, be sure to set a public key hash manually, otherwise you won't ever be able to get an identity for it and without that you cannot use keychain API for tasks like signing or decrypting data. The public key hash must be stored in an attribute named kSecAttrApplicationLabel
(stupid name, I know, but it's really not a label and nothing the user can ever see, check out the documentation). E.g.:
OSStatus error = SecItemAdd(
(__bridge CFDictionaryRef)@{
(__bridge NSString *)kSecClass:
(__bridge NSString *)kSecClassKey,
(__bridge NSString *)kSecAttrApplicationLabel:
hashOfPublicKey, // hashOfPublicKey is NSData *
#if TARGET_OS_IPHONE
(__bridge NSString *)kSecValueRef:
(__bridge id)privateKeyToAdd, // privateKeyToAdd is SecKeyRef
#else
(__bridge NSString *)kSecUseItemList:
@[(__bridge id)privateKeyToAdd], // privateKeyToAdd is SecKeyRef
// @[ ... ] wraps it into a NSArray object,
// as kSecUseItemList expects an array of items
#endif
},
&outReference // Can also be NULL,
// otherwise reference to added keychain entry
// that must be released with CFRelease()
);