Export Public Key in PEM Format

Hi


Using the newer methods in the SDK for iOS 10, I can generate private and public keys, with the private key residing in the Keychain. The public key is exported.


Here's my code thus far:


    func createKeyPair(_ keyName: String, authenticationRequired: SecAccessControlCreateFlags? = nil, completion: (_ success: Bool, _ publicKeyData: Data?) -> Void)
    {
        guard !keyName.isEmpty else
        {
            NSLog("\tNo keyname provided.")
            return completion(false, nil)
        }

        var error: Unmanaged<CFError>?

        // Private key parameters
        var privateKeyParams: [String: Any] = [
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: keyName
        ]

        // If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
        if authenticationRequired != nil
        {
            guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
            {
                NSLog("\tError: %@", error!.takeRetainedValue().localizedDescription)
                completion(false, nil)
                return
            }

            privateKeyParams[kSecAttrAccessControl as String] = accessControl
        }

        /
        let parameters: [String: Any] = [
            kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
            kSecAttrKeySizeInBits as String: 2048,
            kSecPrivateKeyAttrs as String: privateKeyParams
        ]

        // Global parameters for our key generation
        guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
        {
            NSLog("\tError generating keypair. %@", "\(error!.takeRetainedValue().localizedDescription)")
            return completion(false, nil)
        }

        // Generate the keys.
        guard let publicKey = SecKeyCopyPublicKey(privateKey) else
        {
            NSLog("\tError obtaining public key.")
            return completion(false, nil)
        }

        // Get the public key.
        guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil) else
        {
            NSLog("\tError obtaining export of private key.")
            return completion(false, nil)
        }

        print("\nPrivate key: \(String(describing: exportPublicKey(privateKeyData as Data)))")


        // Extract the public key for export.
        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) else
        {
            NSLog("\tError obtaining export of public key.")
            return completion(false, nil)
        }

        completion(true, publicKeyData as Data)
    }

    public func exportPublicKey(_ rawPublicKeyBytes: Data, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
    {
        return rawPublicKeyBytes.base64EncodedString(options: base64EncodingOptions)
    }

   // Call the function like so.
   _ = createKeyPair(keyName)
   {
       (status, data) in
       if status
       {
            print("exporting public key: \(String(describing: exportPublicKey(data!)))")
       }
   }


If I've understood the documentation, SecKeyCopyExternalRepresentation says that the method returns data in the PCKS #1 format for an RSA key. From the various forums, I'm lead to beleive that simply base64 encoding to a string the output of SecKeyCopyExternalRepresentation is all that is required to export the public key in PEM format (without BEGIN RSA PUBLIC KEY and END RSA PUBLIC KEY)


When the public key is used to validate some signed data in a Java app, the public key fails to load with invalid key errors... Anyone provide some guidance on this?


Thanks

Accepted Reply

Simply base64 encoding the SecKey is not enough, it's missing from what I understand is the RSA OID header and the ASN.1 sequence.

Right. I’ve talked about this extensively in other threads (related to public keys, not private keys, but similar logic applies). For example:

In many cases you can convince the ‘foreign’ security toolkit to accept the unwrapped structure by using

BEGIN RSA PUBLIC KEY
markers in the PEM (as opposed to
BEGIN PUBLIC KEY
, which is used for the wrapped version).

Do you know of any functions in the iOS SDK that do this?

No. For simple RSA cases it’s relatively easy to do this wrapping by hand. However, my recommendation is that you look at doing this on the server side; it’s likely that the security toolkit there is more fully-featured, and thus can handle this job.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

I'm lead to beleive that simply base64 encoding to a string the output of SecKeyCopyExternalRepresentation is all that is required to export the public key in PEM format (without

BEGIN RSA PUBLIC KEY
and
END RSA PUBLIC KEY
)

Yep.

When the public key is used to validate some signed data in a Java app, the public key fails to load with invalid key errors...

Please post an example of a key you’ve generated.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo


Here's what got generated. Although taking the code snippet will print out the private\public keys.


Public Key

