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!

Post not yet marked as solved Up vote post of dlew0000 Down vote post of dlew0000
380 views

Replies

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 and thanks for the quick reply! Sorry, I do use P256.Singing instead of P256.KeyAgreement, that was a typo on my part but it doesn't seem I can edit my post. And hardware encryption is kinda a requirement, so unfortunately Secure Enclave is not optional.

  • Hey @eskimo and thanks for the quick reply! That was a typo on my part. I am using P256.Signing instead of P256.KeyAgreement. As for Secure Enclave, hardware encryption is not optional unfortunately. (sorry if this message is a duplicate, I submitted the other one but don't see it 'pending review')

Add a Comment

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?