CryptoKit to create JWT

Hi,


I would like to create a JWT to interact with Apple's Webservices.


As the first step, I have a private key file, I have loaded it as Data.


I would like to create an instance of Private Key instance (not sure what is the correct struct to use, tried HMAC, P256.Signing.PrivateKey etc)


Every time an exception is thrown.


Any help and direction would greatly help. Many thanks.


let privateKeyPath = URL(fileURLWithPath: "some valid path")
let privateKeyData = try Data(contentsOf: privateKeyPath, options: .alwaysMapped)

let message = "abcd"
let messageData = message.data(using: .utf8)!

do {
    //Option1:
    //This throws: Error: incorrectParameterSize
    let signature1 = try P256.Signing.ECDSASignature(rawRepresentation: privateKeyData)

    //Option2:
    
    let key2 = SymmetricKey(data: privateKeyData)

    //This throws: Error: incorrectKeySize
    var encryptedContent2 = try AES.GCM.seal(messageData, using: key2).combined
}
catch {
    print("Error: \(error)")
}

Accepted Reply

The trick here is to understand the various representations of an EC key. When you create an API key for Apple web service (like the App Store Connect API), you get back a

.p8
file, which a PEM-encoded PKCS#8 private key:
% cat AuthKey_94H2XDKFA3.p8 
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQguInKjBKrr3SeyhHU
2Da4KNGZT42zclJJPYVEAggFZB6gCgYIKoZIzj0DAQehRANCAATNu4bTLE0IEWqk
0zhOrhvf4+zl1G8DDc85zXk8LuZ0+pNUEPIQQZJ4V32HclXzML70ym/wiVUkuLaE
iZxKCLUa
-----END PRIVATE KEY-----

IMPORTANT This is a real key generated using App Store Connect, but it has been revoked.

Now extract the Base64 data into a file

key.b64
and use
base64
to decode it:
% base64 -D < key.b64 > key.asn1

You can then dump the key using

dumpasn1
:
1 % dumpasn1 -p key.asn1
 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   }

The format of this key is complex, but you should pay attention to the following:

  • Line 5 tells you that it’s an elliptic curve (EC) key.

  • Line 6 tells you the specific type of EC, namely prime 256 random 1 (

    dumpasn1
    shows
    prime256v1
    , which RFC 5440 lists as a legacy name for
    secp256r1
    ).
  • Lines 12…13 represent the key’s K value. This is essentially the private key bytes.

  • Lines 19…23 represent the key’s X || Y value, prefixed by an 04. This is essentially the public key bytes.

Apple CryptoKey represents a prime 256 random 1 key as the

P256
type. The public and private keys in that type have two properties,
rawRepresentation
and
x963Representation
[1]. If you successfully imported the above mentioned key and printed those properties, here’s what you get:
public raw: cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51a
public x9.63: 04cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51a

private raw: b889ca8c12abaf749eca11d4d836b828d1994f8db37252493d8544020805641e
private x9.63: 04cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51ab889ca8c12abaf749eca11d4d836b828d1994f8db37252493d8544020805641e

So, here’s what’s going on here:

  • The

    rawRepresentation
    for the public key is just X || Y.
  • The

    x963Representation
    for the public key is 04 || X || Y.
  • The

    rawRepresentation
    for the private key is just K.
  • The

    x963Representation
    for the private key is 04 || X || Y || K.

You can use the corresponding initialisers to create a key. For example, to create a key from the K value above:

