Using SecKeyCreateWithData to Load data from openssl ecparam generated keys

I've been reading previous threads such as this about how to import openssl generated keys using SecKeyCreateWithData but always get the error Code=-50 "EC private key creation from data failed". I understand that SecKeyCreateWithData doesn't allow the standard headers that openssl generates, but even after converting the bit string section to a binary file, it refuses to load.


This is the command I use to generate the key:


openssl ecparam -name prime256v1 -genkey -noout -out openssl.key -outform der


Using dumpasn1:

$ ~/Desktop/dumpasn1 -w76 openssl.key
  0 119: SEQUENCE {
  2   1:   INTEGER 1
  5  32:   OCTET STRING
       :     67 BF 75 38 C7 14 38 88 4C DD BC 91 A5 5C 88 10
       :     DE 0C 6F EE A7 2B 85 75 19 EC 71 1D 91 BD FD E3
39  10:   [0] {
41   8:     OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
       :     }
51  68:   [1] {
53  66:     BIT STRING
       :       04 5A C4 BB DE 43 15 25 9E E2 A3 CD D8 80 0E 12
       :       57 24 0D 68 BD 22 61 57 D9 87 F8 E2 16 DD 8C 02
       :       AD 73 4F D0 69 6F F0 61 0D FD FB 4D EB F1 45 C0
       :       AB D4 46 82 B6 DB 69 62 8F C9 7C C9 07 09 0C 91
       :       91
       :     }
       :   }


If I trim off the headers:


$ dd if=openssl.key of=stripped.openssl.key skip=56 bs=1
65+0 records in
65+0 records out
65 bytes transferred in 0.000320 secs (203001 bytes/sec)

$ hexdump -Cv stripped.openssl.key
00000000  04 5a c4 bb de 43 15 25  9e e2 a3 cd d8 80 0e 12  |.Z...C.%........|
00000010  57 24 0d 68 bd 22 61 57  d9 87 f8 e2 16 dd 8c 02  |W$.h."aW........|
00000020  ad 73 4f d0 69 6f f0 61  0d fd fb 4d eb f1 45 c0  |.sO.io.a...M..E.|
00000030  ab d4 46 82 b6 db 69 62  8f c9 7c c9 07 09 0c 91  |..F...ib..|.....|
00000040  91                                                |.|
00000041


It's 65 bytes beginning with 04, which I believe it just the keydata. This refuses to load on iOS with the error:


var error: Unmanaged?
        guard let key = SecKeyCreateWithData(privateKey as CFData,
                                             [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
                                              kSecAttrKeyClass: kSecAttrKeyClassPrivate,
                                              kSecAttrKeySizeInBits: 256] as CFDictionary,
                                             &error)



key length: 65
failed - Failed to create key: Unmanaged(_value: Error Domain=NSOSStatusErrorDomain Code=-50 "EC private key creation from data failed" UserInfo={NSDescription=EC private key creation from data failed})
However, it will load as a public key which I don't understand.


I tried creating a key in iOS then saving it as data and it generates a larger size (97 vs 65) but it will succesfully load.


$ hexdump -Cv ios.private.key
00000000  04 6b 17 c3 46 32 db 10  02 9a 2b 02 de 53 89 c3  |.k..F2....+..S..|
00000010  87 71 d3 bf b6 1a 64 c0  0e e1 35 6e 1c 9f af 5f  |.q....d...5n..._|
00000020  70 d2 05 ba fc 4e fb 5a  e2 93 6a 68 12 b1 18 a8  |p....N.Z..jh....|
00000030  c3 f1 2a db aa 77 a1 e0  57 bc d9 23 6c a4 82 c7  |..*..w..W..#l...|
00000040  75 a9 d0 e0 01 76 6c de  8d f0 22 64 a0 4e 06 bf  |u....vl..."d.N..|
00000050  c3 0e b1 4d 45 3e fc 9f  f1 a4 4b e3 85 e0 0f 07  |...ME>....K.....|
00000060  bd                                                |.|
00000061


I just want to understand what these extra bytes are and how to get a openssl-generated key to load in iOS.


Thanks

Answered by DTS Engineer in 403629022

In

<Security.framework/SecKey.h>
you’ll see this comment:
The requested data format depend on the type of key (kSecAttrKeyType) being created:
 * kSecAttrKeyTypeRSA               PKCS#1 format, public key can be also in x509 public key format
 * kSecAttrKeyTypeECSECPrimeRandom  ANSI X9.63 format (04 || X || Y [ || K])