MIIBCgKCAQEAyZ4X0iE7X8J/hc9FcuEZXoc6Toul+Qbvrw/OK0lyKHg9what1F1QebrmP9DEeM1QGlGcP00HCPMWoifNHZ9g9Q5gpQRP7vbe34JkrphBa8kNgBKMHnyc8x4vg2wK01fbbJKJTajRoggGy0r0LEXEKS4gA3bvxPPlVxs9wT2Bd1dIIB0Dnnc9myCKeOtizUtaqEa9KbNtdtq4mNVxgdWSEDbxmjn6TIh5dtfjM2as+O1JEM+jDBvT3N11h/qUG/FenOVrrx3oCSpKn6Yv/1MVoj5utMgQrNpp2dakXgLX/1OSumeBA65D4/lC5xgH9Yfcgapw5JcxM6tSd62fy4eT7QIDAQAB


Private Key

MIIEowIBAAKCAQEAyZ4X0iE7X8J/hc9FcuEZXoc6Toul+Qbvrw/OK0lyKHg9what1F1QebrmP9DEeM1QGlGcP00HCPMWoifNHZ9g9Q5gpQRP7vbe34JkrphBa8kNgBKMHnyc8x4vg2wK01fbbJKJTajRoggGy0r0LEXEKS4gA3bvxPPlVxs9wT2Bd1dIIB0Dnnc9myCKeOtizUtaqEa9KbNtdtq4mNVxgdWSEDbxmjn6TIh5dtfjM2as+O1JEM+jDBvT3N11h/qUG/FenOVrrx3oCSpKn6Yv/1MVoj5utMgQrNpp2dakXgLX/1OSumeBA65D4/lC5xgH9Yfcgapw5JcxM6tSd62fy4eT7QIDAQABAoIBABJb8Mfb6lPvOl0FMYoDOOPsdPEDkubBDLDYg9nZu4k3X8pNdeFFaQdYr1BG0qk8auumnE+AVGBqgFhePvWQVowiFcdZA+1a8hMQxNnIOAbYXUAZEETTbJhP2pxBSaASm0LA+jtF4Ob8C6BV8DOa28CzOEuQeEdrPSpIKwCueNxUvwVhqjHDMSFUOWaM6p/4LD7qB2KzddEbyUGMLAqnBVlx2CDgsYa3nMCVttCglMWLGit48vknnQU1p0q0HrUDnF/IFknOyUIvFALKrtrLQFWnVfQpfmMiSoxwjUTbGm6vUPXvBHRONQma/0gJdaEcx4sRGIV1uhNjKAUPQ+TOKqkCgYEA+jEt/Q5xWfbqByKe3gtSfyN9/ine70MOegCDUcdZ2kRcAeUNMtDPDK6cgewKwQH+KIM7pFSuZNVSlhtN44w8eX/eHu0FNlbmqPVAKBTwFO4qHo8SmCpgOvr4ORZ/7Skw0WDKfTmibuh5xVtuIANfdinU5+Wk/PxbsMf/dBgDuxUCgYEAzkw/qJEvpMDSJ+PNpHlxCR1eW9qc0oOPknoaT5D/u+/3ZUIRXH2d1+Na7KteIqG8H7lcQJoq97foMhU4u0eoZS1PR0WYQcda7VTJ7qvpv4kvfp2m3LOQxXkPAz8sYZiJDxcwGoANYgwbxRjAy5k23DW5j/bcLLnAaZP/2VthS3kCgYBRUHKV8H++sZVWRFZF7IAfejWUyZ7/PFgUJt6HUbdOTTFqHDux7FOe15FeWI0WNcOY/y3/NtaHRx3UU4N5FTcCdiCHBJnRE2VcPHlhjYyKAO4HiJCOjBdClBbg1wM03VJUepTVJko4qa4KCrE8DlyUVvwvcl+xKYPTbO0Fmlh8EQKBgQCb5FqmkzAhm+QzaJ3ZkDuu25aWatje67PuyH5haf0Wk/urdQWRkwtYPOJUvhrgqL90aaog119o2nyIfCjmvPuvmVGzVg/8hR1dAxmlVhvJNW3CjHevh7H6x8Rke7SZ+5523Nro66MxWF0Tz+TA9gLS3XZgJ/exhJy2K7THF5qqYQKBgFMXsZsGQR/9WrHGoZn/WCYmD5FeSY0VR/5BWFb3jZi1nJfNbPAO7o6Wmk7fZM9mXQEdxwYBtwSoiUO0BWvIeCxtTLU2qkUH8o+LMCraHIRUe5GP7SkKky19XWx3RYVmvJUDBzQHJIQNjDIwKE6RnIfTmxPA9+lBZdWIJ8G13Bdy


