How to extract the private key from a p8 APNs file?

I own an Apple APNs p8 file, which is basically a PKCS#8 file containing a public key as well as a private key.

I basically need to extract the private key information from this file, to then sign a JWT.

DISCLAIMER: the following information matches a real key which has been revoked.

I started by generating an ASN1 file from my p8 file. Using the dumpasn1 tool, I get this output:

 2 SEQUENCE {
 3   INTEGER 0
 4   SEQUENCE {
 5     OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
 6     OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
 7     }
 8   OCTET STRING, encapsulates {
 9     SEQUENCE {
10       INTEGER 1
11       OCTET STRING
12         B8 89 CA 8C 12 AB AF 74 9E CA 11 D4 D8 36 B8 28
13         D1 99 4F 8D B3 72 52 49 3D 85 44 02 08 05 64 1E
14       [0] {
15         OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
16         }
17       [1] {
18         BIT STRING
19           04 CD BB 86 D3 2C 4D 08 11 6A A4 D3 38 4E AE 1B
20           DF E3 EC E5 D4 6F 03 0D CF 39 CD 79 3C 2E E6 74
21           FA 93 54 10 F2 10 41 92 78 57 7D 87 72 55 F3 30
22           BE F4 CA 6F F0 89 55 24 B8 B6 84 89 9C 4A 08 B5
23           1A
24         }
25       }
26     }
27   }

As I understand it, the private key is represented by a byte array written in HEX format from line 19 to line 23.

I now want to convert this byte array into a raw string.

IMHO, a good starting point would be to determine the encoding of the original text these bytes come from... and that's where I'm stuck.

Are there some tools I can use to achieve my goal?

Thank you

As I understand it, the private key is represented by a byte array written in HEX format from line 19 to line 23. I now want to convert this byte array into a raw string.

Okay, but you will need the bytes concatenated from your public key and your private key in order to make a SecKey. Take a look at RFC 5480 for all of the details. Converted to Swift would look something like:

/*
 [1] BIT STRING {
    04 CD BB 86 D3 2C 4D 08 11 6A A4 D3 38 4E AE 1B
    DF E3 EC E5 D4 6F 03 0D CF 39 CD 79 3C 2E E6 74
    FA 93 54 10 F2 10 41 92 78 57 7D 87 72 55 F3 30
    BE F4 CA 6F F0 89 55 24 B8 B6 84 89 9C 4A 08 B5
    1A
 }

 OCTET STRING {
    B8 89 CA 8C 12 AB AF 74 9E CA 11 D4 D8 36 B8 28
    D1 99 4F 8D B3 72 52 49 3D 85 44 02 08 05 64 1E
 }
 */

let privateKeyBytes = [UInt8]([0x04, 0xCD, 0xBB, 0x86, 0xD3, 0x2C, 0x4D, 0x08,
                               0x11, 0x6A, 0xA4, 0xD3, 0x38, 0x4E, 0xAE, 0x1B,
                               0xDF, 0xE3, 0xEC, 0xE5, 0xD4, 0x6F, 0x03, 0x0D,
                               0xCF, 0x39, 0xCD, 0x79, 0x3C, 0x2E, 0xE6, 0x74,
                               0xFA, 0x93, 0x54, 0x10, 0xF2, 0x10, 0x41, 0x92,
                               0x78, 0x57, 0x7D, 0x87, 0x72, 0x55, 0xF3, 0x30,
                               0xBE, 0xF4, 0xCA, 0x6F, 0xF0, 0x89, 0x55, 0x24,
                               0xB8, 0xB6, 0x84, 0x89, 0x9C, 0x4A, 0x08, 0xB5,
                               0x1A, 0xB8, 0x89, 0xCA, 0x8C, 0x12, 0xAB, 0xAF,
                               0x74, 0x9E, 0xCA, 0x11, 0xD4, 0xD8, 0x36, 0xB8,
                               0x28, 0xD1, 0x99, 0x4F, 0x8D, 0xB3, 0x72, 0x52,
                               0x49, 0x3D, 0x85, 0x44, 0x02, 0x08, 0x05, 0x64, 0x1E])


