How to import ec pem-encoded public/private keys?

I have a function that get import RSA keys.



+ (SecKeyRef)addKeyWithData:(NSData *)data asPublic:(BOOL)public tag:(NSString *)tag type:(NSString *)type error:(NSError *__autoreleasing*)error; {
    NSString *keyClass = (__bridge NSString *)(public ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate);
    NSInteger sizeInBits = data.length;
    NSDictionary *attributes = @{
        (__bridge NSString*)kSecAttrKeyType : type,
        (__bridge NSString*)kSecAttrKeyClass : keyClass,
        (__bridge NSString*)kSecAttrKeySizeInBits : @(sizeInBits)
    };
    
    if (SecKeyCreateWithData != NULL) {
        CFErrorRef createError = NULL;
        SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)attributes, &createError);
        if (error && createError != nil) {
            *error = (__bridge NSError*)createError;
        }
        return key;
    }
    / 
    else {
        CFTypeRef result = NULL;
        NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary *commonAttributes = @{
            (__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassKey,
            (__bridge NSString*)kSecAttrApplicationTag: tagData,
            (__bridge NSString*)kSecAttrAccessible: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked
        };
        NSDictionary *addItemAttributes = @{
           (__bridge NSString*)kSecValueData: data,
           (__bridge NSString*)kSecReturnPersistentRef: @(YES),
        };
        OSStatus addItemStatus = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryByCombiningDictionaries:@[attributes, commonAttributes, addItemAttributes]], &result);
        if (addItemStatus != errSecSuccess && addItemStatus != errSecDuplicateItem) {
            / 
            / 
            return NULL;
        }
        NSDictionary *copyAttributes = @{
                (__bridge NSString*)kSecReturnRef: @(YES),
        };
        CFTypeRef key = NULL;
        / 
        OSStatus copyItemStatus = errSecSuccess;
        SecItemCopyMatching((__bridge CFDictionaryRef)[self dictionaryByCombiningDictionaries:@[attributes, commonAttributes, copyAttributes]], &key);
        if (key == NULL) {
            / 
        }
        return (SecKeyRef)key;
    }
    return NULL;
}


I try to adjust it to get kSecAttrKeyTypeECSECPrimeRandom keys.

Input data is base64 data:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc
/DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==
-----END PUBLIC KEY-----


Here data is

NSString *urlEncodedString = @"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==";
NSData *data = [[NSData alloc] initWithBase64EncodedString:urlEncodedString options:0];


For private key:

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJmVse5uPfj6B4TcXrUAvf9/8pJh+KrKKYLNcmOnp/vPoAoGCCqGSM49
AwEHoUQDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9
MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==
-----END EC PRIVATE KEY-----


Data is

NSString *urlEncodedString = @"
MHcCAQEEIJmVse5uPfj6B4TcXrUAvf9/8pJh+KrKKYLNcmOnp/vPoAoGCCqGSM49AwEHoUQDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w==";
NSData *data = [[NSData alloc] initWithBase64EncodedString:urlEncodedString options:0];


Function invocation is