String to sign

hello world


Signature

DrIjBV7heaZG+KN1kH0YJASm3IFIrtF9F/2n6xID2y9GQazhcRKNtlu4ZkxrSxycQyzz0+sQTGJbkQmtTSHlySNG0tGSOq2oPI/rJFnhelYzaadq70U6JukGmGFCSEwes5+9LLgTLAH3dyOd06OdtNvtAjD/H/Oi9tP6XKoSbUMSQQPODiTqjOPe0vzG2E6FbPs7+KKWYNNG6xXFR9rYEEYMP8iHn5mvSTegEFRiv35ubMIQnId/L842jVWYjJKufHWWu0XEtNG0VP3+qvFwBGKGpWZ/MmMxvyLDnpCTbe0cBoFvyiebkOnLXQBpr2UAPNVUKQmfnEVbXiZ7+jq1A==


Algorithm

rsaSignatureDigestPKCS1v15SHA512


Apprecite your help

Thanks. Alas, the Base64 of your signature seems to have got munged along the way. I put it into a file like so:

$ xxd sig.b64 
00000000: 4472 496a 4256 3768 6561 5a47 2b4b 4e31  DrIjBV7heaZG+KN1
00000010: 6b48 3059 4a41 536d 3349 4649 7274 4639  kH0YJASm3IFIrtF9
00000020: 462f 326e 3678 4944 3279 3947 5161 7a68  F/2n6xID2y9GQazh
00000030: 6352 4b4e 746c 7534 5a6b 7872 5378 7963  cRKNtlu4ZkxrSxyc
00000040: 5179 7a7a 302b 7351 5447 4a62 6b51 6d74  Qyzz0+sQTGJbkQmt
00000050: 5453 486c 7953 4e47 3074 4753 4f71 326f  TSHlySNG0tGSOq2o
00000060: 5049 2f72 4a46 6e68 656c 597a 6161 6471  PI/rJFnhelYzaadq
00000070: 3730 5536 4a75 6b47 6d47 4643 5345 7765  70U6JukGmGFCSEwe
00000080: 7335 2b39 4c4c 6754 4c41 4833 6479 4f64  s5+9LLgTLAH3dyOd
00000090: 3036 4f64 744e 7674 416a 442f 482f 4f69  06OdtNvtAjD/H/Oi
000000a0: 3974 5036 584b 6f53 6255 4d53 5151 504f  9tP6XKoSbUMSQQPO
000000b0: 4469 5471 6a4f 5065 3076 7a47 3245 3646  DiTqjOPe0vzG2E6F
000000c0: 6250 7337 2b4b 4b57 594e 4e47 3678 5846  bPs7+KKWYNNG6xXF
000000d0: 5239 7259 4545 594d 5038 6948 6e35 6d76  R9rYEEYMP8iHn5mv
000000e0: 5354 6567 4546 5269 7633 3575 624d 4951  STegEFRiv35ubMIQ
000000f0: 6e49 642f 4c38 3432 6a56 5759 6a4a 4b75  nId/L842jVWYjJKu
00000100: 6648 5757 7530 5845 744e 4730 5650 332b  fHWWu0XEtNG0VP3+
00000110: 7176 4677 4247 4b47 7057 5a2f 4d6d 4d78  qvFwBGKGpWZ/MmMx
00000120: 7679 4c44 6e70 4354 6265 3063 426f 4676  vyLDnpCTbe0cBoFv
00000130: 7969 6562 6b4f 6e4c 5851 4270 7232 5541  yiebkOnLXQBpr2UA
00000140: 504e 5655 4b51 6d66 6e45 5662 5869 5a37  PNVUKQmfnEVbXiZ7
00000150: 2b6a 7131 413d 3d                        +jq1A==

