Signature Creation with PrivateKey().signature(for:) vs SecKeyCreateSignature

Quick Summary

I'm having trouble using SecKeyCreateSignature(deviceSigningKeyRef, .ecdsaSignatureMessageX962SHA256, digest, &error) but when using SecureEnclave.P256.KeyAgreement.PrivateKey().signature(for: digest) the other code I'm using to verify succeeds.

Full use case and code

If I just initiate a SecureEnclave.P256.KeyAgreement.PrivateKey() class variable and then later use signature(for: digest).rawRepresentation to generate a signature, I get a signature value that can be passed to the verifying code

class MyClass {
   var myPrivateKey: SecureEnclave.P256.KeyAgreement.PrivateKey?

   init() {
      myPrivateKey = SecureEnclave.P256.KeyAgreement.PrivateKey()
      let myPublicKey = myPrivateKey?.publicKey.rawRepresentation
   }
   func createAndSendSignature(_ digest: Data) {
      let signature = try? myPrivateKey?.signature(for: digest).rawRepresentation // 64 bytes
      sendSignatureWithDigest(signature, digest)
   }

}

But if I create my key in keychain via Secure Enclave with the way the documentation recommends (here's a few links to start Signing/Verifying, Keys for encryption), and then retrieve the key representation and use SecKeyCreateSignature, the resulting signature (which I manipulate a little more because it is DER encoded and does not comes back as 64 bytes) fails against the verifying code.

class MyClass {
   var myKeyTag: String = "myKeyTag"
   func createAndStoreKey() {
      let access = SecAccessControlCreateWithFlags(
                kCFAllocatorDefault,
                kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                .privateKeyUsage,
                nil)! // Ignore errors.
            
            let attributes: NSDictionary = [
                kSecClass as String: kSecClassKey,
                kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
                kSecAttrKeySizeInBits as String: 256,
                kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
                kSecPrivateKeyAttrs as String: [
                    kSecAttrIsPermanent as String: true,
                    kSecAttrApplicationTag as String: myKeyTag,
                    kSecAttrAccessControl as String: access,
                    kSecAttrCanSign as String: true,
                ]
            ]

            var error: Unmanaged<CFError>?

            guard let keyRef: SecKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
                throw error!.takeRetainedValue() as Error
            }
      return keyRef as SecKey!
   }
   func getKey(){
      let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: myKeyTag,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecReturnRef as String: true,
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess else {
            throw KeyStoreError("Unable to retrieve key: \(status.message)")
        }
        return (item as! SecKey)
   }
   func createAndSendSignature(_ digest: Data) {
      let privKey = getKey()
      let signature = SecKeyCreateSignature(
                privKey,
                .ecdsaSignatureMessageX962SHA256,
                digest as CFData,
                &error) as Data? else {
                    print(error)
                    return
            } // bytes varry due to DER encoding and R and S values
      let ecdsaSignature = try P256.Signing.ECDSASignature(derRepresentation: signature)
      let signatureBytes = ecdsaSignature.rawRepresentation
      sendSignatureWithDigest(signatureBytes, digest)
   }

}

An important note: digest is not an actual digest but a message that needs to be hashed to turn into a digest? Sorry if that sounds off, my security knowledge is limited.

Please forgive any syntax errors, I can't copy and paste the code and am just extracting the important elements.

Anything helps, thanks!

As a first step, there’s an algorithm mismatch here:

  • SecureEnclave.P256.KeyAgreement.PrivateKey is a key agreement algorithm.

  • .ecdsaSignatureMessageX962SHA256 is a signature algorithm.

I’m not sure if that actually matters in practice, but I recommend that you switch your CryptoKit code to the SecureEnclave.P256.Signing algorithm to bring things in to sync. It’ll be interesting to see whether your server is still able to verify the CryptoKit signature in that case.

Beyond that, I recommend that, during this bring-up phase, you switch to use keys that are not protected by the Secure Enclave. That simplifies the whole process.

Share and Enjoy

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

Hey @eskimo I've read a bunch of your other answers on security related questions here and you seem very knowledgeable about this stuff, so I was wondering if you have any thoughts on what's going on or how to fix it?

In the meantime, I've used the following approach but not sure if this is best practice or secure (ignoring 100% perfect syntax and error handling):

   do {
            let privateDeviceKey = try SecureEnclave.P256.Signing.PrivateKey()
            let pubKey = privateDeviceKey.publicKey.rawRepresentation
            let query = [kSecClass: kSecClassGenericPassword,
                     kSecAttrAccount: tagPrivateDeviceSigningKey,
                     kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
                     kSecUseDataProtectionKeychain: true,
                     kSecValueData: privateDeviceKey.dataRepresentation] as [String: Any]

             // Add the key data.
             let status = SecItemAdd(query as CFDictionary, nil)
            result(pubKey as Data)
        } catch {
            ...
        }

...
      let query = [kSecClass: kSecClassGenericPassword,
                     kSecAttrAccount: tagPrivateDeviceSigningKey,
                     kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
                     kSecUseDataProtectionKeychain: true,
                     kSecReturnData: true] as [String: Any]

        // Find and cast the result as data.
       let deviceSigningKeyData: Data
        var item: CFTypeRef?
        switch SecItemCopyMatching(query as CFDictionary, &item) {
        case errSecSuccess:
            guard let data = item as? Data else { return nil }
            deviceSigningKeyData = data

          let secDeviceSigningKey = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: deviceSigningKeyData ?? Data(0))
          let signature = try? secDeviceSigningKey.signature(for: digest).rawRepresentation

Thoughts?

Signature Creation with PrivateKey().signature(for:) vs SecKeyCreateSignature
 
 
Q