SecKeyCreateDecryptedDataWithParameters always fails with algo not supported

Attempting to DECRYPT a cipher message using the Apple API SecKeyCreateDecryptedData(privateKey, .rsaEncryptionOAEPSHA256, encryptedMessage). Decryption ALWAYS fails for every algorithm.

SecKeyCreateDecryptedDataWithParameters  Error: `Domain=NSOSStatusErrorDomain Code=-50 "algid:encrypt:RSA:OAEP:SHA256: algorithm not supported by the key <SecKeyRef:('com.yubico.Authenticator.TokenExtension:5621CDF8560D4C412030886584EC4C9E394CC376DD9738B0CCBB51924FC26EB6') 0x3007fd150>" UserInfo={numberOfErrorsDeep=0, NSDescription=algid:encrypt:RSA:OAEP:SHA256: algorithm not supported by the key <SecKeyRef:('com.yubico.Authenticator.TokenExtension:5621CDF8560D4C412030886584EC4C9E394CC376DD9738B0CCBB51924FC26EB6') 0x3007fd150>}`

Decryption failed: SecKeyCreateDecryptedData returned nil.
Error: One or more parameters passed to a function were not valid.

When checking with SecKeyIsAlgorithmSupported(privateKey, .decrypt, <ANYalgorithm>) all algorithms fail. Btw - The privateKey does support decryption when retrieving the attributes.

Important to know:

The private key is a reference to an external private key placed in the iOS Keychain via a 3rd party CryptoTokenKit Extension app. When I perform, the SecKeyCreateSignature(...) and pass in the SAME privateKey reference, the OS automatically calls the 3rd party app to perform a successful signing with the private key that reside on a YubiKey.

Here's my code for obtaining the private key reference from an Identity:

func getKeyPairFromIdentity() -> (privateKey: SecKey, publicKey: SecKey)? {
        let query = NSDictionary(
            dictionary: [
                kSecClass as String: kSecClassIdentity,
                kSecAttrTokenID as String: self.tokenID!,
                kSecReturnRef as String: kCFBooleanTrue as Any
            ]
        )
        
        var identityRef: CFTypeRef?
        let status = SecItemCopyMatching(query, &identityRef)
        
        if status == errSecSuccess, let identity = identityRef {
            var privateKeyRef: SecKey?
            let keyStatus = SecIdentityCopyPrivateKey(identity as! SecIdentity, &privateKeyRef)
            
            if keyStatus == errSecSuccess, let privateKey = privateKeyRef {
                let publicKey = SecKeyCopyPublicKey(privateKey)
                if let publicKey = publicKey {
                    print("Private and public keys extracted successfully.")
                    return (privateKey, publicKey)
                } else {
                    print("Failed to extract public key from private key.")
                    return nil
                }
            } else {
                print("SecIdentityCopyPrivateKey: Private key not found error: \(keyStatus)")
                return nil
            }
        } else {
            print("SecIdentity not found or error: \(status)")
            return nil
        }
    }

Answered by DTS Engineer in 836398022

Thanks for all the background info.

Here's the attributes of the private key reference

That’s interesting. Note the presence of sign and decr, both with a value of 1. Those correspond to kSecAttrCanSign and kSecAttrCanDecrypt, which is the key indicating that it supports both signing and decryption.

If this were a normal key then it’d be the end of the story. However, for the case of a CTK-backed key then the actual work is done by the CTK appex. Whether a specific operation type or algorithm is supported or not depends on the appex. Given that you have access to the appex’s source code, I can explain the mechanics of that and then you can look up in the code to see what it does in that case.

Every CTK appex include a TKTokenSession subclass [1]. That subclass typically acts as its own delegate. The delegate has to implement the tokenSession(_:supports:keyObjectID:algorithm:) method. This determines what algorithms it supports. And, based on that, it then needs to support one or more of the following:

