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

Up vote post of neal1 Down vote post of neal1
Post marked as solved 3.9k views

12 Replies

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"
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:

Code Block text
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


Note that I did not re-run my tests here — all I did was fix the formatting — so the testing parameters described above still apply.

Share and Enjoy

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

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?

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.

AccessibleBacked Up ByRestored OnResult
NormaliTunessame devicerestored
different devicegone
iTunes, encryptedsame devicerestored
different devicerestored
iCloudsame devicerestored
different devicegone
ThisDeviceOnlyiTunessame devicerestored
different devicegone
iTunes, encryptedsame devicerestored
different devicegone
iCloudsame devicerestored
different devicegone

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!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

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)
    }
}

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?

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"

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 .

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

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

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"

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.

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