Output SecKeyCopyExternalRepresentation
Asked Answered
F

4

5

I'm trying to pass around a public key from my iPhone to other parties, however I am unable to use the output from iOS.

let parameters: [String: Any] = [
    kSecAttrKeySizeInBits as String: 384,
    kSecAttrKeyType as String: kSecAttrKeyTypeEC,
    kSecPrivateKeyAttrs as String: [
        kSecAttrIsPermanent as String: false
    ]
]

var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error)
let publicKey = SecKeyCopyPublicKey(privateKey!)

let pub = SecKeyCopyExternalRepresentation(publicKey!, &error)
let pubData = pub as Data?
print(pubData!.base64EncodedString())

Example output:

BJSCZtBatd2BYEHtyLB0qTZNlphKf3ZTGI6Nke3dSxIDpyP9FWMZbG0zcdIXWENyndskfxV0No/yz369ngL2EHZYw6ggNysOnZ5IQSPOLFFl44m1aAk0o0NdaRXTVAz4jQ==

In python (where my second party is) I have the following:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

pub_key = serialisation.load_pem_public_key(
    data=xcode_data.encode(),
    backend=default_backend()
)

The error I get is ValueError: Could not deserialize key data.

So what exactly is the output of the SecKeyCopyExternalRepresentation as described by the documentation:

The method returns data in the PCKS #1 format for an RSA key. For an elliptic curve public key, the format follows the ANSI X9.63 standard using a byte string of 04 || X || Y. For an elliptic curve private key, the output is formatted as the public key concatenated with the big endian encoding of the secret scalar, or 04 || X || Y || K. All of these representations use constant size integers, including leading zeros as needed.

How would one describe the X6.93 format? And how would I go about converting it to something I can use in the python code?

P.S. I have tried to add headers such as -----BEGIN PUBLIC KEY----- to the xcode output.

Fleischer answered 22/4, 2018 at 15:38 Comment(0)
F
7

I havent quite found the answer to the question as I still don't know what exactly the output is that Apple provides, however, I came up with a solution found in this key import export manager.

let parameters: [String: Any] = [
    kSecAttrKeySizeInBits as String: 384,
    kSecAttrKeyType as String: kSecAttrKeyTypeEC,
    kSecPrivateKeyAttrs as String: [
        kSecAttrIsPermanent as String: false
    ]
]

var pubKey: SecKey?
var priKey: SecKey?
var error: Unmanaged<CFError>?
let keyPair = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &priKey)

let publicKeyData = SecKeyCopyExternalRepresentation(pubKey!, &error)
// Code from the library
let ieManager = CryptoExportImportManager()
if let exportPEM = ieManager.exportPublicKeyToPEM(publicKeyData as Data!, keyType: kSecAttrKeyTypeEC as String, keySize: 384) {
    print(exportPEM)
} else {
    print("Error exporting to PEM")
}

Example output:

Exporting EC raw key: 97 bytes -----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFpCnTrJFQq0mZBvy+vzl9noKLZ4/s1cf I6hygug6s8dvBreMhabAcAbbhSa1losjCxV450nq92W9ZymonYasaAuhshDWjmvx 2qTXHEpVEVb9GawqX6XqpWtIBf+meHKS -----END PUBLIC KEY-----

Implementation in python using cryptography

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

xcode = '-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFpCnTrJFQq0mZBvy+vzl9noKLZ4/s1cf\nI6hygug6s8dvBreMhabAcAbbhSa1losjCxV450nq92W9ZymonYasaAuhshDWjmvx2\nqTXHEpVEVb9GawqX6XqpWtIBf+meHKS\n-----END PUBLIC KEY-----'
pub_key = serialization.load_pem_public_key(xcode.encode(), default_backend())
xcode

Outputs the following: <cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey object at 0x7fb4f6f50e10>

Note that you do have to add the new lines yourself in python in order for this all to work.

Update

The output of the SecKeyCopyExternalRepresentation for ECC keys is the X9.62 or X9.63 format of the key (in uncompressed form). This is very different from DER and therefor PEM encoding.

The encoding is 04 || X || Y for a public key and 04 || X || Y || K for a private key. 04 is a fixed byte for this format. The X, Y and optionally K value are points or coordinates that define the curve of this key. More info about that over here.

Fleischer answered 23/4, 2018 at 14:15 Comment(0)
C
2

I will provide an answer for those who can't use a third party.

The output of the format is 04 || X || Y [ || K] without the EC header identifier that many outside systems expect. You'll need to add that header when exporting it for other platforms. Here's an example:

