RSA Generating Public Key From Private Key

I am recieving a PKCS8 private key from a web service and I am inporting that into the key chain using


SecItemImport()


This is working correctly and I am getting back a SecKeyRef and it puts it into the keychain.


Here are my three Question


  1. I would like to be able to set some attributes for the key that I import such as name and other variables can I do that when I import it?
  2. I can figure out how to get it once I have saved it.
  3. I need to get the public key from the private key I have imported. I have looked at the source for the security framework and there is a method SecKeyCreatePublicFromPrivate() But this appears to be private and not accesible through the framework. Is there a way to store it in the keychain differently like as a certificate or something that way it makes it easier?

I would like to be able to set some attributes for the key that I import such as name and other variables can I do that when I import it?

You can set some attributes via the various fields in the SecItemImportExportKeyParameters structure you pass to SecItemImport. Beyond that, you'll have to do a SecItemUpdate.

I can figure out how to get it once I have saved it.

The "outItems" parameter lets you get a list of items that were actually imported, which you can then use to reference them later on (in a SecItemUpdate call, for example).

I need to get the public key from the private key I have imported. I have looked at the source for the security framework and there is a method SecKeyCreatePublicFromPrivate() But this appears to be private and not accesible through the framework. Is there a way to store it in the keychain differently like as a certificate or something that way it makes it easier?

Looking at the source for SecKeyCreatePublicFromPrivate it really seems to resolve down to a call to SecItemExport on the private key, asking for an export format of kSecFormatBSAFE (at least for RSA keys). That's public API, so there's no reason you can't use it.

Let us know how you get along.

Share and Enjoy

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

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

Thanks for the reply


I am trying to change the name of the private key using SecKeychainItemModifyAttributesAndData() and that seems to be working.


I am still having a problem getting the public key from the imported private key tho. When I call

OSStatus status = SecItemExport(privateKey, kSecFormatBSAFE, 0, NULL, &publicBytes);

It is returning a -25316 status "The contents of this item cannot be retrieved." I am not sure what I am doing wrong here.


I also have a question about get the keys once I have saved them to my applications keychain. This so I can use the key i stored in a different part of my application. I have found the method