NSData *data = [self getKeyFromPemFile];
BOOL public = public; // YES or NO
NSString *tag = (__bridge NSString*)kSecAttrKeyTypeECSECPrimeRandom;
NSError *error = nil;
NSString *tag = [NSUUID UUID].UUIDString;
[self addKeyWithData:(NSData *)data asPublic:(BOOL)public tag:tag type:type error:error;


And error is

Error Domain=NSOSStatusErrorDomain Code=-50 "EC public key creation from data failed" UserInfo={NSDescription=EC public key creation from data failed}


( Error occures on mac/iOS simulator )

Replies

The bad news is that importing and exporting private keys in this way is not supported. It works, kinda, but that’s more an accident of the implementation than as properly designed API. I’m not surprised you’re having problems with EC keys.

The recommended way to import a key on older versions of iOS is via a PKCS#12.

The good news is that iOS 10 introduced a new API for importing and exporting keys, namely

SecKeyCreateWithData
and
SecKeyCopyExternalRepresentation
.

Share and Enjoy

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

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

I run tests on iOS Simulator ( iOS SDK 10 ).
Function should ( due to weak linking ) choose condition branch with SecKeyCreateWithData. ( And it choose ).However, I still have a problems with improting keys.


What data and what attributes dictionary expect SecKeyCreateWithData for EC key? ( Public or Private )

Maybe I am incorrect in attributes?

Quoting the header comments for

SecKeyCreateWithData
:
Mandatory attributes are:

* kSecAttrKeyType
* kSecAttrKeyClass
* kSecAttrKeySizeInBits

and:

The requested data format depend on the type of key
(kSecAttrKeyType) being created:

* kSecAttrKeyTypeRSA              PKCS#1 format
* kSecAttrKeyTypeECSECPrimeRandom  SEC1 format (www.secg.org)

Share and Enjoy

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

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

Well, I still have problems with SecKeyCreateWithData

As I understand correctly, SEC1 format is an internal format for keys. It is a binary wrapper for EC keys.

These keys are presented in file format, for example, in PEM file.


Next, if I am correctly understand 'errors', it seems that openssl tool doesn't generate pure SEC1. Key should be polished ( headers removed, for example ) before passing to SecKeyCreateWithData.

I suppose that somekind of header ( binary format header ) prevents SecKeyCreateWithData from creating correct key.

Passing keys 'as is' to SecKeyCreateWithData errored with the same error, so, one sequence of bytes could be a reason why it happens. Header could be a candidate for it. It exists in both keys, for example.



However, I generate keys with these commands. ( Which are incompatible 'as is' with SecKeyCreateWithData, they are incorrectwithout postprocessing (removing somekind of header, for example)).

# generate ecdsa private key.
openssl ecparam -genkey -name prime256v1  -out ec256-private.pem
# generate ecdsa public key.
openssl ec -in ec256-private.pem -pubout -out ec256-public.pem

The output of your commands is a PEM, a text file as shown below.

$ cat ec256-public.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0izAMfQ7xhdrx07MziByyh4CbTgb
Q0KYt0zSgNyrz1Re78e9m7FYDES95y/f5zZ1HSmq0+h4LJRmcpV0BXethA==
-----END PUBLIC KEY-----

If you strip the PEM markers (the first and last line), you get Base64-encoded ASN.1 data. You can decide this as follows:

$ base64 -D < ec256-public.b64 > ec256-public.asn1
$ dumpasn1 -p ec256-public.asn1
SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER '1 2 840 10045 2 1'
    OBJECT IDENTIFIER ansiX9p256r1 (1 2 840 10045 3 1 7)
    }
  BIT STRING
    04 D2 2C C0 31 F4 3B C6 17 6B C7 4E CC CE 20 72
    CA 1E 02 6D 38 1B 43 42 98 B7 4C D2 80 DC AB CF
    54 5E EF C7 BD 9B B1 58 0C 44 BD E7 2F DF E7 36
    75 1D 29 AA D3 E8 78 2C 94 66 72 95 74 05 77 AD
    84
  }

Note You can get

dumpasn1
from here.

This is an ASN.1

SubjectPublicKeyInfo
structure, where the stuff before the
BIT STRING
is a header identifying the type of key, and the stuff inside the
BIT STRING
is the raw key itself. For more details, read this post.

I believe, but haven’t tested, that

SecKeyCreateWithData
wants the raw key. One way to test whether that’s the case is to generate an EC key on iOS (using
SecKeyGeneratePair
) and then export it using
SecKeyCopyExternalRepresentation
. If the data looks like the contents of the
BIT STRING
shown above, you’re on the right track.

Share and Enjoy

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

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

Hi!
One year later, I have started to complete this task 🙂


Now I could create EC-keys, but it is a bit painful, because Public keys really want BitString.


But Private keys want sequence before:


  SEQUENCE {
    OBJECT IDENTIFIER '1 2 840 10045 2 1'
    OBJECT IDENTIFIER ansiX9p256r1 (1 2 840 10045 3 1 7)
    }


Could you advise a correct ASN.1 template from coder which could split key into Public part and Private part?


#import <Security/SecAsn1Coder.h>
#import <Security/SecAsn1Templates.h>


My task is a bit simple than parsing whole key. I need to parse sequence into sequence of private part ( above ) and public Bit string ( second part ).


Thanks!

Could you advise a correct ASN.1 template from coder which could split key into Public part and Private part?

I thought you were working on iOS. If so,

SecAsn1Coder
won’t help because it’s a macOS-only API.

Share and Enjoy

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

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

Could you advise how to retrieve private part of key?
https://github.com/yourkarma/JWT/issues/145


Because I do not understand which bytes I should skip ( after stripping off Bit String with Public key ).

A part of my inverstigation in this issue.

I try to create private key from your key. ( Apple Key )


MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgpnX9ZXmgLCWQ+Hkpvae2PLU68XEzJdp+NjswuBS9RHWgCgYIKoZIzj0DAQehRANCAARMSO6bkKjLT+9Mx9wJRXoqUx+CbeOhAbVGS+3fgvVNGv3QM3NlMou3uguMrITwVvpWjuocXbSzjTwMstMMjsZg


Ok, I move on and strip off public key first ( BIT String ) from the end of key.

Next, I cut one-by-one byte from the beginning.

And yes, I create both private key and public key.


(lldb) po privateKey.key <SecKeyRef curve type: kSecECCurveNone, algorithm id: 3, key type: ECPrivateKey, version: 4, block size: 192 bits, addr: 0x100512a20> 
(lldb) po publicKey.key <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPublicKey, version: 4, block size: 256 bits, y: FDD0337365328BB7BA0B8CAC84F056FA568EEA1C5DB4B38D3C0CB2D30C8EC660, x: 4C48EE9B90A8CB4FEF4CC7DC09457A2A531F826DE3A101B5464BEDDF82F54D1A, addr: 0x10070efb0>

And as I understand, private key is broken. It doesn't have proper curve type.