app keychain not getting backed up/restored using iCloud backup

Greetings,

my app adds various items to its keychain and when doing an iTunes backup and restore, it successfully restores the keychain items provided the iTunes backup is encrypted. Works great!


However I can't seem to be able to restore my app's keychain when doing an iCloud backup and then restoring to a different iPhone. Is there a way of specifying that the iCloud backup is encrypted in such a way that apps' keychains are backed up and restored properly when using iCloud backup/restore function?


Is there a iCloud specific attribute that must be provided for keychain items to make this work?


Thanks,


Neal

Accepted Reply

Found answer. Must use

kSecAttrSynchronizable
attribute for iCloud backup of keychain items which are to be deemed as restorable from an iCloud backup.

This is a fine solution but it doesn’t work the way that you think it works. Keychain items are never included iCloud backup. [The striked out sentence is incorrect. See below for more.] When you set

kSecAttrSynchronizable
the item goes into iCloud Keychain, which is a very different thing.

Be aware that users can opt out of iCloud Keychain, in which case your keychain items will not be available after a restore from an iCloud backup (and, for that matter, a restore from a no-password iTunes backup).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Found answer. Must use kSecAttrSynchronizable attribute for iCloud backup of keychain items which are to be deemed as restorable from an iCloud backup.

Found answer. Must use

kSecAttrSynchronizable
attribute for iCloud backup of keychain items which are to be deemed as restorable from an iCloud backup.

This is a fine solution but it doesn’t work the way that you think it works. Keychain items are never included iCloud backup. [The striked out sentence is incorrect. See below for more.] When you set

kSecAttrSynchronizable
the item goes into iCloud Keychain, which is a very different thing.

Be aware that users can opt out of iCloud Keychain, in which case your keychain items will not be available after a restore from an iCloud backup (and, for that matter, a restore from a no-password iTunes backup).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Is this a definitive statement that no matter what the iOS settings and whether your key has the Accessible attribute set to one of the options that does not end with ThisDeviceOnly, that Keychain entries are never backed up to iCloud? I'm having the same problem and the kSecAttrSynchronizable attribute was the only solution I found, but it seemed more like a workaround like you described.

I had trouble finding any documentation that said Keychain entries are not backed up to iCloud at all.

I’m not particularly looking for the Keychain entry to be synced between devices. I just wanted it to be there after an iCloud restore on a new phone.

Is this a definitive statement that no matter what the iOS settings and whether your key has the Accessible attribute set to one of the options that does not end with ThisDeviceOnly, that Keychain entries are never backed up to iCloud?

Actually, what I posted earlier was incorrect. I had a re-read of the iOS Security document, which has a specific section on iCloud Backup. That makes it clear that iCloud Backup acts like an iTunes backup without a password: the keychain items are included in the backup but they are wrapped with a device-specific key. Thus, they can only be restored to the device that originally backed them up, which means that they get lost when you restore the backup to a different device (which is how you tested this).

I’m sorry I didn’t double check this earlier.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Eskimo,

Once again you've cleared up all my issues. Thank you. I thought that by specifying the kSecAttrSynchronizable key with value of true for my various keychain items I'd be able to have them saved in the backup and then restored to another device. After many tests I found this not to be the case and assumed I was not doing something properly. (And of course I was - I misunderstood what the kSecAttrSynchronizable was for).


This means that my app which has data stored in the keychain can only be backed up and restored using iTunes encrypted backups. That works great. It's a shame that there is no way to preserve the keychain data using iCloud backups to be restored to a different device. Now, I'm going to have to rip that data out of the keychain and store it in a data file.


Why oh why couldn't there have been a simple option for the user to encrypt the backup's keychain data with a password, so that if the password was not specified when doing the restore to another device, the keychain data wouldn't be propagated, but if the user specified the password then the keychain data would've been restored as well.


From my perspective having the keychain items only be restorable to the same device they were originally backed up from is pretty useless functionality.


Many thanks for saving me a ton of fruitless work 😟


Neal

It appears that more and more people opt for using iCloud backups instead of iTunes because of the simplicity. That iCloud backups have no option for saving/restoring apps' keychain items across devices is awful.


Neal

will you fix this in the next update. Right after I updated to the most recent beta update, my key chain would not populate full info. Just the user name but not passwords . I did get this info elavated to the developer team but I also had other issues that my cell number could not be changed as well in the keychain text you a conformation number . I do not have access to cell no more . That was a big issue for me today .

will you fix this in the next update.

