My end goal is to use eciesEncryptionCofactorX963SHA256AESGCM
with a key generated on the Secure Enclave using CryptoKit, that requires Biometric Authentication.
CryptoKit does not implement the ECIES encryption algorithms, so my goal was to fall back to the Security framework.
The public key can be easily converted to a SecKey because it implements x963Representation
which can then be imported as follows:
let enclaveSecKey: SecKey = SecKeyCreateWithData(enclaveKey.x963Representation as CFData, [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: 256
] as [String: Any] as CFDictionary, nil),
I have everything working except the code to decrypt with the private key. Naturally, the Secure Enclave does not expose the private key - as is its design - rather some kind of token?
I did read the Keychain documentation which notes that it is not possible to simply obtain an x963Representation
of the private key (as it's a custom representation returned by the Secure Enclave).
However, my ultimate question is this: can one convert the Secure Enclave representation into something that can be used as a SecKey for encryption/decryption (without necessarily being stored in the Keychain - i.e., 'correct') as it seems both CryptoKit and Security have a means of representing the private key token returned by the Secure Enclave? (Or is one's only recourse to use the Security framework for generating and storing the keys too?)
I have also tried this code to create a SecKey representation, having retrieved the GenericPasswordConvertible
out of the keychain (note the use of kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
) with the aforementioned goal of loading the Secure Enclave's private token as a SecKey:
let enclaveSecKey: SecKey = SecKeyCreateWithData(enclaveKey.rawRepresentation as CFData, [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecAttrAccessible: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecUseAuthenticationContext: try await createAuthContext(
reason: "Decrypt data",
fallbackTitle: "Enter your device password to decrypt data",
mustEvaluate: true
),
kSecAttrIsPermanent: true,
kSecAttrIsExtractable: false,
kSecAttrSynchronizable: false,
kSecAttrKeySizeInBits: 256,
kSecAttrAccessControl: SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.biometryAny, .privateKeyUsage],
&cfSecKeyCreateError
)!
] as [String: Any] as CFDictionary, nil)
This works, in and of itself, (i.e., it loads without error and cfSecKeyCreateError
is nil, however when I try SecSecKeyCopyPublicKey
I get a different, incorrect public key and - naturally, I suppose - if I attempt to decrypt data with the private key that fails with:
Optional(Swift.Unmanaged<__C.CFErrorRef>(_value: Error Domain=NSOSStatusErrorDomain Code=-50 "ECIES: Failed to aes-gcm decrypt data (err -69)" UserInfo={numberOfErrorsDeep=0, NSDescription=ECIES: Failed to aes-gcm decrypt data (err -69)}))
can one convert the Secure Enclave representation into something that can be used as a SecKey for encryption/decryption
The following Works on My Machine™:
let ck = try SecureEnclave.P256.Signing.PrivateKey()
let sf = try secCall { SecKeyCreateWithData(ck.dataRepresentation as NSData, [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
] as NSDictionary, $0) }
print(sf)
// <SecKeyRef:('com.apple.setoken') 0x60000266ed40>
Oh, that’s macOS 13.2.1 btw, but it should work on any SE-capable device.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"