and that won’t decode:

$ base64 -D < sig.b64
Invalid character in input stream.

The individual characters all seem to be fine, meaning there’s something wrong with the sequencing.

In situations like this I’ve found it’s better to post hex dumps; they tend to survive copy’n’paste, forums formatting, and so on much better.

ps If you haven’t already looked at the CryptoCompatibility sample code, you should. It shows how to do various crypto transforms such the results exactly match other crypto toolkits, including OpenSSL.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn


Thanks for the info. I think the issue while working on the Java side is I need to take the SecKey from SecKeyCopyExternalRepresentation and convert that into a DER format. Simply base64 encoding the SecKey is not enough, it's missing from what I understand is the RSA OID header and the ASN.1 sequence.


Do you know of any functions in the iOS SDK that do this?


Thanks again

Simply base64 encoding the SecKey is not enough, it's missing from what I understand is the RSA OID header and the ASN.1 sequence.

Right. I’ve talked about this extensively in other threads (related to public keys, not private keys, but similar logic applies). For example:

In many cases you can convince the ‘foreign’ security toolkit to accept the unwrapped structure by using

BEGIN RSA PUBLIC KEY
markers in the PEM (as opposed to
BEGIN PUBLIC KEY
, which is used for the wrapped version).

Do you know of any functions in the iOS SDK that do this?

No. For simple RSA cases it’s relatively easy to do this wrapping by hand. However, my recommendation is that you look at doing this on the server side; it’s likely that the security toolkit there is more fully-featured, and thus can handle this job.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi


The server-side consuming the iOS generated public key is implemented in Java 7 and is currently performed using X509EncodedKeySpec with a constructor being a byte[] of the ASN.1 encoding. Passing in a base64 encoded string with BEGIN/END RSA PUBLIC KEY markers throws an exception.


You'd think Java should be able to import and PCKS #1 with the markers without having to add RSA OID header and the ASN.1 sequence.


Thanks again, appreciate your response.


PS: Adding the OID and ASN.1 the raw public key (Data) works fine with the X509EncodedKeySpec class in Java...

SecKeyCopyExternalRepresentation returns data in the PKCS #1 format for an RSA key. The RSA Public key PEM file looks like below
Code Block
-----BEGIN RSA PUBLIC KEY-----
BASE64 ENCODED DATA
-----END RSA PUBLIC KEY-----

Within the base64 encoded data the following DER structure is present:

Code Block
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}

The PKCS #1 format for an RSA key should be preppended by appropriate “precoded” ASN.1 binary data structure. Refer to the pemPrefixBuffer in the example below:
Code Block
// creating client public and private key
var publicKeySec, privateKeySec: SecKey?
var error: Unmanaged<CFError>?
let keyattribute = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String : 1024,
kSecAttrIsPermanent as String: false
] as CFDictionary
SecKeyGeneratePair(keyattribute, &publicKeySec, &privateKeySec)
// client public key to pem string
let keyData = SecKeyCopyExternalRepresentation(publicKeySec!, &error)
let data = keyData! as Data
let pemPrefixBuffer :[UInt8] = [
0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
0x05, 0x00, 0x03, 0x81, 0x8d, 0x00
]
var finalPemData = Data(bytes: pemPrefixBuffer as [UInt8], count: pemPrefixBuffer.count)
finalPemData.append(data)
let finalPemString = finalPemData.base64EncodedString(options: .lineLength64Characters)
let clientPublicKeyString = "-----BEGIN PUBLIC KEY-----\r\n\(finalPemString)\r\n-----END PUBLIC KEY-----\r\n"

Now you can send clientPublicKeyString to your server expecting a PEM encoded RSA Public key.