let keyK = Data([
    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 privateKey = try P256.Signing.PrivateKey(rawRepresentation: keyK)

Share and Enjoy

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

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

[1] There’s also

compactRepresentation
, but it returns
nil
.

Replies

The trick here is to understand the various representations of an EC key. When you create an API key for Apple web service (like the App Store Connect API), you get back a

.p8
file, which a PEM-encoded PKCS#8 private key:
% cat AuthKey_94H2XDKFA3.p8 
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQguInKjBKrr3SeyhHU
2Da4KNGZT42zclJJPYVEAggFZB6gCgYIKoZIzj0DAQehRANCAATNu4bTLE0IEWqk
0zhOrhvf4+zl1G8DDc85zXk8LuZ0+pNUEPIQQZJ4V32HclXzML70ym/wiVUkuLaE
iZxKCLUa
-----END PRIVATE KEY-----

IMPORTANT This is a real key generated using App Store Connect, but it has been revoked.

Now extract the Base64 data into a file

key.b64
and use
base64
to decode it:
% base64 -D < key.b64 > key.asn1

You can then dump the key using

dumpasn1
:
1 % dumpasn1 -p key.asn1
 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   }

The format of this key is complex, but you should pay attention to the following:

  • Line 5 tells you that it’s an elliptic curve (EC) key.

  • Line 6 tells you the specific type of EC, namely prime 256 random 1 (

    dumpasn1
    shows
    prime256v1
    , which RFC 5440 lists as a legacy name for
    secp256r1
    ).
  • Lines 12…13 represent the key’s K value. This is essentially the private key bytes.

  • Lines 19…23 represent the key’s X || Y value, prefixed by an 04. This is essentially the public key bytes.

Apple CryptoKey represents a prime 256 random 1 key as the

P256
type. The public and private keys in that type have two properties,
rawRepresentation
and
x963Representation
[1]. If you successfully imported the above mentioned key and printed those properties, here’s what you get:
public raw: cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51a
public x9.63: 04cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51a

private raw: b889ca8c12abaf749eca11d4d836b828d1994f8db37252493d8544020805641e
private x9.63: 04cdbb86d32c4d08116aa4d3384eae1bdfe3ece5d46f030dcf39cd793c2ee674fa935410f210419278577d877255f330bef4ca6ff0895524b8b684899c4a08b51ab889ca8c12abaf749eca11d4d836b828d1994f8db37252493d8544020805641e

So, here’s what’s going on here:

  • The

    rawRepresentation
    for the public key is just X || Y.
  • The

    x963Representation
    for the public key is 04 || X || Y.
  • The

    rawRepresentation
    for the private key is just K.
  • The

    x963Representation
    for the private key is 04 || X || Y || K.

You can use the corresponding initialisers to create a key. For example, to create a key from the K value above:

let keyK = Data([
    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 privateKey = try P256.Signing.PrivateKey(rawRepresentation: keyK)

Share and Enjoy

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

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

[1] There’s also

compactRepresentation
, but it returns
nil
.

Wow !!! Thanks a ton !!! Amazing explanation.


I was breaking my head over it for 2 days, and had almost given up .... thank you so much !!


Doubts:


1. While compiling dumpasn1.c I encountered a compilation error in the 14th line shown below:


if( i >= 4 && \
item->header[ 0 ] == 0x30 || item->header[ 0 ] == 0x31 )


Error:

dumpasn1.c:3020:14: note: place parentheses around the '&&' expression to silence this warning
                if( i >= 4 && \
                           ^
                    (


I wasn't sure what the logic needed to be, but I changed it as follows:

if( i >= 4 && \
  (item->header[ 0 ] == 0x30 || item->header[ 0 ] == 0x31) )


Is this correct or should the parantheses be else where ?


2. You had mentioned the following:


The

x963Representation
for the public key is 04 || X || Y || K.


Just wondering if the above corresponded to the private key instead of the public key ? (Since public key was mentioned 3 times and private key was mentioned once)

Is this correct or should the parantheses be else where ?

That looks good to me, but it’s not my code. I recommend that you confirm this with the author.

Just wondering if the above corresponded to the private key instead of the public key ?

Yes. My bad. I went back and fixed my post lest it confuse Future Developers™ [1].

Share and Enjoy

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

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

[1] One of the reasons I post stuff here is so that I can find it easily later, so Future Developers™ may turn out to be Future Quinn™.

Thank you so much for such a detailed explanation.


I managed to generate JWT and it works with Apple's Web Service, however the JWT that I generated was different from the one that was generated by another library.



My JWT: AAA.BBB.CCC

Other JWT: AAA.BBB.DDD


The JSON header and payload were the same and both JWT works but they are both different.


AAA is the header

BBB is the payload


CCC and DDD are the signatures.


I get the feeling I am signing differently from the library. Just wondering if there are multiple P256 versions or some attributes of the signing that are different.


Is this possible or am I missing something ?

This is expected. Apple’s web services use an EC key, and EC signing includes a random component. Thus, the signature will by different for every token.

Share and Enjoy

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

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

That explains it, thank you so much, I have learned so much !!

Great info, thanks. How do we get the info out of a p12 for signing?

How do we get the info out of a p12 for signing?

If you start a new thread, I’ll try to answer there. Make sure to tag it with Security.

Share and Enjoy

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