Finally, keep in mind that a CTK appex associated with hardware will typically bundle up the crypto operation and pass it along to the hardware. That’s whole point of having a hardware token, in that the private key never actually leaves the hardware.

Different hardware places different limits on what operations a particular key can perform. And the hardware might support multiple keys, each with its own set of supported operations. For example, a PIV smart card typically supports signing in slot 9C and decryption in slot 9D.

If the hardware has such a limit then there’s nothing that the CTK appex can do about it. The best you can hope for is for it to set kSecAttrCanSign and kSecAttrCanDecrypt correctly.

Share and Enjoy

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

[1] A smart card token actually subclasses TKSmartCardTokenSession, which is itself a subclass of TKTokenSession. You’re using a virtual token, so I’m gonna focus on that.

[2] One day we’ll update the docs to make the exact mechanics of that clear (r. 90838636).

My go-to mechanism for testing this stuff is to derive the public key from the private key (SecKeyCopyPublicKey) and then run a local encrypt-then-decrypt test. That helps isolate problems with the data format from problems with the key. That is, if a private key can’t decrypt data that was encrypted with the public key, something is borked with the key.

I suspect that you’ve already done thus but, if not, please do.

Assuming that the above test fails, that brings us to this:

The private key is a reference to an external private key placed in the iOS Keychain via a 3rd party CryptoTokenKit Extension app.

Is this your CTK appex? Or one from another third-party developer?

Share and Enjoy

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

Thanks for the response. I should have clarified this earlier but we are using “persistent tokens” from a 3rd party Smart Card App Extension.

In the code attached, I do make use of SecKeyCopyPublicKey. I retrieve a SecIdentity based on the certificate and associated private key (pointer) using SecItemCopyMatching from querying the keychain. I then use SecIdentityCopyPrivateKey to get the private key and then SecKeyCopyPublicKey to retrieve the public key. I use that public key to encrypt the message using SecKeyCreateEncryptedData. I then pass the private key reference and algorithm used to the decryption API SecKeyCreateDecryptedData.

As for the Smart Card Token App Extension…We are using Yubico Authenticator (YA) Apple App Store application that acts as the 3rd party Smart Card App Extension. I’m building a solution to provide encryption/decryption based on those tokens generated by the 3rd party CTK Extension. The YA app, as a token extension, adds persistent tokens to the keychain and are made available (visible) to any 3rd party apps by adding an entitlement of **Keychain Access Group: com.apple.token **

The encryption/decryption app solution is treating Yubico Authenticator app as a 3rd party app and not in my APEX, but I do have access to the source code of the Yubico Authenticator Token Extension to make changes, if needed.

BTW - The solution works great when calling SecKeyCreateSignature and passing the SAME private key reference from my app. Calling SecKeyCreateSignature immediately notifies the user to interact with the Yubico Authenticator app (via local push notifications) to perform the signing with the private key residing on the YubiKey. The signed blob is returned to the calling app.

I'm 100% sure the private key is valid and capable of decryption because I can send the same private to for a signature but in the YA app, treat it like a decryption and the plain text is returned to the app. It's just not working with SecKeyCreateDecryptedData.

Here's the attributes of the private key reference when I specify the kSecReturnAttributes in a SecItemCopyMatching query.

// kSecReturnAttributes (true)

