Unable to create a SecKey from EC private key

Hi, I am trying to implement encryption and decryption with EC signing keys. in the process I am getting the following error while creating a SecKey from the private key.

Error in creating a secKey Optional(Swift.Unmanaged<__C.CFErrorRef>(_value: Error Domain=NSOSStatusErrorDomain Code=-50 "EC private key creation from data failed" (paramErr: error in user parameter list) UserInfo={numberOfErrorsDeep=0, NSDescription=EC private key creation from data failed}))

Code snippet for decryption

func decrypt(data: Data, key: SecureEnclave.P256.Signing.PrivateKey) throws -> Data? {

    var error: Unmanaged<CFError>?

    let privateKeyData: CFData = key.dataRepresentation as CFData

    let privateKeyAttributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,

                                kSecAttrKeyClass: kSecAttrKeyClassPrivate] as CFDictionary

    guard let SecKey = SecKeyCreateWithData(privateKeyData, privateKeyAttributes as CFDictionary, &error)

    else {

        print("Error in creating a secKey", error)

        return nil

    }

    

    guard SecKeyIsAlgorithmSupported(SecKey, .decrypt, EncryptAndDecryptAlogrithm)

    else {

        print("Decryption algorithm is not supported", error)

        return nil

    }

    

    guard let decryptedData = SecKeyCreateDecryptedData(SecKey, EncryptAndDecryptAlogrithm, data as CFData, &error) else {
        print("Error in decryption", error)
        return nil
    }

    return decryptedData as Data

}

let data = Data(base64Encoded: "BNtHrb1cZuflSDZz+E3PnIkLtYUQuBDW+ONlzuAypZcQa+5oKv0L0wSIBMMseMr0roloexPwTaVV26ddewTP0+vRt9v6uLOg366cElMo6P5nh2K7xKi1PMcRyBVel+Kq9WQWT/EkRIuUkHdq2KLXy/Q=")!

let alice = try SecureEnclave.P256.Signing.PrivateKey()

let decryptedData = try decrypt(data: data, key:alice)

Thank you in advance.

Replies

in the process I am getting the following error while creating a SecKey from the private key.

Why are you trying to do this?

You seem to be mixing CryptoKit and the older SecKey API, which can be done but it’s a little weird. The code you posted generates a new random CryptoKit key:

let alice = try SecureEnclave.P256.Signing.PrivateKey()

and then tries to convert that key to a SecKey. It’d be easier to just create the SecKey directly by calling SecKeyCreateRandomKey.

Share and Enjoy

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

I am actually using the SecureEnclave.P256.Signing.PrivateKey for signing and then for the encryption and decryption, here I just posted the code where the error occurs while decryption. Will you please suggest me the correct approach to implement signing, verification, encryption and decryption with SecureEnclave's Private key?

Here’s a snippet showing how to sign and verify with CryptoKit:

let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey

// Sign with the private key.

let message = Data("Hello Cruel World!".utf8)
let signature = try privateKey.signature(for: message)

// Verify with the public key.

let isMessasgeValid = publicKey.isValidSignature(signature, for: message)
print(isMessasgeValid)      // true
let badMessage = Data("Hello World!".utf8)
let isBadMessageValid = publicKey.isValidSignature(signature, for: badMessage)
print(isBadMessageValid)    // false

I’m using P256 but SecureEnclave.P256 works the same way.


CryptoKit does not support ECC encryption directly. Rather you use two keys, your private key and the remote peer’s public key, to derived a shared secret that you use as a one-time-use key for a symmetric encryption algorithm (like AES.GCM). For example:

let alicePrivateKey = P256.KeyAgreement.PrivateKey()
let alicePublicKey = alicePrivateKey.publicKey

let bobPrivateKey = P256.KeyAgreement.PrivateKey()
let bobPublicKey = bobPrivateKey.publicKey

let sharedInfo = Data("Hello Cruel World!".utf8)

let aliceShared = try alicePrivateKey.sharedSecretFromKeyAgreement(with: bobPublicKey)
let aliceKey = aliceShared.x963DerivedSymmetricKey(using: SHA256.self, sharedInfo: sharedInfo, outputByteCount: 32)
print((aliceKey.withUnsafeBytes { Data($0) } as NSData).debugDescription)

let bobShared = try bobPrivateKey.sharedSecretFromKeyAgreement(with: alicePublicKey)
let bobKey = bobShared.x963DerivedSymmetricKey(using: SHA256.self, sharedInfo: sharedInfo, outputByteCount: 32)
print((bobKey.withUnsafeBytes { Data($0) } as NSData).debugDescription)

