247

I am using idandersen's scifihifi-iphone code for keychain and save password using

[SFHFKeychainUtils storeUsername:@"User" andPassword:@"123"
              forServiceName:@"TestService" updateExisting:YES error:&error];

When I delete the application from the device, the password remains in the keychain.

I want to remove the password from the keychain when the user deletes the application from the device. How can I do this?

Leo Dabus
  • 198,248
  • 51
  • 423
  • 494
enc
  • 3,127
  • 3
  • 19
  • 19
  • 13
    Since your code doesn't run when your application is being deleted, you have no way of doing this. – Jonathan Grynspan Jan 23 '11 at 09:20
  • 1
    I think that you can delete a keychain item only from inside the app, but not before uninstall it. You can take a look at deleteItem method of SFHFKeychainUtils to delete an username or a password from the keychain. – matteodv Apr 06 '11 at 15:14

9 Answers9

417

You can take advantage of the fact that NSUserDefaults are cleared by uninstallation of an app. For example:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Clear keychain on first run in case of reinstallation
    if (![[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"]) {
        // Delete values from keychain here
        [[NSUserDefaults standardUserDefaults] setValue:@"1strun" forKey:@"FirstRun"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }

    //...Other stuff that usually happens in didFinishLaunching
}

This checks for and sets a "FirstRun" key/value in NSUserDefaults on the first run of your app if it's not already set. There's a comment where you should put code to delete values from the keychain. Synchronize can be called to make sure the "FirstRun" key/value is immediately persisted in case the user kills the app manually before the system persists it.

Amro
  • 4,503
  • 1
  • 14
  • 9
  • 2
    I agree with Amro that you can delete/purge your keychain upon the first run of the application. This will clear out everything that was set prior to the app being uninstalled the last time. I did this for one of my apps that stores Facebook/Twitter credentials and it's been working pretty well knowing the fact that only your app has access to whatever keychain that was set. – XCool Jun 17 '11 at 04:20
  • 3
    NSUserDefaults are not cleared when the user manually quits the app. Only values that you've set but either the system (periodically) or you have not yet synchronized with the disk (by calling ```synchronize```) are lost in that case. It's a good idea to call synchronize after setting the first run key. And yes, NSUserDefaults are cleared when a device is reset (and not restored from backup), and that's fine in this case. – Amro Sep 21 '13 at 01:53
  • 5
    You're wrong and you're probably doing something that's causing user defaults to be cleared. The entire point of NSUserDefaults is to save preferences and have those preferences persist through multiple application launches. Again, resetting the device or deleting an app will remove user defaults. Look at how many people have up-voted this answer and check your code. Then go read the documentation. Heck, send me the relevant code and I'll *show* you what you're doing wrong. It's been this way since iOS 2.0. Down vote away but I'd suggest writing an isolated, simple, test case first. – Amro Sep 23 '13 at 18:22
  • P.S. And here's an article that explains it in detail http://chrisrisner.com/31-Days-of-iOS--Day-11-Saving-data-using-NSUserDefaults – Amro Sep 23 '13 at 18:25
  • Oh...you are right. Sorry. Hard to find the test code in 800 files. I'll update my answer. – stephen Sep 24 '13 at 01:59
  • 9
    I would not be very confident on using NSUserDefault for this. Why? Have a look at that thread: http://stackoverflow.com/questions/20269116/nsuserdefaults-loosing-its-keys-values-when-phone-is-rebooted-but-not-unlocked. If you start your app from the background, there are cases where your custom keys in NSUserDefaults are just not set. Applying this answer would lead to delete your Keychain custom keys although you really didn't want that! – Aurelien Porte Jun 10 '14 at 15:28
  • @AugustinePA they're not suggesting saving user credentials into `UserDefaults`. They're suggesting to store a flag that specifies whether the app has previously been run. That way if the `UserDefaults` (which clears on app uninstall) states the app has not been previously run, but the keychain contains items, you can delete the items. – theoriginalbit Jul 04 '19 at 09:33
45

For users looking for a Swift 3.0 version of @amro's answer:

let userDefaults = UserDefaults.standard

if !userDefaults.bool(forKey: "hasRunBefore") {
     // Remove Keychain items here

     // Update the flag indicator
     userDefaults.set(true, forKey: "hasRunBefore")
}

*note that synchronize() function is deprecated

Alex Stone
  • 41,555
  • 51
  • 213
  • 379
bwcooley
  • 1,232
  • 1
  • 11
  • 23
31

There is no trigger to perform code when the app is deleted from the device. Access to the keychain is dependant on the provisioning profile that is used to sign the application. Therefore no other applications would be able to access this information in the keychain.

It does not help with you aim to remove the password in the keychain when the user deletes application from the device but it should give you some comfort that the password is not accessible (only from a re-install of the original application).

Shane Fitzgibbon
  • 896
  • 7
  • 11
  • 2
    So, If we change provisioning profile of our application, would it be able to access previously stored values in keychain. – Moaz Saeed Feb 06 '19 at 07:26
  • 1
    @MoazSaeed from my experience, the app have access to previously stored values in Keychain even if the provisioning profile changes. – ricardopereira Apr 12 '21 at 14:42
28

For those looking for a Swift version of @amro's answer:

    let userDefaults = NSUserDefaults.standardUserDefaults()

    if userDefaults.boolForKey("hasRunBefore") == false {

        // remove keychain items here


        // update the flag indicator
        userDefaults.setBool(true, forKey: "hasRunBefore")
        userDefaults.synchronize() // forces the app to update the NSUserDefaults

        return
    }
rsc
  • 9,304
  • 4
  • 32
  • 31
9

C# Xamarin version

    const string FIRST_RUN = "hasRunBefore";
    var userDefaults = NSUserDefaults.StandardUserDefaults;
    if (!userDefaults.BoolForKey(FIRST_RUN))
    {
        //TODO: remove keychain items
        userDefaults.SetBool(true, FIRST_RUN);
        userDefaults.Synchronize();
    }

... and to clear records from the keychain (TODO comment above)

        var securityRecords = new[] { SecKind.GenericPassword,
                                    SecKind.Certificate,
                                    SecKind.Identity,
                                    SecKind.InternetPassword,
                                    SecKind.Key
                                };
        foreach (var recordKind in securityRecords)
        {
            SecRecord query = new SecRecord(recordKind);
            SecKeyChain.Remove(query);
        }
InquisitorJax
  • 984
  • 9
  • 15
  • 1
    By using `if (VersionTracking.IsFirstLaunchEver) {// remove keychain items}` from Xamarin.Essentials you do not need the code for the `userDefaults`. [Xamarin.Essentials wraps that for you](https://github.com/xamarin/Essentials/blob/ac795bc661770695f541c5f57121128f3e7bd82f/Xamarin.Essentials/VersionTracking/VersionTracking.shared.cs#L20). – Christopher Stephan Jun 12 '19 at 13:29
8

Files will be deleted from your app's document directory when the user uninstalls the app. Knowing this, all you have to do is check whether a file exists as the first thing that happens in application:didFinishLaunchingWithOptions:. Afterwards, unconditionally create the file (even if it's just a dummy file).

If the file did not exist at time of check, you know this is the first run since the latest install. If you need to know later in the app, save the boolean result to your app delegate member.

stephen
  • 1,019
  • 13
  • 30
8

@amro's answer translated to Swift 4.0:

if UserDefaults.standard.object(forKey: "FirstInstall") == nil {
    UserDefaults.standard.set(false, forKey: "FirstInstall")
    UserDefaults.standard.synchronize()
}
stkent
  • 18,470
  • 14
  • 80
  • 99
Muhammad Nayab
  • 1,344
  • 12
  • 13
  • Or even `if !UserDefaults.standard.bool(forKey: "FirstInstall")` which defaults to false if the key doesn't exist. And .synchronize() not needed. – CharlesA May 23 '20 at 07:24
3

This seems to be the default behavior on iOS 10.3 based on behavior people have been witnessing in beta #2. Haven't found any official documentation about this yet so please comment if you have.

Stavash
  • 14,032
  • 5
  • 50
  • 79
0

Just add an app setting bundle and implement a toggle to reset the keychain on app restart or something based on the value selected through settings (available through userDefaults)