let fullKeyData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0))
if fullKeyData != nil
{
    //Fixed schema header per key size in bits
    //var headerBytes256r1: [UInt8] = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00] //uncomment if you use 256 bit EC keys
    var header384r1: [UInt8] = [0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00] //384 bit EC keys
    //var header521r1: [UInt8] = [0x30, 0x81, 0x9B, 0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86, 0x00] // For 521 bit EC keys
    let headerSize = CFIndex(header384r1.count)

    CFDataAppendBytes(fullKeyData, &header384r1, headerSize)
    CFDataAppendBytes(fullKeyData, CFDataGetBytePtr(pub), CFDataGetLength(pub)) //pub = data you got from SecKeyCopyExternalRepresentation

    var pem = ""
    //pem.append("-----BEGIN PUBLIC KEY-----\n") //uncomment if needed
    pem.append((fullKeyData as Data?)?.base64EncodedString() ?? "")
    //pem.append("\n-----END PUBLIC KEY-----\n") //uncomment if needed

    //do something with pem
}
Camilia answered 24/4, 2020 at 17:34 Comment(1)
Yeah, in the meantime I understand what the format is. The documentation is confusing. The export format is X9.62/X9.63 (as you pointed out). The exact output is the uncompressed point output. The documentation usually says however that public keys are exported in DER format, which isn't true for elliptic curves, but thanks!Fleischer
C
1

For elliptic curve keys, you can get the DER/PEM representation of the X9.63 data using CryptoKit's P256.Signing.PublicKey (in iOS 13 or later), so you don't have to hardcode the header nor use an external library.

For example:

import CryptoKit

let externalRepresentation = SecKeyCopyExternalRepresentation(...)
let p256PublicKey = try P256.Signing.PublicKey(x963Representation: externalRepresentation ?? Data())
let derData = p256PublicKey.derRepresentation

print(derData.base64EncodedString())
// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBstxGDlRch5JplnnBRXSG8X+pxV25WcfGH/+h6sG8XgiCxR+W2x9y0wcqnD5GF7clLbOqnEjPWEv2GXsJ1mfSw==

If you need the PEM instead of the DER representation, you may use p256PublicKey.pemRepresentation instead. Example output:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBstxGDlRch5JplnnBRXSG8X+pxV2
5WcfGH/+h6sG8XgiCxR+W2x9y0wcqnD5GF7clLbOqnEjPWEv2GXsJ1mfSw==
-----END PUBLIC KEY-----
Coffer answered 27/10, 2023 at 16:24 Comment(0)
F
0

BJSCZtBatd2BYEHtyLB0qTZNlphKf3ZTGI6Nke3dSxIDpyP9FWMZbG0zcdIXWENyndskfxV0No/yz369ngL2EHZYw6ggNysOnZ5IQSPOLFFl44m1aAk0o0NdaRXTVAz4jQ==

The key appears to be malformed.

It appears to be an EC key. I suspect it is the public one detailed as 04 || X || Y, but I could be wrong. Do you know what field the key is over? The field tells you how many bytes are in X and Y.

$ cat key.dat
BJSCZtBatd2BYEHtyLB0qTZNlphKf3ZTGI6Nke3dSxIDpyP9FWMZbG0zcdIXWENyndskfxV0No/yz369ngL2EHZYw6ggNysOnZ5IQSPOLFFl44m1aAk0o0NdaRXTVAz4jQ==

$ base64 -d key.dat | hexdump -C
00000000  04 94 82 66 d0 5a b5 dd  81 60 41 ed c8 b0 74 a9  |...f.Z...`A...t.|
00000010  36 4d 96 98 4a 7f 76 53  18 8e 8d 91 ed dd 4b 12  |6M..J.vS......K.|
00000020  03 a7 23 fd 15 63 19 6c  6d 33 71 d2 17 58 43 72  |..#..c.lm3q..XCr|
00000030  9d db 24 7f 15 74 36 8f  f2 cf 7e bd 9e 02 f6 10  |..$..t6...~.....|
00000040  76 58 c3 a8 20 37 2b 0e  9d 9e 48 41 23 ce 2c 51  |vX.. 7+...HA#.,Q|
00000050  65 e3 89 b5 68 09 34 a3  43 5d 69 15 d3 54 0c f8  |e...h.4.C]i..T..|
00000060  8d                                                |.|
00000061
Fogbound answered 22/4, 2018 at 16:27 Comment(2)
So something odd happening. Any clue as to where it happens? Is it at the CFData -> Data conversion, or does it happen at the base64 conversion? Or does it happen in the function provided by Apple?Fleischer
As I am in development mode an recreate private keys every time, my pk is: BK7IHmEXa3K/kEWtBNAn4PtF9t9x7dY6xGN1zQwRP+W1rvH9vcjnLU+//rLVQhnsFPFXefUTzOCbLSS2k0cH1WUhGpmW+EMowArfLwEe7+l8q0BMJ0044uR/CH8yiIiLAXjt3XjqFsneVqJxsWAmndpVLHsfxOK8szsb0+MET5w78Q/394sNCgGtSIVG5EMmTQ== and my public key is BK7IHmEXa3K/kEWtBNAn4PtF9t9x7dY6xGN1zQwRP+W1rvH9vcjnLU+//rLVQhnsFPFXefUTzOCbLSS2k0cH1WUhGpmW+EMowArfLwEe7+l8q0BMJ0044uR/CH8yiIiLAQ==Fleischer

© 2022 - 2024 — McMap. All rights reserved.