skip to Main Content

I am using the below code to save a private key (needed for end-to-end encryption) into the keychain. If I take out the line with kSecAttrSynchronizable as String: kCFBooleanTrue!, then the key is correctly being saved. Seems like this line is causing the error. I am trying to get the private key to sync across all iCloud devices. I’d appreciate any help resolving this.

func save(userUID: String, privatekey: String) {
    let passwordData = privatekey.data(using: .utf8)!
    
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrSynchronizable as String: kCFBooleanTrue!,
        kSecAttrService as String: "hustles/(userUID)",
        kSecAttrAccount as String: userUID,
        kSecValueData as String: passwordData
    ]
    SecItemAdd(query as CFDictionary, nil)
}

2

Answers


  1. Chosen as BEST ANSWER

    The solution is to also update the query in the read function by also adding in "kSecAttrSynchronizable as String: kCFBooleanTrue!,". The save query must match the read query.


  2. First, it is better to specify the kSecAttrAccessible key to indicates when the keychain item is accessible, like kSecAttrAccessibleWhenUnlocked mentioned here.

    And make sure the kSecAttrService, kSecAttrAccount, and kSecAttrSynchronizable combined must be unique, as explained here.

    That being said, the presence of kSecAttrSynchronizable should not inherently cause a saving operation to fail. Typically, the best approach in these situations is to check the status returned by SecItemAdd (or other Keychain API calls) to get detailed error information.

    As noted in ahmed‘s answer, if you are saving a keychain item with specific attributes (like kSecAttrSynchronizable set to kCFBooleanTrue), then when you try to read (or query) that item, you must also specify the same attributes to successfully retrieve it. The attributes used in the save operation essentially set the "conditions" for that keychain item, and when you query for it, you need to match those conditions.

    So, if you are saving a keychain item with kSecAttrSynchronizable set to true, your read/query function should also include kSecAttrSynchronizable set to true.

    If your save function is:

    let saveQuery: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrSynchronizable as String: kCFBooleanTrue!,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
        kSecAttrService as String: "hustles/(userUID)",
        kSecAttrAccount as String: userUID,
        kSecValueData as String: passwordData
    ]
    SecItemAdd(saveQuery as CFDictionary, nil)
    

    Then your read function must be (inspired from this answer):

    func retrievePrivateKey(for userUID: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrSynchronizable as String: kCFBooleanTrue!,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
            kSecAttrService as String: "hustles/(userUID)",
            kSecAttrAccount as String: userUID,
            kSecReturnData as String: kCFBooleanTrue!,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        
        guard status == errSecSuccess else {
            return nil
        }
        
        if let keyData = item as? Data,
           let keyString = String(data: keyData, encoding: .utf8) {
            return keyString
        }
        
        return nil
    }
    

    The kSecMatchLimit as String: kSecMatchLimitOne makes sure only one item is returned (which makes sense for our use-case, since each UID should only have one private key associated with it).

    That ensures your search criteria in the read function matches the attributes set when the item was saved, so you can successfully retrieve it from the keychain.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search