CryptoKit SecureEnclave private keys and extraction

Hi,


I've been playing a little with CryptoKit, and I'm confused by the fact that you can convert the SecureEnclave private keys to data and then create keys from that data. For example:


let signingKey = try! SecureEnclave.P256.Signing.PrivateKey()
print("Signing key data: \(signingKey.dataRepresentation.base64EncodedString())")


And then on another device you can "import" it:


let importedKey = Data(base64Encoded: "BIIBHzGCARswGgwCZWQxFDASDANhY2wxCzAJDARkYWNsAQEBMIH8DAJyazGB9TAHDAJiYwIBCjALDANiaWQEBOqZTJcwFwwDa2lkBBBFzglTIUNGbZ5MBD7cUr2wMAcMAmt0AgEEMAcMAmt2AgEBMEgMA3B1YgRBBKd1FjDccEJwDkCKtEQoUb321YA5NEwBobllMP/rkvRMXpy97YBKqQr3YtpHMU1plIVXkziI91r7+4afKYT2XGwwJwwDcmttBCBIJACijmlKv5GDMlHGzziP/EZF0MFj61ST5RaiksP4ITAPDANya28CCIAAAAAAAAAAMC4MAndrBCgw/AdDczvX7RShPFQqQJIavDny6KCMznht870F2fPph5NTh/UVZ83n")!
let importedSigningKey = try! SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: importedKey)
print("Imported signing key data: \(importedSigningKey.dataRepresentation.base64EncodedString())")


This prints the same dataRepresentation as on the original device. However it appears that you can't actually use those private keys for any signing or encryption operations.


If you do this on the same device and then use the key for signing, it works, items signed with both the original signing key and the copied signing key will validate correctly:


let signingKeyBase64 = signingKey.dataRepresentation.base64EncodedString()
let copiedKeyData = Data(base64Encoded: signingKeyBase64)!
let copiedKey = try! SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: copiedKeyData)
let copySignature = try! copiedKey.signature(for: "Hello, world!".data(using: .utf8)!)

assert(copiedKey.publicKey.isValidSignature(copySignature, for: "Hello, world!".data(using: .utf8)!))
assert(signingKey.publicKey.isValidSignature(copySignature, for: "Hello, world!".data(using: .utf8)!))


I'm guessing what's really happening is that the data representation is some cunning reference to a SecureEnclave key which is being stored in the keychain, and "importing" that data just binds it in some fashion to that existing key. It fails on the other device because there is not a private key with that encoded reference.


Have I missed some documentation that covers all this? What's the purpose of the data representation here - is that something that could be stored as a value in, say, a shared keychain for use between apps? And can someone verify that SecureEnclave private keys remain un-extractable?



Thanks,

Dave

Accepted Reply

Along with iOS 13 Beta 5, the CryptoKit developers have posted a documentation update that addresses this:


https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain


About 25% into the document, they note:


"Keys that you store in the Secure Enclave expose a raw representation as well, but in this case the data isn’t the raw key. Instead, the Secure Enclave exports an encrypted block that only the same Secure Enclave can later use to restore the key."


So, as I suspected, the .data represents a wrapped object with the real private key (and presumably use / authorization policy) encrypted by a wrapping key unique to the individual hardware secure element.

Replies

They have not documented this that I'm aware of, but presumably what's acutally happening is referred to as "key wrapping".


The "data representation" of the private key is almost certainly a blob which includes both policy data (key use restrictions, signing or key exchange, auth requirements, etc) AND the actual private key. These would then be encrypted (or "wrapped") by an actual hardware bound key which is unique to each internal secure element. (This is why it won't work on another device -- the key to unwrap the data representation would be a different key (incorrect for this data representation) on that other secure element).


This is like a virtual HSM, in a sense. It is unlikely the SecureEnclave actually has an internal keychain that stores all these developer requested keys, as Secure Elements generally have quite limited storage space. Thus, I posit that the SecureEnclave derived keys are wrapped and unwrapped so that the developer generating keys can store the data representation (even insecurely) without compromising the actual key. The SecureEnclave API is just unwrapping the key inside the secure environment when needed to perform operations with the key.


If your question is how to extract the actual raw private key or do a backup of the SecureEnclave generated key that can be used on another device, I belive there's not a way to do that and that they designed it that way on purpose.

Along with iOS 13 Beta 5, the CryptoKit developers have posted a documentation update that addresses this:


https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain


About 25% into the document, they note:


"Keys that you store in the Secure Enclave expose a raw representation as well, but in this case the data isn’t the raw key. Instead, the Secure Enclave exports an encrypted block that only the same Secure Enclave can later use to restore the key."


So, as I suspected, the .data represents a wrapped object with the real private key (and presumably use / authorization policy) encrypted by a wrapping key unique to the individual hardware secure element.

Thanks!