AppSec Blog

How Not to Store Passwords in iOS

The WordPress iOS App

I was looking for an open source iOS application and quickly came across the WordPress app. Once you log in to your WordPress blog via the app your credentials are then stored on the device itself. If done correctly this is not necessarily a bad thing. However, the WordPress app's implementation leaves a bit to be desired. Take a look at the following snippet of code from WPcomLoginViewController.m and in Spot the Vuln fashion see if you can find the issue.

Update: The WordPress for iOS team has already committed a change for the next release to address the issue. Check out their comments below.

...snip... - (void)saveLoginData { if(![username isEqualToString:@""]) [[NSUserDefaults standardUserDefaults] setObject:username forKey:@"wpcom_username_preference"]; if(![password isEqualToString:@""]) [[NSUserDefaults standardUserDefaults] setObject:password forKey:@"wpcom_password_preference"]; if(isAuthenticated) [[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"wpcom_authenticated_flag"]; else [[NSUserDefaults standardUserDefaults] setObject:@"0" forKey:@"wpcom_authenticated_flag"]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)clearLoginData { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_username_preference"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_password_preference"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_authenticated_flag"]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (BOOL)authenticate { if([[WPDataController sharedInstance] authenticateUser:WPcomXMLRPCUrl username:username password:password] == YES) { isAuthenticated = YES; [self saveLoginData]; } else { isAuthenticated = NO; [self clearLoginData]; } return isAuthenticated; }

As you can see from the saveLoginData method above, (NSUserDefaults *)standardUserDefaults is used to store the username, password, and an authenticated flag after the user successfully signs on. To see this code in action simply logon to the app then go to Settings -> WordPress on the device to see your stored credentials as shown in the following screen shot.

The problem is that standardUserDefaults stores information in a plist file in plain text.

If you're using the iOS Simulator to run the WordPress app the org.wordpress.plist file is stored at ~/Library/Application Support/iPhone Simulator/4.2/Applications/<APP_ID>/Library/Preferences where <APP_ID> is a randomly generated string.

If you're running the app on an actual iOS device and perform a backup (I did this with my iPad) the plist file is located
at ~/Library/Application Support/MobileSync/Backup/<DEVICE_ID> where <DEVICE_ID> is a random string.

The plist file will also have a random name but you can simply run grep wpcom_password_preference * to find the file that contains your password.

Figure 1: Screenshot showing the plaintext credentials stored in the plist file

Using the Keychain

Sensitive data like passwords and keys should be stored in the Keychain. Apple's Keychain Services Programming Guide states that a "keychain is an encrypted container that holds passwords for multiple applications and secure services. Keychains are secure storage containers, which means that when the keychain is locked, no one can access its protected contents". Moreover, in iOS, each application only has access to its own keychain items.

You interact with the Keychain by passing in a dictionary of key-value pairs that you want to find or create. Each key represents a search option or an attribute of the item in the keychain. For example, the following code shows how you can add a new item to the keychain.

NSMutableDictionary *query = [NSMutableDictionary dictionary]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:account forKey:(id)kSecAttrAccount]; [query setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible]; [query setObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; OSStatus error = SecItemAdd((CFDictionaryRef)query, NULL);

The code first creates a NSMutableDictionary then simply sets the appropriate key-value pairs in the dictionary. We start by adding the kSecClass key with the value kSecClassGenericPassword which, as the name implies, means that we are adding a generic password to the keychain. The kSecAttrAccount key specifies the account whose password we're storing. In our case it would be for the "iostesting" username we saw above. The kSecValueData key specifies the password we want to store in the keychain. In our case the inputString variable would contain the password "princess123" that is in the screen shot above. Finally, calling the SecItemAdd function adds the data to the keychain.

Retrieving, updating, and deleting items from the keychain can be done using other functions [1]. To see how it works feel free to use this sample code that is based on work by Michael Mayo.

Protection Attributes

When you are adding or updating keychain items via the SecItemAdd or SecItemUpdate functions you should also specify the appropriate protection attribute. You may have noticed the kSecAttrAccessible key with the value kSecAttrAccessibleWhenUnlocked in the code above. This specifies that the keychain item can only be accessed while the device is unlocked. In total, there are six constants which were introduced in iOS 4.0 that you can use to specify when an item in the keychain should be readable.

Attribute Data is...
kSecAttrAccessibleWhenUnlocked Only accessible when device is unlocked.
kSecAttrAccessibleAfterFirstUnlock Accessible while locked. But if the device is restarted it must first be unlocked for data to be accessible again.
kSecAttrAccessibleAlways Always accessible.
kSecAttrAccessibleWhenUnlockedThisDeviceOnly Only accessible when device is unlocked. Data is not migrated via backups.
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly Accessible while locked. But if the device is restarted it must first be unlocked for data to be accessible again. Data is not migrated via backups.
kSecAttrAccessibleAlwaysThisDeviceOnly Always accessible. Data is not migrated via backups.


Never use kSecAttrAccessibleAlways. If you are storing user data in the keychain it's safe to assume that it should be protected.

In general, you should always use at least kSecAttrAccessibleWhenUnlocked to ensure that data is not accessible when the device is locked. If you have to access data while the device is locked use kSecAttrAccessibleAfterFirstUnlock.

The three ...ThisDeviceOnly attributes should only be used if you are sure that you never want to migrate data from one device to another.


Mobile development brings both familiar and new challenges for creating secure applications. The iOS platform provides a number of strong security features with the Keychain and protection attributes introduced in iOS 4.0. As usual, it is up to developers, development teams, and security professionals to make sure that user data is handled securely and appropriate APIs are used correctly.


Frank Kim is the curriculum lead for application security at the SANS Institute and the author of DEV541 Secure Coding in Java. If you liked this post check out SANS' new class on Secure iOS App Development.

[1] See the Keychain Services Reference for full details on the API


Posted January 5, 2011 at 10:39 PM | Permalink | Reply

Isaac Keyet

Thanks for taking the time to go through this. Looks like we overlooked this security flaw when building the app and didn't realize it.
We're marking this as a Blocker-type issue which means the next version of the app (2.6.4) won't be released until this is fixed.

Posted January 5, 2011 at 10:50 PM | Permalink | Reply

Frank Kim

Thanks Isaac, that's great to hear!

Posted January 6, 2011 at 12:46 AM | Permalink | Reply

Josh Straub

If you Encrypt your Backups in iTunes (and you definitely should), your password is not stored in plaintext in the MobileSync folder.
As a bonus, Encrypting your iTunes Backups also is the only way to transfer your saved passwords (including voicemail password) if you ever Restore your Backup onto a different iPhone (for instance if your phone dies and you get a replacement).

Posted January 6, 2011 at 4:54 AM | Permalink | Reply


The sad truth is that other applications (and far more critical ones that provide access to online passwords!) like RoboForm on the iPhone do the same and store the Roboform Online account credentials in cleartext on the device.
You might want to install roboform (I think it is free) and do the same analysis. The Roboform Online username+password just sits there in one of the XML files unencrypted.

Posted January 6, 2011 at 5:07 AM | Permalink | Reply


Bit more info:
Roboform stores the credentials in cleartext in this file:
And this is supposedly one of the leading password aggregators. What a shame.

Posted January 6, 2011 at 5:32 PM | Permalink | Reply

Jorge Bernal

Thanks a lot for the article. This will be fixed in the next released.
You can see the fix in
Right now, we are using the SFHFKeychainUtils class, which doesn't set the kSecAttrAccessible attribute.
I can't fully understand from the docs what are the implications of using kSecAttrAccessibleAlways vs the other options. Do you have more insight on that?

Posted January 6, 2011 at 5:55 PM | Permalink | Reply

Frank Kim

Hi Jorge,
I definitely recommend setting the kSecAttrAccessible attribute to ensure that credentials are not accessible while the device is locked. The documentation isn't all that clear but check out Session 209 ''" Securing Application Data from WWDC 2010 at
It's a great talk where they cover some other important topics and they do a good job explaining why you really need to use the kSecAttrAccessible attribute.
Thanks for fixing this so quickly!

Post a Comment


* Indicates a required field.