NSUserDefaults aren’t meant to store sensitive information.

If you have been developing for iOS for a while you know that NSUserDefaults is pretty much the “de-facto” way to store a lot of small data. It has its limitations, sure, but you can easily store preferences in them. From NSStrings to NSNumbers, NSUserDefaults can store almost anything related to preferences.

And here’s the thing – You should use NSUserDefaults to store preferences only – just preferences for your app’s functionality and nothing else. Anything else like user data or IAP data should be stored someplace else. Especially if said that is sensitive and you don’t want to risk your user’s data.

I was a little bit shocked when I was reading Apple’s StoreKit Guide (you know, their OFFICIAL guide for dealing with StoreKit). It is pretty informative and everything but things get dangerous when you scroll down the document to find the way to persist user purchases. If you need to persist purchases on iOS 7, that’s no problem, you can use the receipt. But if you need to persist purchases in earlier iOS versions… There’s where things get troublesome as you can’t use the receipt with them. Apple’s solution is a little bit… Well, dangerous. Let me quote the document right here:

After making the product available, your app needs to make a persistent record of the purchase. Your app uses that persistent record on launch to continue to make the product available. It also uses that record to restore purchases, as described in “Restoring Purchased Products” (page 38). Your app’s persistence strategy depends the type of products you sell and the versions of iOS.

  • For non-consumable products and auto-renewable subscriptions in iOS 7 and later, use the app receipt as your persistent record.
  • For non-consumable products and auto-renewable subscriptions in versions of iOS earlier than iOS 7, use the User Defaults system or iCloud to keep a persistent record.
  • For non-renewing subscriptions, use iCloud or your own server to keep a persistent record.

(Bolded part done by me).

They even bothered to use some source code as an example. Let me show you:

Storing the value:

#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
[storage setBool:YES forKey:@"enable_rocket_car"];
[storage setObject:@15 forKey:@"highest_unlocked_level"];

[storage synchronize];

Unlocking purchases:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];

if (rocketCarEnabled) {
// Use the rocket car.
} else {
// Use the regular car.
}

Why storing sensitive data in NSUserDefaults is a big problem.

Okay, okay, so what’s the big deal, anyway? This is a big issue: NSUserDefaults are stored in plist in binary format, with no encryption, and is stored in your app’s directory. This means that any user, even the “noobiest” one, can tinker with your NSUserDefaults with 5 minutes of their time.

Any user can edit, see, share, move and whatever they please with it. Jailbreak users can simply install iFile, navigate to your app directory, play with the file, and move on with their day. A problem if you’re persisting things like purchases with NSUserDefaults, huh?

“Dammit, these jailbreak users always getting on the way of my business and making things more complicated!”

WRONG! Jailbreak users can use iFile to get the plist and modify it, yes, but even people who don’t have jailbreak can use other software to do it. iExplorer and iFunBox are two examples of software that allow you to explore iOS’ filesystem without having your phone jailbroken (try them out yourself – iFunBox is free). These programs exist for both Mac and Windows (there may or may not be alternatives for Linux, but that’s irrelevant for this post). Anyone has access to these plists if the filesystem is not encrypted. Jailbroken users and non-jailbroken users alike. If you want to add In-App Purchases to your apps and persist them on the device, or if you need to store sensitive information, you need to do things differently.

And to make things even worse for you, using iFunBox is pretty trivial. You have easy access to essential parts of the file system. This is what “User Applications” tab looks like:

Screenshot 2014-01-09 16.30.19

When you go inside an app, you will find the familiar App Directory structure you know by heart (Documents, Library, and tmp in this case – if you ever thought users couldn’t access these directories at all you’re really wrong).

Screenshot 2014-01-09 16.34.24

If you to Library > Preferences from here you will find the plist that stores NSUserDefaults. It’s name is your App identifier with .plist at the end. For example, for Colloquy, it’s name is “info.colloquy.mobile.plist”. This is the one file that has all your NSUserDefaults.

Screenshot 2014-01-09 16.46.33

You can then simply click “Copy to Mac”, and save it somewhere easy to access. I saved it on my desktop for the sake of making it easier to show why it’s dangerous to store sensitive info on it.

Now if you try to open it directly, you will see that file has gibberish. This is because the plist is in binary format, which is enough to deter some people from tinkering with it. But converting it to something readable by humans is pretty easy.

You can use an utility on the command line called plutil to convert this plist into XML and make it a piece of cake to read it and modify it. To convert Colloquy’s file:

cd Desktop
plutil -convert xml1 info.colloquy.mobile.plist

Then open it in any text editor (or in XCode, if you fancy being able to see the data easier). And be ready to see all the NSUserDefaults:

Screenshot 2014-01-09 16.53.14

I couldn’t see that Colloquy specifically was doing anything particularly dangerous. It’s storing my IRC username for Saurik’s IRC there, but nothing particularly dangerous.

If you later edit the plist and want to use it on your phone, save it and convert it back to binary:

plutil -convert binary1 info.colloquy.mobile.plist

Then you can put it back on your phone with iFunBox.

So you can clearly see that Apple hasn’t considered the ability to easily do this. Go back to the source code I quoted above. You can easily enable the “car rocket” by switching a BOOL in the plist. Not black much black magic here.

Go ahead and try it. Maybe many of the apps you use are storing sensitive info with NSUserDefaults. If you find anything like that, please kindly contact the developers of the app to let them know.

Okay so I can’t use NSUserDefaults for everything – What should I do instead?

This depends a lot on what kind of data you’re storing. The main thing that got me to write this article was Apple’s StoreKit documentation which tells developers to simply store purchases in plists in iOS versions earlier than 7. That’s a big no-no.

I have already said that NSUserDefaults is great for settings and small data. It wouldn’t be surprising if you wanted to store things like password hashes and API keys in them.

If you need to persist user purchases in iOS 7, persist the receipt (doing that is out of this tutorial but you will find the way to do it in the article I linked to above) instead of persisting the thing they just bought in NSUserDefaults. You have seen how easy it is to see them and modify them. The IAP Receipt itself is encrypted and signed by Apple, but you can easily get the contents without storing its contents someplace dangerous.

But what to do if you need to support iOS versions earlier than 7, or you just want to store any generic sensitive info in general? Use the iOS Keychain. Which yes, it’s a pain to learn by reading Apple’s docs, but some people have written small libraries to make it using the keychain easier and less of a pain in general. For example Secure-NSUserDefaults is an example of such a library. This way you can store sensitive data by interfacing directly with NSUserDefaults, without actually storing your data there.

Of course, I never said that NSUserDefaults should be ditched completely forever – It has it’s uses for app settings, but you should limit its use to that. Anything else should be stored somehow else.

Positive SSL