let attributes = [kSecAttrKeyType: kSecAttrKeyTypeEC,
                  kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]


var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateWithData(Data(privateKeyBytes) as CFData,
                                        attributes as CFDictionary,
                                        &error) else {
    fatalError("Could not create private key")
}

var publicKey: SecKey = SecKeyCopyPublicKey(privateKey)!

print("Private Key: \(privateKey)")
print("Public Key: \(publicKey)")

Now, regarding:

I now want to convert this byte array into a raw string. IMHO, a good starting point would be to determine the encoding of the original text these bytes come from... and that's where I'm stuck.

These bytes are coming from an ASN.1 structure, as you have mentioned. So reversing this would be starting as a p8 PEM with a base64 encoded wrapper -> binary ASN.1 structure -> raw bytes. Then dumpasn1 converted the raw bytes into a readable ASN.1 structure. So, what are you looking to do with a raw string here?

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Thank you for your answer.

Regarding your code, I don't know anything to Swift. I tried to execute it on an online playground. Some errors showed, for example:

error: use of unresolved identifier 'kSecAttrKeyType' let attributes = [kSecAttrKeyType: kSecAttrKeyTypeEC,

Are some imports required?

So, what are you looking to do with a raw string here?

I'm trying to use an Apex method called Crypto.sign() whose last param, privateKey, is essentially a representation of a string ("Blob" type). That's why I want to extract the private key info from my p8 APNs file, which contain both a private and a public keys.

https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_classes_restful_crypto.htm#apex_System_Crypto_sign

I’m going to let Matt help you here but I wanted to answer a question you posed on your related thread, namely:

First, I'm not sure what the p8 format really is.

You can find an answer to that, and many other related questions, in my On Cryptographic Key Formats post.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Some errors showed, for example: error: use of unresolved identifier 'kSecAttrKeyType' let attributes = [kSecAttrKeyType: kSecAttrKeyTypeEC, Are some imports required?

I tried running this code in an iOS Playground and a macOS command line tool and an import was not required, but these constants are declared out in Security framework so you could try using import Security.

Regarding:

I'm trying to use an Apex method called Crypto.sign() whose last param, privateKey, is essentially a representation of a string ("Blob" type). That's why I want to extract the private key info from my p8 APNs file, which contain both a private and a public keys.

Okay, well I cannot help you with the Apex APIs here but you may want to take a look at the link Quinn added below regarding the Private Key formats and the possibilities of transferring a p8 between a PEM and a binary representation.

Also, on the Security API front, there is SecKeyCreateSignature which could take your private key extracted in the Swift code above and create a signature from it:

let plainText = "hello elliptic curve world"
guard let plainTextData = plainText.data(using: .utf8) else {
    fatalError("Could not create plaintext data")
}

let signature = SecKeyCreateSignature(
    privateKey,
    .ecdsaSignatureMessageX962SHA256,
    plainTextData as CFData,
    nil
)! as Data

let based64EncodedRawSignatureString = signature.base64EncodedString()
print("String based signature: \(String(describing: based64EncodedRawSignatureString))")

And there is also the CryptoKit route:

let plainText = "hello elliptic curve world"
guard let plainTextData = plainText.data(using: .utf8) else {
    fatalError("Could not create plaintext data")
}

let pemBasedPrivateKey = """
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
"""
let signer = try! P256.Signing.PrivateKey(pemRepresentation: pemBasedPrivateKey)
let signatureData = try! signer.signature(for: plainTextData)
let signature = signatureData.derRepresentation.base64EncodedString()
print("String based signature: \(signature)")
Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
How to extract the private key from a p8 APNs file?
 
 
Q