Look at the last line, which describes the EC key format. The

K
item is in square brackets, indicating it’s optional. This is the difference between a public key (without
K
) and a private key (with
K
).

I don’t know what spec the OpenSSL key conforms to — I don’t have time tonight to look up the details — but it seems that it stores this

K
value in first
OCTET STRING
. That is, if you take the 67 BF 75 38 … 91 BD FD E3 bytes and append it to 04 5A C4 BB … 09 0C 91 91 bytes, you end up with 97 bytes that import correctly.

Share and Enjoy

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

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

In

<Security.framework/SecKey.h>
you’ll see this comment:
The requested data format depend on the type of key (kSecAttrKeyType) being created:
 * kSecAttrKeyTypeRSA               PKCS#1 format, public key can be also in x509 public key format
 * kSecAttrKeyTypeECSECPrimeRandom  ANSI X9.63 format (04 || X || Y [ || K])

Look at the last line, which describes the EC key format. The

K
item is in square brackets, indicating it’s optional. This is the difference between a public key (without
K
) and a private key (with
K
).

I don’t know what spec the OpenSSL key conforms to — I don’t have time tonight to look up the details — but it seems that it stores this

K
value in first
OCTET STRING
. That is, if you take the 67 BF 75 38 … 91 BD FD E3 bytes and append it to 04 5A C4 BB … 09 0C 91 91 bytes, you end up with 97 bytes that import correctly.

Share and Enjoy

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

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

Wow that works! thanks


I wrote a simple ruby script to convert it if anyone needs it: https://gist.github.com/niveus/f9ed2010dadeaf1df72a2d81d1317722

Hi, thank you for making that script, however i still can't get it to work.

I created a key like mentioned in the script:

openssl ecparam -name prime256v1 -genkey -noout -out openssl.key -outform der

And converted it using the script:

./convert-ec-private-key-openssl-to-apple.rb openssl.key out.der
Private key start: 6, length: 32
Private key: 5...3
Public key start: 56, length: 66
Public key: 047481669ec2e9835109c55c574c78237b7a98e8743e0eec41e44ff3c496a648f7665bbdc0e1ff530a100796a3763a4b4768e6fec2538edb3ffe5ce5eeaa208cec

The filesize is 97bytes suggesting a correct conversion, and it starts with 0x04:

hexdump -Cv out.der
00000000  04 74 81 66 9e c2 e9 83  51 09 c5 5c 57 4c 78 23  |.t.f....Q..\WLx#|
00000010  7b 7a 98 e8 74 3e 0e ec  41 e4 4f f3 c4 96 a6 48  |{z..t>..A.O....H|
00000020  f7 66 5b bd c0 e1 ff 53  0a 10 07 96 a3 76 3a 4b  |.f[....S.....v:K|
00000030  47 68 e6 fe c2 53 8e db  3f fe 5c e5 ee aa 20 8c  |Gh...S..?.\... .|
00000040  ec 57 a4 64 ed dc a2 ee  13 ef 12 5f e2 2b 5a 22  |.W.d......._.+Z"|
00000050  c6 64 2b 9c 54 61 6f 1b  75 00 53 13 e7 90 71 6a  |.d+.Tao.u.S...qj|
00000060  73                                                |s|
00000061

But when i try to load it, i still get the dreaded code -50 with "EC public key creation from data failed". What am i doing wrong?

Here is my loading code:

    private static func loadPublicKey() -> SecKey? {
        guard let publicKeyData = try? Data(contentsOf: Bundle.main.url(forResource: "out", withExtension: "der")!) else {
            print("Failed to load public key data from file")
            return nil
        }
        
        print("Public key data length: \(publicKeyData.count)")
        
        let byteArray = publicKeyData.map { String(format: "%02x", $0) }
        print("Public key data bytes (loaded in iOS): \(byteArray.joined(separator: " "))")

        let attributes: [String: Any] = [
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
            kSecAttrKeySizeInBits as String: 256
        ]

        var error: Unmanaged<CFError>?
        guard let secKey = SecKeyCreateWithData(publicKeyData as CFData, attributes as CFDictionary, &error) else {
            if let error = error {
                print("SecKeyCreate init failed: \(error.takeRetainedValue() as Error)")
            } else {
                print("SecKeyCreate init failed with unknown error")
            }
            return nil
        }

        return secKey
    }
Using SecKeyCreateWithData to Load data from openssl ecparam generated keys
 
 
Q