Here both print(…) calls print the same key value, showing that Alice has agreed on a one-time-use key with Bob based solely on Bob’s public key and a not-secret shared info, and vice versa for Bob.

Share and Enjoy

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

Why are you trying to do this?

I'm trying to do something similar. My ultimate goal is to interface the C-level SecKey API with some Go code, but I'm trying to validate that I'm doing things correctly by round-tripping some simple sign and verify tasks with CryptoKit and Secure Enclave keys.

You seem to be mixing CryptoKit and the older SecKey API, which can be done but it’s a little weird.

What would it look like? I'm getting the dataRepresentation from a SecureEnclave.P256.Signing.PrivateKey, then trying to recreate it for use by the SecKey APIs with this:

bool verify(const UInt8 *privateKey, size_t privateKeyLength, const UInt8 *data, size_t dataLength, const UInt8 *signature, size_t signatureLength) {
    CFDataRef keyData = CFDataCreate(NULL, privateKey, privateKeyLength);
    void *attributeKeys[] = {
        (void *)kSecAttrKeyType,
        (void *)kSecAttrKeyClass,
        (void *)kSecAttrTokenID
    };
    void *attributeValues[] = {
        (void *)kSecAttrKeyTypeEC,
        (void *)kSecAttrKeyClassPrivate,
        (void *)kSecAttrTokenIDSecureEnclave
    };
    CFDictionaryRef attributes = CFDictionaryCreate(NULL, (const void **)attributeKeys, (const void **)attributeValues, sizeof(attributeKeys) / sizeof(attributeKeys[0]), NULL, NULL);

    CFErrorRef error;
    SecKeyRef privateKeyRef = SecKeyCreateWithData(keyData, attributes, &error);

    // ...
}

This "works" insofar as I am not getting any errors, but signature validation always fails.

Much to my surprise this actually works:

// Use CryptoTokenKit to create a private key protected by the SE.

let privateKey = try SecureEnclave.P256.Signing.PrivateKey()

// Create `SecKey` from that.

let rep = privateKey.dataRepresentation
let privateKeyRef = try secCall { SecKeyCreateWithData(rep as NSData, [
    kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass: kSecAttrKeyClassPrivate,
    kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
] as NSDictionary, $0) }

// Sign some data.

var dataToSign = Data("Hello Cruel World!".utf8)
let signature = try secCall { SecKeyCreateSignature(privateKeyRef, .ecdsaSignatureMessageX962SHA256, dataToSign as NSData, $0) }

// Check that the original data verifies and some corrupted data does not.

let publicKeyRef = try secCall { SecKeyCopyPublicKey(privateKeyRef) }
let isValidOK = SecKeyVerifySignature(publicKeyRef, .ecdsaSignatureMessageX962SHA256, dataToSign as NSData, signature as NSData, nil)
print("isValidOK: \(isValidOK)")
dataToSign[0] = UInt8(ascii: "h")
let isValidNG = SecKeyVerifySignature(publicKeyRef, .ecdsaSignatureMessageX962SHA256, dataToSign as NSData, signature as NSData, nil)
print("isValidNG: \(isValidNG)")

On iOS 16.1 it prints:

isValidOK: true
isValidNG: false

Note I’m using the helpers from Calling Security Framework from Swift here.

However, this seems very roundabout. If you want to create an ephemeral SE-protected SecKey object, just do this:

let privateKeyRef = try secCall { SecKeyCreateRandomKey([
    kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeySizeInBits: 256,
    kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
] as NSDictionary, $0) }

Share and Enjoy

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

I'd hate to throw a spanner into the works here, but while this method creates a working Secure Enclave key, it's actually a different one:

let securityKeyData = try secCall { SecKeyCopyExternalRepresentation(publicKeyRef, $0) }
print(securityKeyData.hexEncodedString())
let cryptoKitKeyData = privateKey.publicKey.x963Representation
print(cryptoKitKeyData.hexEncodedString())

prints for instance:

04a14dc571ce3d1645304811794cc7b18aed4d3dfb07c5f40c0dab7efa109c2c0ebb3aa374ec385bb3864081a43b068f06577573cd06f36563ea0981ddb73f5a86
048698990ab597d7775652620bb6156f07cf8e65288f915e30ac4fcf051916ab0a14438b4cfc46322ef3676b0280feeec9799b4f82bc923de86d189e911b30c333

while one would hope that both outputs were equal.

Indeed. This came up just now on another thread.

Sorry I didn’t reply to your earlier post; it was hidden by over-enthusiastic moderation )-:

Share and Enjoy

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