SecItemCopyMatching(<#CFDictionaryRef query#>, <#CFTypeRef *result#>);

But I am not sure how to specify my keychain in the query (I created a keychain to store all the credentials for my application in it).


Here is the code for when I add the stuff into the keychain can you take a look and see if there is anything that you can see i need to change?

#pragma mark - User Key Methods
+ (void)setUserPrivateRSAKey:(NSString *)privateKey
{
    /
    if (!privateKey || [privateKey length] == 0)
        return;
    /
    NSData * keyData = [privateKey dataUsingEncoding:NSUTF8StringEncoding];
    /
    SecKeyImportExportFlags importFlags = kSecKeyImportOnlyOne;
    /
    NSMutableArray * keyUsage = [NSMutableArray array];
    [keyUsage addObject:kSecAttrCanDecrypt];
    /
    NSMutableArray * keyAttributes = [NSMutableArray array];
    [keyAttributes addObject:kSecAttrIsPermanent];
    [keyAttributes addObject:kSecAttrIsSensitive];
    /
    SecItemImportExportKeyParameters importParameters;
    importParameters.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    importParameters.flags = importFlags;
    importParameters.passphrase = NULL;
    importParameters.accessRef = [self createAccess:USER_PRIVATE_KEY_NAME];
    importParameters.keyUsage = (__bridge CFArrayRef)(keyUsage);
    importParameters.keyAttributes = (__bridge CFArrayRef)(keyAttributes);
    /
    SecExternalFormat inputFormat = kSecFormatUnknown;
    /
    SecExternalItemType itemType = kSecItemTypePrivateKey;
    /
    CFArrayRef items;
    /
    [self removePrivateKey];
    /
    OSStatus err = SecItemImport((__bridge CFDataRef)keyData, NULL, &inputFormat, &itemType, 0, &importParameters, [self keychain], &items);
    /
    if (err == errSecSuccess)
    {
        /
        NSArray * keys = (__bridge NSArray *)(items);

        /
        SecKeyRef privateKey = (__bridge SecKeyRef)([keys firstObject]);

        /
        SecKeychainAttributeList attributeList;
        SecKeychainAttribute attributes[2];

        /
        const char * labelUTF8 = [USER_PRIVATE_KEY_NAME UTF8String];
        attributes[0].tag = kSecKeyPrintName;
        attributes[0].data = (void *)labelUTF8;
        attributes[0].length = (UInt32)strlen(labelUTF8);

        /
        const char * tagUTF8 = [USER_PRIVATE_KEY_APPLICATION UTF8String];
        attributes[1].tag = kSecKeyApplicationTag;
        attributes[1].data = (void *)tagUTF8;
        attributes[1].length = (UInt32)strlen(tagUTF8);

        /
        attributeList.count = 2;
        attributeList.attr = attributes;

        /
        SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)privateKey, &attributeList, 0, NULL);

        /
        [self addPublicKeyFromPrivateKey:privateKey];
    }
}
+ (void)addPublicKeyFromPrivateKey:(SecKeyRef)privateKey
{
    /
    CFDataRef publicBytes = NULL;
    /
    OSStatus status = SecItemExport(privateKey, kSecFormatBSAFE, 0, NULL, &publicBytes);
    if ((status == errSecSuccess) && (publicBytes != NULL))
    {
        /
        SecKeyImportExportFlags importFlags = kSecKeyImportOnlyOne;

        /
        NSMutableArray * keyUsage = [NSMutableArray array];
        [keyUsage addObject:kSecAttrCanEncrypt];

        /
        NSMutableArray * keyAttributes = [NSMutableArray array];
        [keyAttributes addObject:kSecAttrIsPermanent];
        [keyAttributes addObject:kSecAttrIsSensitive];

        /
        SecItemImportExportKeyParameters importParameters;
        importParameters.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
        importParameters.flags = importFlags;
        importParameters.passphrase = NULL;
        importParameters.accessRef = [self createAccess:USER_PUBLIC_KEY_NAME];
        importParameters.keyUsage = (__bridge CFArrayRef)(keyUsage);
        importParameters.keyAttributes = (__bridge CFArrayRef)(keyAttributes);

        /
        SecExternalFormat inputFormat = kSecFormatBSAFE;

        /
        SecExternalItemType itemType = kSecItemTypePublicKey;

        /
        CFArrayRef items;

        /
        OSStatus status = SecItemImport(publicBytes, NULL, &inputFormat, &itemType, 0, &importParameters, [self keychain], &items);

        /
        CFRelease(publicBytes);
    }
}

Not sure why comments didn't get added.

SecKeyCreateFromPublicDataLooking at the source for SecKeyCreatePublicFromPrivate it really seems to resolve down to a call to SecItemExport on the private key, asking for an export format of kSecFormatBSAFE (at least for RSA keys). That's public API, so there's no reason you can't use it.


I'm having trouble with this too. I don't really understand how this is supposed to work. Without specifying some export parameter to the export function, how does it know whether it should export the private key or derive the public key and export that?


Using what loosegoose posted above, I can verify that :


1. I am passing in a PKCS8 PEM encoded 1024 unencrypted RSA private key. Specifiying kSecFormatBSAFE allows me to import the key.
2. When I try to export the item and specify kSecFormatBSAFE, I get a PKCS8 1024 unencrypted RSA private key (the same one byte for byte I passed in). If I specify kSecFormatOpenSSL, I get a PKCS1 1024 RSA Private key.


This all follows what I would expect from this table listed in Apple source:https://slack-files.com/T03BUJQE0-F07404UNM-166ce9f5f0


Which is what I would expect. I asked to export a private key from keychain, and I got it. What I don't understand is in SecKeyCreatePublicFromPrivate, where we are provided a reference to the private key. We call SecKeyCopyPublicBytes, which checks the algorithm ID. I would GUESS it would be defined as kSecRSAAlgorithmID (since it's an RSA key), in which case this is done:


return SecItemExport(key, kSecFormatBSAFE, 0, NULL, publicBytes);


The naming convention of the variables leads me to believe that we are exporting the public key portion of the provided key (which is a private key). The format just specifies that it should be exported (if we expect to be getting a public key) as PKCS1. But how can the export function possibly know that without any export attributes? To me this either returns the full private key into publicBytes, in which case that naming is extremely confusing, or ... I have no idea what else it would do.