I’m sorry to hear about the problems you’re having but DevForums isn’t the right place to request changes like this. Rather, you should use the bug reporting system to file an enhancement request.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

The iOS Security document states that only items with kSecAttrSynchronizable will be stored in the cloud.

I've tested this. KeychainItems with the Attribute kSecClassGenericPassword are restored from iCloud Backup even if the kSecAttrSynchronizable is not defined. Am I misunderstanding the Security document or is something wrong there?

I had reason to investigate this properly today and ended up running a comprehensive set of tests here in my office. I figured that, once I’ve done all this work, I might as well share the results with everyone.

....
Accessible Backed Up By Restore On Result 
Normal iTunes same device restored
different device gone
iTunes, encrypted same device restored
different device restored
iCloud same device restored
different device gone
ThisDeviceOnly iTunes same device restored
different device gone
iTunes, encrypted same device restored
different device gone
iCloud same device restored
different device gone

Some testing parameters:

  • Xcode 10.1
  • iOS 12 (12.0.1 on one device, 12.1.1 on the other)
  • iCloud account has neither two-factor nor two-step authentication enabled

However, I don’t think any of these parameters make a difference. As far as I know things have always behaved this way.

Finally, my test code it pasted in below, just in case you want to try this yourself (in which case you’ll get very familiar with the iOS setup assistant :-).

Share and Enjoy

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


class MainViewController : UITableViewController {

    @IBOutlet var normalLabel: UILabel!
    @IBOutlet var thisDeviceOnlyLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.load()
    }

    private func load(account: String, into label: UILabel) {
        var copyResult: CFTypeRef? = nil
        let err = SecItemCopyMatching([
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: "KeychainBackupTest",
            kSecAttrAccount: account,
            kSecReturnData: true,
        ] as NSDictionary, &copyResult)
        switch err {
        case errSecItemNotFound:
            label.text = "-"
        case errSecSuccess:
            let resultData = copyResult! as! NSData as Data
            let result = String(bytes: resultData, encoding: .utf8)!
            label.text = result
        default:
            label.text = "\(err)"
        }
    }

    private func load() {
        self.load(account: "normal", into: self.normalLabel)
        self.load(account: "thisDeviceOnly", into: self.thisDeviceOnlyLabel)
    }

    private func save(account: String, access: CFString, value: String) {
        _ = SecItemAdd([
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: "KeychainBackupTest",
            kSecAttrAccount: account,
            kSecAttrAccessible: access,
            kSecValueData: value.data(using: .utf8)!
        ] as NSDictionary, nil)
    }

    private func save() {
        let value = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
        // We use a time as the value so it's easy to see that the restored value makes sense.
        self.save(account: "normal", access: kSecAttrAccessibleWhenUnlocked, value: value)
        self.save(account: "thisDeviceOnly", access: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, value: value)
        self.load()
    }

    private func reset() {
        _ = SecItemDelete([
            kSecClass: kSecClassGenericPassword,
        ] as NSDictionary)
        self.load()
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        switch (indexPath.section, indexPath.row) {
        case (0, 0): break
        case (0, 1): break
        case (0, 2): self.save()
        case (0, 3): self.reset()
        default: fatalError()
        }
        self.tableView.deselectRow(at: indexPath, animated: true)
    }
}

Sorry for bumping up this old thread: Do you know what is the impact of the Settings > [Apple ID] > iCloud > Keychain [On/Off] entry on this?

The table above didn’t survive the transition to the new DevForums (not surprising given that I was using raw HTML!) so I’ve included a new version below

I was able to fix the formatting of the original post, so this post is no longer necessary.

Share and Enjoy

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

kSecAttrAccessibleAlwaysThisDeviceOnly gets retained on another device if we use transfer from another phone option. How to identify this? my secure enclave doesnt get restored for my app,so users are left stranded for face/touch id and getting item not found error.
It seems like you created a specific thread for this, so I’ll respond there.

Share and Enjoy

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

Greetings,

I know this thread has been up for a while and most questions have been answered. However with the introduction of the new "Advanced Data Protection"option in iOS 16.2 seems like an attempt to fix this issue?

I have run into the keychain backup issue on my app and had to find a solution to inform the user in the case of lost data via a backup. However according to this Advanced data protection document the update is expected solve the primary issue of not encrypting keychain data when pushed to iCloud is that correct?

Because I did update to iOS 16.2 and enabled the advanced data protection feature. However I was still seeing a loss of keychain data. Is that an expected behavior and has anyone else tried out this feature?

Thanks,

Buwaneka