Private key found: {
    UUID = "47DC7D0B-C97A-4FE6-8C56-B53EAF30E848";
    accc = "<SecAccessControlRef: tkid(com.yubico.Authenticator.TokenExtension:972BC027C9E349CFA63856C2A2968F16XXXXX71564A94570EE131DEA92E9BB0F);od(true)>";
    agrp = "com.apple.token";
    atag = {length = 64, bytes = 0x39373242 43303237 43394533 34394346 ... 39324539 42423046 };
    bsiz = 2048;
    cdat = "2025-04-11 20:06:11 +0000";
    class = keys;
    crtr = 0;
    decr = 1;
    drve = 1;
    edat = "2001-01-01 00:00:00 +0000";
    esiz = 0;
    kcls = 1;
    klbl = {length = 20, bytes = 0x550a1dc9b2030d43d574af1e156db84dd1c2ab53};
    labl = 972BC027C9E349CFA63856C2A2968F16XXXXX71564A94570EE131DEA92E9BB0F;
    mdat = "2025-04-11 20:06:11 +0000";
    musr = {length = 0, bytes = 0x};
    pdmn = dk;
    priv = 1;
    sdat = "2001-01-01 00:00:00 +0000";
    sha1 = {length = 20, bytes = 0xda1fe302bd2aeafdcfcb8cebe987e6520799a205};
    sign = 1;
    sync = 0;
    tkid = "com.yubico.Authenticator.TokenExtension:972BC027C9E349CFA63856C2A2968F16XXXXX71564A94570EE131DEA92E9BB0F";
    tomb = 0;
    type = 42;
    unwp = 1;
    "v_Ref" = "<SecKeyRef:('com.yubico.Authenticator.TokenExtension:972BC027C9E349CFA63856C2A2968F16XXXXX71564A94570EE131DEA92E9BB0F') 0x303ba4f10>";
}
Accepted Answer

Thanks for all the background info.

Here's the attributes of the private key reference

That’s interesting. Note the presence of sign and decr, both with a value of 1. Those correspond to kSecAttrCanSign and kSecAttrCanDecrypt, which is the key indicating that it supports both signing and decryption.

If this were a normal key then it’d be the end of the story. However, for the case of a CTK-backed key then the actual work is done by the CTK appex. Whether a specific operation type or algorithm is supported or not depends on the appex. Given that you have access to the appex’s source code, I can explain the mechanics of that and then you can look up in the code to see what it does in that case.

Every CTK appex include a TKTokenSession subclass [1]. That subclass typically acts as its own delegate. The delegate has to implement the tokenSession(_:supports:keyObjectID:algorithm:) method. This determines what algorithms it supports. And, based on that, it then needs to support one or more of the following:

Finally, keep in mind that a CTK appex associated with hardware will typically bundle up the crypto operation and pass it along to the hardware. That’s whole point of having a hardware token, in that the private key never actually leaves the hardware.

Different hardware places different limits on what operations a particular key can perform. And the hardware might support multiple keys, each with its own set of supported operations. For example, a PIV smart card typically supports signing in slot 9C and decryption in slot 9D.

If the hardware has such a limit then there’s nothing that the CTK appex can do about it. The best you can hope for is for it to set kSecAttrCanSign and kSecAttrCanDecrypt correctly.

Share and Enjoy

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

[1] A smart card token actually subclasses TKSmartCardTokenSession, which is itself a subclass of TKTokenSession. You’re using a virtual token, so I’m gonna focus on that.

[2] One day we’ll update the docs to make the exact mechanics of that clear (r. 90838636).

That was it, Quinn. Thanks for the response. The apex extension set the correct operation as you saw in the private key attributes but the missing implementation (within the CTK appex) was this:

The delegate has to implement the tokenSession(_:supports:keyObjectID:algorithm:) method. This determines what algorithms it supports.

In the Yubico Authenticator apex extension, that method only returned true for .signData. I added support for .signData and .decryptData. Now, when calling SecKeyCreateDecryptedData from a 3rd party app triggers the apex tokenSession(_:decrypt:keyObjectID:algorithm:) method correctly and shows the supported algorithms for that specific private key.

The CTK appex is a game changer and is exactly what is needed to support an external PIV smart card like the YubiKey. The Yubico Authenticator handles all the operations with different limits on each certificate and performs the appropriate crypto actions (signing, decryption) within the various slots within the YubiKey without the associated private ever leaving the device.

Thanks again. Resolved.

SecKeyCreateDecryptedDataWithParameters always fails with algo not supported
 
 
Q