When I call SecItemExport with my private key reference (Which should be exactly what the open source does), I get back the private key. When that function returns it calls SecKeyCreateFromPublicData, but from what I can see, it fully believes that it's getting public key data. That method reads the length of publicData, which has a length of the private key. This leads me to believe that the SecItemExport was really intended to spit out the public key and not the private key.


The ONLY thing I can think of that would make it "work" is if the type was set to kSecECDSAAlgorithmID, since it actually parses the ASN.1 encoding and tries to pull out the header and get to the actual public data, ignores the remander of the private key, and returns the public data. But that would be confusing since, EC would lead me to believe this is meant for an eliptic curve key.


I had to write a parser to remove the pkcs8 header information and derive the public key from the private key for iOS, because iOS doesnt have the ability to deal with PKCS8 keys. I'd rather use Apple maintined code rather than my own and was told that OS X had the capability to do such things.

Looking in the Security-57031.1.35 source and the method


SecKeyRef SecKeyCreatePublicFromPrivate(SecKeyRef privateKey)


Appears to be working incorrectly when an RSA private key is used.


There are two different implementations one in the SecKey.cpp and one in SecKey.c files neither appears to work.


The c plus plus version is the one that I did the buly of my testing with. The method tries to copy the public bytes and then use create from public key data. Here is the code


OSStatus status = errSecParam;

CFDataRef serializedPublic = NULL;

status = SecKeyCopyPublicBytes(privateKey, &serializedPublic);
if ((status == errSecSuccess) && (serializedPublic != NULL)) {
    SecKeyRef publicKeyRef = SecKeyCreateFromPublicData(kCFAllocatorDefault, SecKeyGetAlgorithmId(privateKey), serializedPublic);
    CFRelease(serializedPublic);
    if (publicKeyRef != NULL) {
        return publicKeyRef;
    }
}


The problem here is that when


status = SecKeyCopyPublicBytes(privateKey, &serializedPublic);


is being called it is actaully returning the bytes for the private key. Then when


SecKeyRef publicKeyRef = SecKeyCreateFromPublicData(kCFAllocatorDefault, SecKeyGetAlgorithmId(privateKey), serializedPublic);


Is called it is taking the private bytes and trying to import them with the public key flag


SecKeyRef SecKeyCreateFromPublicData(CFAllocatorRef allocator, CFIndex algorithmID, CFDataRef publicBytes)
{
    SecExternalFormat externalFormat = kSecFormatOpenSSL;
    SecExternalItemType externalItemType = kSecItemTypePublicKey;
    CFDataRef workingData = NULL;
    CFArrayRef outArray = NULL;
    SecKeyRef retVal = NULL;
  
    if (kSecRSAAlgorithmID == algorithmID) {
  /
  * kSecFormatBSAFE uses the original PKCS#1 definition:
  *     RSAPublicKey ::= SEQUENCE {
  *        modulus           INTEGER,  -- n
  *        publicExponent    INTEGER   -- e
  *     }
  * kSecFormatOpenSSL uses different ASN.1 encoding.
  */
  externalFormat = kSecFormatBSAFE;
        workingData = _CFDataCreateReferenceFromRange(kCFAllocatorDefault, publicBytes, CFRangeMake(0, CFDataGetLength(publicBytes)));
    }

  if(SecItemImport(workingData, NULL, &externalFormat, &externalItemType, 0, NULL, NULL, &outArray) != errSecSuccess) {
  goto cleanup;
    }
  if(!outArray || CFArrayGetCount(outArray) == 0) {
  goto cleanup;
  }
    retVal = (SecKeyRef)CFArrayGetValueAtIndex(outArray, 0);
    CFRetain(retVal);
cleanup:
    if(workingData) CFRelease(workingData);
    if(outArray) CFRelease(outArray);
    return retVal;
}


Amm I doing something worng here? I would like to use Apple code for this rather than writing my own parser to pull out the public key.

Am I doing something worng here?

Alas, I don't have time to dig into this in the context of DevForums. You should open a DTS tech support incident so that I, or one of my colleagues, can spend time researching it.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
RSA Generating Public Key From Private Key
 
 
Q