Basic Security in iOS 5 – Part 2

Chris Lowe

This is a post by Chris Lowe, an iOS application developer and aspiring game developer living in San Antonio, Texas.

Learn how to protect files with the data protection API!

Learn how to protect files with the data protection API!

Welcome to Part Two of the epic tutorial series on implementing basic security in iOS 5!

In Part One, we began work on Christmas Keeper, an app designed to keep track of holiday gift ideas. We built out the shell for our app and put it under lock and key via prompting the user for a PIN/password. In the process, we learned a little about hashing and cryptography.

In this second and final part of the series, we’ll tackle using the iOS Keychain and Data Protection API, as well as several more segues and the new Twitter API.

This tutorial starts where the previous tutorial left off, so if you don’t have it already, grab the project where we left off last time.

Read on to get even more security-savvy!

Keys to the Kingdom

Before we jump back into the app, let’s take a deeper look at the methods in the KeychainWrapper class. These methods are all helper methods to help you manage items in your keychain.

The keychain in iOS (and Mac) is a highly secure location inside of the OS that is the de facto storage location for user names, passwords, logon tokens, secret keys, etc. It’s a feature provided to you out of the box, with the additional “benefit” of being C-based (so it’s faster).

This is the ideal place to store our sensitive data for several additional reasons:

  • The keychain is located OUTSIDE of your application, you just have access to it (via the provided API). This is important should your application ever be compromised (read: jailbroken/rooted) and someone has access to all of the information in your binary.
  • The keychain is encrypted and managed for you – going the route of saving to a PLIST, Core Data database, etc. saves files in plaintext so you’ll need to manage the encryption of it yourself (not a trivial task). Plus, that’s where Apple saves their data too, seems like a safe bet to me :).
  • The keychain can be migrated for you automatically across backup/restores so the user can pick up from where they left off.

That being said, the keychain is an I/O monster and is not meant for large amounts of data – if you have many sensitive data elements, you might need to consider something like a Core Data database and roll your own encryption, unfortunately.

Also, just because the password is hashed and more secure, does not mean we are free to post it to Twitter for all to see! It’s still sensitive information and, again, given enough time and computational horsepower, can be broken so it’s best to store it somewhere to safer.

Likewise, each app has its space in the keychain, and this keychain access can be shared across apps (typically within the same company, not cross-company). As you’ll see in the code, jumping from Objective-C to Core Foundations (C code) while using ARC makes for some interesting keywords that are easy to miss and misuse.

We’re going to open up KeychainWrapper.m, and take a look at each method but before we do that, let’s review the Dictionary keys we are going to use/can use to setup our Keychain entry. For more technical details on the iOS Keychain implementation, Apple has a great reference document on Keychain Services for iOS (including their own version of reading/writing to the keychain). Be sure to brush up on “Keychain Services Concepts” there as well because “knowing is half the battle!

In general, the most common keys you are going to set in the Keychain are below (take from Apple’s sample code at the above link).

   [keychainData setObject:@"Item label" forKey:(id)kSecAttrLabel];  // Not used
   [keychainData setObject:@"Item description" forKey:(id)kSecAttrDescription];  // Not used
   [keychainData setObject:@"Service" forKey:(id)kSecAttrService];
   [keychainData setObject:@"Account" forKey:(id)kSecAttrAccount];
   [keychainData setObject:@"Your comment here." forKey:(id)kSecAttrComment]; // Not used
   [keychainData setObject:@"password" forKey:(id)kSecValueData];
 
    // The below are taken from another methods so they don't use "keychainData" but still function the same
   [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
   [genericPasswordQuery setObject:keychainItemID forKey:(id)kSecAttrGeneric];
   [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
   [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

Here is how to read the keys: “k” is an Apple (perhaps Industry) standard for a Constant (usually defined in an enum). “Sec” means Security. “Attr” means Attribute. The remainder is what the attribute is, for example “Label” is the Label for the Keychain entry.

Some of these keys are just carry over from the Mac such as kSecAttrLabel, kSecAttrDescription, and kSecAttrComment. These are important on a Mac because users can see the keychain using the Keychain Access app and you want your Keychain entry to provide enough information that the user understands what it’s for and doesn’t accidentally delete it. However on iOS, Apple has deemed viewing the Keychain a unnecessary for users. The keys we do use in the app however, include:

    [searchDictionary setObject:APP_NAME forKey:(__bridge id)kSecAttrService];

This key uniquely identifies the “Service name” for our key entry (remember, the Keychain is a shared repository). In this instance, we are just using our App Name (ChristmasKeeper, from our Constants.h class) which is pretty unique but another popular option is your company name in reverse-DNS notation (com.raywenderlich.iOSSecurityFTW).

    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];

These two lines of code specify WHO will be accessing our keychain. We are using the same identifier for both to keep things simple, but you can specify different values for each.

    [dictionary setObject:valueData forKey:(__bridge id)kSecValueData];

This is the actual data that we are saving which, in our case, is the hashed password from the user.

    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

Here we are specifying that we are storing a Password which is the most common type of entry for iOS. You can also store “Internet Passwords” and certain Certificates but this is mostly for Mac usage.

    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];

Technically, we should only get one result back from our search – if we get more than one back we either didn’t specify a specific enough search and/or our keychain entry wasn’t unique enough. In any event, we only want one back, so we specify that here.

    [searchDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];

This last one is a bit hard to read, but what it is saying is that when we do a search for a keychain value, we are going to get something back and we have the opportunity to specify what that return type is. In this case, we want “Data” back, which just happens to be CFData which we later change into the familiar NSData.

Now let’s look at each method individually.

  • + (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier
    You can think of this method as the foundation for the rest of the keychain helper methods. This method sets up all of the default parameters needed to easily reach the keychain.

    You’ll notice the “(__bridge id)” used quite a bit in conjunction with this method. This is an ARC directive that specifies a type-cast (in this case to identify) for the respective CF type (all of the kSecAttr options are of type CFTypeRef). One alternative to this code is to turn off ARC for this class, but that’s the easy way out. :]

  • + (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier
    I’d call this method the “raw data” method. It searches the keychain for the value we’re requesting, but it returns it as an NSData object. We then have to manually manipulate it ourselves.

    One important line of code is:

     
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &foundDict);

    This is what actually searches the keychain. It takes in search parameters (via searchDictionary) and an output parameter (foundDict).

  • + (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier
    This is really the method you want to use for searching the keychain, because it returns the value found as an NSString, which is much easier to compare against another input value (such as the password entered by the user).
  • + (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
    This method writes to the keychain. We use “SecItemAdd,” which is the key line of code that physically writes to the keychain.

    Like the JSON file below, we want to protect this key from being accessed while the device is locked, so we set the following line of code:

     
    [dictionary setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];

    This limits when the key can be accessed. We also check to see if the key already exists. If it does, we update it instead of creating a new one or deleting the old one.

  • + (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
    Like createKeychainValue, but updates a value that is already in the keychain.
  • + (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier
    A simple method to delete a keychain value.
  • + (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash
    A convenient method that compares the input value (in SHA256 encrypted format) to the value in the keychain.

Remember, we used these methods in the last tutorial in ChristmasRootViewController.m’s textFieldDidEndEditing method. Now that you understand how the keychain methods work a bit better, walk through how the code and make sure you fully understand how it works now.

Keeping Track of Christmas

Now back to our Christmas Keeper app. When we left off last time, we had finished implementing user authentication, necessary to protect the user’s Christmas lists from prying eyes!

Successful authentication should have brought us to our UITableView of Christmas presents, but currently our app is missing logic to handle anything with the table, so let’s remedy the situation.

First off, we are going to create a model to store our Christmas presents and manage several other (mostly housekeeping) things for us. Create a new file with the iOS\Cocoa Touch\Objective-C class template, enter ChristmasItems for the class, and make it a subclass of NSObject. Then replace ChristmasItems.h with the following:

#import 
 
@interface ChristmasItems : NSObject
@property (nonatomic, strong) NSMutableArray *christmasList;
 
// Default initializer.
- (id)initWithJSONFromFile:(NSString *)jsonFileName;
 
// Exposed method to load the data directly into the 'christmasList' mutable array.
- (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName;
 
// Helper method to ensure that we have a default image for users who don't specify a new picture.
- (void)writeDefaultImageToDocuments;
 
// Writes JSON file to disk, using Data Protection API.
- (void)saveChristmasGifts;
 
// Should someone delete a present, we can remove it directly, then save the new list to disk (saveChristmasGifts).
- (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath;
 
// Should someone add a present, we can add it directly, then save the new list to disk (saveChristmasGifts).
- (void)addPresentToChristmasList:(NSDictionary *)newItem;
 
// Accessor method to retrieve the saved image for a given present.
- (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath;
 
// Accessor method to retrieve the saved text for a given present.
- (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath;
 
// Accessor method to retrieve the saved image name for a given present.
- (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath;
 
@end

The first thing we do is create an array to actually hold our Christmas elements (which will just be a dictionary). For the rest of the header, we have several methods for setting up our Christmas list, as well as for accessing details about individual presents. Read through the comments to get an overview of what they will do.

Now switch to ChristmasItems.m and replace the contents with the following:

#import "ChristmasItems.h"
 
@implementation ChristmasItems
@synthesize christmasList;
 
- (id)initWithJSONFromFile:(NSString *)jsonFileName 
{
    self = [super init];
    if (self) {
        self.christmasList = [self dataFromJSONFile:jsonFileName];
        [self writeDefaultImageToDocuments];
    }
    return self;
}
 
// Helper method to get the data from the JSON file.
- (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName 
{
 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *jsonPath = [documentsDirectory stringByAppendingPathComponent:jsonFileName];
    if ([[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
        NSError* error = nil;
        NSData *responseData = [NSData dataWithContentsOfFile:jsonPath];
        NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];        
        return [[NSMutableArray alloc] initWithArray:[json objectForKey:@"gifts"]];
    }
 
    // We have to have a default cell. :]
    return [[NSMutableArray alloc] initWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:@"Thank Chris Lowe for an awesome tutorial on Basic iOS Security! Maybe follow him on Twitter (@chrisintx)?", @"text", @"noImage", @"imageName", nil], nil];
}
 
// Helper method to save the JSON file.
// Here we are write-protecting the file, then we are setting the file itself to use File Protection (Data At Rest).
- (void)saveChristmasGifts 
{
 
    NSError *error = nil;
    // We wrap our Gifts array inside of a dictionary to follow the standard JSON format (you could keep it as an array).
    NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObject:self.christmasList forKey:@"gifts"];
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary 
                                                       options:NSJSONWritingPrettyPrinted error:&error];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *jsonPath = [documentsDirectory stringByAppendingPathComponent:@"christmasItems.json"];
    [jsonData writeToFile:jsonPath options:NSDataWritingFileProtectionComplete error:&error];
    [[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey] ofItemAtPath:jsonPath error:&error];
}
 
// This method ensures that we always have an image available in case the user doesn't specify one.
- (void)writeDefaultImageToDocuments 
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:@"default_image.png"];
    // If the file does NOT exist, then save it
    if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        UIImage *editedImage = [UIImage imageNamed:@"present.png"];
        NSData *webData = UIImagePNGRepresentation(editedImage);
        [webData writeToFile:imagePath atomically:YES];
    }
}
 
// Accessor method to retrieve the saved text for a given present.
- (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath 
{
    return  [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"text"];
}
 
// Accessor method to retrieve the saved image name for a given present.
- (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath 
{
    return [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"];
}
 
- (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath 
{
    // Admittedly not the most efficient way to set an image. :]
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imageKey = [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageKey];
    UIImage *presentImage = nil;
    if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        presentImage = [UIImage imageWithContentsOfFile:imagePath];
    } else { // If we dont have an "imageName" key then we use the default one
        presentImage = [UIImage imageNamed:@"present.png"];
    }
    return presentImage;
}
 
- (void)deleteImageWithName:(NSString *)imageName 
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageName];
    // Delete the file, but not the default image. (We need that one!)
    if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath] && (![@"default_image.png" isEqualToString:imageName])) {
        [[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil];
    }
}
 
- (void)addPresentToChristmasList:(NSDictionary *)newItem 
{
    [self.christmasList addObject:newItem];
    [self saveChristmasGifts];
}
 
- (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath 
{
    [self deleteImageWithName:[[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"]];
    [self.christmasList removeObjectAtIndex:indexPath.row];
    [self saveChristmasGifts];
}
 
@end

Let’s walk through each method here:

  • - (id)initWithJSONFromFile:(NSString *)jsonFileName
    This method is our “default initializer.” This is the method we want people to use when creating our class. In this method, we initialize our array to whatever we find in our “dataFromJSONFile” method. We also save some placeholder images for later use.
  • - (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName
    In this method, we look for a specified JSON file in our documents directory. If we find it, we return the list at key “gift.” If we don’t find it, we create a basic single-element array and return that instead.
  • - (void)saveChristmasGifts
    When someone adds/removes/changes a gift, we call this method to write the JSON list to disk so that we can always have the latest Christmas Gift list saved.
  • - (void)writeDefaultImageToDocuments
    Saves our default present image so that all of our images are stored in one place.
  • - (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath
    Returns the text (the present details) for a given present (at index).
  • - (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath
    Same as above, but for the image of the actual present.
  • - (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath
    Same as above, but for the image name of the present. This is important so that we can pass the name of the picture around instead of the actual picture.
  • - (void)deleteImageWithName:(NSString *)imageName
    We provide the ability for people to delete presents from the list. This method deletes the image associated with the deleted present.
  • - (void)addPresentToChristmasList:(NSDictionary *)newItem
    We add the present to our array and update the JSON file.
  • - (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath
    We can also remove presents. This method deletes the present image, removes it from the array, and updates the JSON file.

The most important security-related code here is the saveChristmasGifts method. This method writes a JSON file to disk using “NSDataWritingFileProtectionComplete,” which protects our file from being accessed WHILE the file is being written.

We also need the next line, which uses “NSFileProtectionComplete” to protect the file “at rest” – i.e., while not being accessed. You’ll get the best results when the user has a passcode lock on their device – your app can only be ready for the user to protect themselves.

Protecting the file while it is being written is important because you don’t want to give someone the chance to modify your file while its open (being written to) such as changing/deleting content or, in a more extreme case, copy the contents somewhere else. There is a small chance of this on an iOS device and is mostly related to the Mac but it’s still good practice all around (plus, you never know when you’ll be porting your iOS app to the Mac :)). The same goes for NSFileProtectionComplete in that you don’t want to allow the file to be accessed while the device is locked because that could mean something bad is happening in general (such as the phone being stolen and the thief is trying to pry open the users data).

In the spirit of full disclosure, saving to the Documents directory is admittedly less secure than say to Caches (NSCachesDirectory) or Temp (NSTemporaryDirectory) directories but its more well known and easier to use as an example in the blog post since people know where to look for it. Conversely, never save sensitive information to NSUserDefaults – these values are stored in PLAIN TEXT (i.e. just like this blog post) for all to see (and is also saved inside of your application bundle). Be mindful of what you put in there.

Creating a Custom Cell

From here, we’ll detour to create a table cell that we are going to use to help simplify our custom table cell code.

Create a new file with the iOS\Cocoa Touch\Objective-C class template, name the class ChristmasListTableViewCell and make it a subclass of UITableViewCell. All you need to do here is add the following properties to ChristmasListTableViewCell.h:

@property (nonatomic, strong) IBOutlet UIImageView *thumbnail;
@property (nonatomic, strong) IBOutlet UILabel *textView;

Then synthesize them in ChristmasListTableViewCell.m:

@synthesize thumbnail;
@synthesize textView;

And that’s it!

Let’s take a break from code for a minute and hook up this class with our UITableViewCell inside of our Christmas Presents Table View.

Head back to MainStoryboard.storyboard and select the cell inside of the Table View called “Christmas Keeper” – I find it easiest to click just to the right of the Disclosure Indicator. Then bring up the Identity Inspector and change the “Class” property to be our newly created ChristmasListTableViewCell class.

CKClassCell

Then Control+Drag from the Table View Cell to the UILabel and to the UIImageView to hook up the outlets we made. Everyone does this their own way, but I find it easy to do with the Document Outline (the view to the left of the Storyboard).

CKDocOutline

Build and run real quick just to make sure everything still runs (although it still won’t display any presents quite yet).

It Giveth Presents, and it Taketh Them Away

Now we need to create two more view controllers – one to handle adding new presents and another to show the presents.

Create a new file with the iOS\Cocoa Touch\UIViewController subclass template, enter AddChristmasItemViewController for the class, make it a subclass of UITableViewController, and leave both checkboxes unchecked.

Replace AddChristmasItemViewController.h with the following, to define a protocol to pass back the new present details and define a few properties that we’ll hook up in a bit:

#import 
 
// This delegate is used to send back the newly created present to the Table View.
@protocol AddChristmasItemDelegate 
-(void)addChristmasItemToList:(NSDictionary *)item;
@end
 
@interface AddChristmasItemViewController : UITableViewController 
 
@property (nonatomic, strong) IBOutlet UITextView *presentText;
@property (nonatomic, strong) IBOutlet UIImageView *presentImage;
@property (nonatomic, strong) UIImagePickerController *imagePicker;
@property (nonatomic, strong) NSString *presentImageFileName;
@property (nonatomic, weak) id delegate;
 
-(IBAction)cancel:(id)sender;
-(IBAction)done:(id)sender;
 
@end

Then switch to AddChristmasItemViewController.m and replace it with the following:

#import "AddChristmasItemViewController.h"
 
@implementation AddChristmasItemViewController
@synthesize delegate, presentText, presentImage, imagePicker, presentImageFileName;
 
- (void)presentPickerForPhoto 
{
 
    // Set source to the Photo Library (so it works in Simulator and on non-camera devices).
    self.imagePicker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
 
    // Set delegate.
    self.imagePicker.delegate = self;
 
    // Disable image editing.
    self.imagePicker.allowsEditing = NO;
 
    // Show image picker.
    [self presentModalViewController:self.imagePicker animated:YES];
}
 
// Once the user has made a selection, the delegate call back comes here.
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    [picker dismissModalViewControllerAnimated:YES];
 
    // Access the original image from info dictionary.
    UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    [self.presentImage setImage:image];
 
    // Capture the file name of the image.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSError *error = nil;
    NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error];
    NSString *fileName = [NSString stringWithFormat:@"photo_%i.png", [dirContents count]];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    self.presentImageFileName = [NSString stringWithFormat:fileName];
 
    // Then save the image to the Documents directory.
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];   
    if ([mediaType isEqualToString:@"public.image"]){
        UIImage *editedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
        NSData *webData = UIImagePNGRepresentation(editedImage);
        [webData writeToFile:imagePath atomically:YES];
    }
}
 
#pragma mark - View lifecycle
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Being proactive as the ImagePicker takes a while to load.
    self.imagePicker = [[UIImagePickerController alloc] init];
}
 
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    self.presentText = nil;
    self.presentImage = nil;
}
 
#pragma mark - Table view delegate
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // The user can tap the cell (where the present image is) to change the photo.
    switch (indexPath.row) {
        case 0:
            [self presentPickerForPhoto];
            break;
        default:
            break;
    }
}
 
-(IBAction)cancel:(id)sender 
{
    [self dismissModalViewControllerAnimated:YES];
}
 
-(IBAction)done:(id)sender 
{
 
    // If the user just presses "Done" without setting an image, this would be blank.
    if (!self.presentImageFileName) {
        self.presentImageFileName = [NSString stringWithFormat:@"default_image.png"];
    }
 
    // Check to make sure we have a delegate and that it implements our method.
    if (self.delegate && [(id)self.delegate respondsToSelector:@selector(addChristmasItemToList:)]) {
        // Send back the newly created item
        NSDictionary *newPresent = [NSDictionary dictionaryWithObjectsAndKeys:self.presentText.text, @"text", self.presentImageFileName, @"imageName", nil];
        [self.delegate addChristmasItemToList:newPresent];
    }
 
    [self dismissModalViewControllerAnimated:YES];
}
 
@end
Charlie Sheen loves storyboards too!

Charlie Sheen loves storyboards too!

We’re going to benefit from Storyboards especially in this class, as you’ll notice that we don’t have to worry about creating the cells, number of cells, etc. And we don’t have to deal with the UITextView that we have in our static table. #winning!

What we are doing in this class, however, is catching when a user presses on the table cell that houses the present image. We cause this to bring up the photo picker, though we could use the camera as well.

When the user selects a picture, we save a copy to our Documents directory so we have it locally. Lastly, we update our delegate to reflect that we have a new present added.

Creating our second view controller will be easier than the first! Create a new file with the iOS\Cocoa Touch\UIViewController subclass template, enter ChristmasDetailsTableViewController for the class, make it a subclass of UITableViewController, and leave both checkboxes unchecked.

Open ChristmasDetailsTableViewController.h and replace it with the following:

#import 
 
@interface ChristmasDetailsTableViewController : UITableViewController
@property (nonatomic, strong) IBOutlet UITextView *presentText;
@property (nonatomic, strong) IBOutlet UIImageView *presentImage;
@property (nonatomic, strong) NSString *textHolder;
@property (nonatomic, strong) NSString *presentImageName;
- (IBAction)tweetButtonTapped:(id)sender;
@end

The purpose of this class is to show the details of the present and to provide the user with the option to tweet about it (really just an excuse to try out the new Twitter API :]).

Then switch to ChristmasDetailsTableViewController.m and replace it with the following:

#import "ChristmasDetailsTableViewController.h"
#import 
 
@implementation ChristmasDetailsTableViewController
@synthesize presentImage, presentText, textHolder, presentImageName;
 
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];
}
 
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    self.presentText = nil;
    self.presentImage = nil;
}
 
- (void)viewWillAppear:(BOOL)animated
{
    self.presentText.text = self.textHolder;
 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:self.presentImageName];
    if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        UIImage *newImage = [UIImage imageWithContentsOfFile:imagePath];
        [self.presentImage setImage:newImage];  
    }
 
    [super viewWillAppear:animated];
}
 
- (IBAction)tweetButtonTapped:(id)sender { 
    if ([TWTweetComposeViewController canSendTweet])
    {
        TWTweetComposeViewController *tweetSheet = [[TWTweetComposeViewController alloc] init];
        [tweetSheet setInitialText:self.presentText.text];
        [tweetSheet addImage:self.presentImage.image];
        [self presentModalViewController:tweetSheet animated:YES];
    }
    else
    {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sorry" message:@"Tweeting is unavailable right now, check your internet connection and that you have at least one Twitter account set up" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alertView show];
    }
}
@end

As we’ll see in a minute, the details about the present are passed forward to us, so all we need to do is go find the image on the disk and display it, as we’ve already gotten the text from the sender.

The tweetButtonTapped: code is copied straight out of the great Twitter API tutorial on this! Gotta love “plug and play” code.

Step back to the MainStoryboard.storyboard file and change the “Class” property on the Add Table View Controller and the Detail Table VIew Controller scenes just like we did for the Table Cell. Set them to their respective new files and don’t forget to hook up the corresponding outlets.

Build and run again to ensure it still runs (although it still won’t display any presents you add).

Santa Claus is Coming to Town!

Now that we have our data model, table cell, and detail view controllers implemented, we can turn our attention back to the Christmas Present Table and start hooking things up for the big finale.

Create a new file with the iOS\Cocoa Touch\UIViewController subclass template, enter ChristmasListTableViewController for the class, make it a subclass of UITableViewController, and leave both checkboxes unchecked.

Then replace ChristmasListTableViewController.h with the following:

#import 
#import "AddChristmasItemViewController.h"
#import "ChristmasItems.h"
 
@interface ChristmasListTableViewController : UITableViewController 
@property (nonatomic, strong) ChristmasItems *christmasGifts;
@property (nonatomic, strong) NSIndexPath *selectedRow;
@end

Here we are going to use the model that we created (ChristmasItems) to easily keep track of presents. We also need to save the selectedRow to pass forward to our detail view that you’ll see in a minute.

Next switch to ChristmasListTableViewController.m and replace it with the following:

#import "ChristmasListTableViewController.h"
#import "ChristmasListTableViewCell.h"
#import "ChristmasDetailsTableViewController.h"
 
@implementation ChristmasListTableViewController
@synthesize christmasGifts, selectedRow;
 
#pragma mark - UIStoryBoardSegue Method
 
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{
    if ([[segue identifier] isEqualToString:@"AddChristmasPresent"]) {
        // Set the delegate for the modal thats about to present
        UINavigationController *navigationController = segue.destinationViewController;
		AddChristmasItemViewController *playerDetailsViewController = [[navigationController viewControllers] objectAtIndex:0];
		playerDetailsViewController.delegate = self;
    } else  if ([[segue identifier] isEqualToString:@"ChristmasDetailsSegue"]) {
        // Pass-forward the details about the present
		ChristmasDetailsTableViewController *playerDetailsViewController = segue.destinationViewController;
		playerDetailsViewController.textHolder = [self.christmasGifts textForPresentAtIndex:self.selectedRow];
		playerDetailsViewController.presentImageName = [self.christmasGifts imageNameForPresentAtIndex:self.selectedRow];
    }
}
 
#pragma mark - View lifecycle
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.christmasGifts = [[ChristmasItems alloc] initWithJSONFromFile:@"christmasItems.json"];
 
    // Register for the Data Protection Notifications (Lock and Unlock).  
    // ** NOTE ** These are only enforced when a user has their device Passcode Protected!
    // Data Protection is not available in the Simulator
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceWillLock) name:UIApplicationProtectedDataWillBecomeUnavailable object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceWillUnLock) name:UIApplicationProtectedDataDidBecomeAvailable object:nil];
}
 
// We still must manually remove ourselves from observing.
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 
#pragma mark - Data Protection Testing Methods
 
// This method will get called when the device is locked, but checkKey will not. It is queued until the file becomes available again.
// I've seen very sporadic results with iOS 5 as to whether this method executes.
- (void)deviceWillLock 
{
    NSLog(@"** Device is will become locked");
    [self performSelector:@selector(checkFile) withObject:nil afterDelay:10];
}
 
- (void)deviceWillUnLock 
{
    NSLog(@"** Device is unlocked");
    [self performSelector:@selector(checkFile) withObject:nil afterDelay:10];
}
 
- (void)checkFile 
{
    NSLog(@"** Validate Data Protection: checkFile");
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *jsonPath = [documentsDirectory stringByAppendingPathComponent:@"christmasItems.json"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
        NSData *responseData = [NSData dataWithContentsOfFile:jsonPath];
        NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil]; 
        NSLog(@"** FILE %@", json);
    }
}
 
#pragma mark - Table view data source
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.christmasGifts.christmasList count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
    ChristmasListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ChristmasListCell"];
    cell.textView.text = [[self.christmasGifts.christmasList objectAtIndex:indexPath.row] objectForKey:@"text"];
    [cell.thumbnail setImage:[self.christmasGifts imageForPresentAtIndex:indexPath]];
 
    return cell;
}
 
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath 
{
    self.selectedRow = indexPath; // Save the selected indexPath so that the prepareForSegue method can access the right data
    return indexPath;
}
 
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [self.christmasGifts removeGiftAtIndexPath:indexPath];
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }   
}
 
#pragma mark - AddChristmasItemViewController Delegate Callback
 
-(void)addChristmasItemToList:(NSDictionary *)item 
{
    [self.christmasGifts addPresentToChristmasList:item];
    [self.tableView reloadData];
}
 
@end

Another fairly straight-forward class, thanks to the awesome power of iOS 5!

Let’s first take a close look at viewDidLoad. Here we are instantiating our model class with the JSON file we specified. Then we are registering for the two Data Protection API methods called “UIApplicationProtectedDataWillBecomeUnavailable” and “UIApplicationProtectedDataDidBecomeAvailable.”

In this case, we aren’t doing anything security-wise, we are merely calling the dummy methods “deviceWillLock” and “deviceWillUnlock.” However, you can do anything you need to here. You may want to delete files, log the user out, prompt them for credentials when the device is unlocked again, etc.

Also, because we are ARC-enabled, we don’t typically have a dealloc method, but in this case it’s needed. This is because we have to remove ourselves from listening for the Data Protection notifications when the instance of our class is released.

Note: The data protection APIs are not available in the Simulator, so you must be on device for this test to work.

Moving down to the Table View Delegate methods, you can see that we are accessing our array in the ChristmasItems model to return the count. This is followed by dequeueing a reusable cell from our table view.

You may wonder where all of my code went for if(cell == nil), etc. In iOS 5, Apple has said “dequeue a cell and if one exists you’ll get it, otherwise we’ll create a new one for you!” Sweet! This simplifies and removes code so that we now only have to deal with the contents of the cell directly (in our case, the textView and thumbnail).

In the commitEditingStyle method, we are utilizing those helper methods we created in the ChristmasItems class to remove the gift. Then we update the table view.

You can also see that we implemented our delegate method for the AddChristmasItemViewControllerDelegate. This allows us to add new presents passed in by the user.

Lastly, in our prepareForSegue method we are checking for the Segue from a user hitting the + button or tapping on a cell. If they want a new present, then we set ourselves as the delegate (to get the addChristmasItemToList delegate callback). If it’s a details view they want, then we pass forward the details of the present.

Make one last trip to the MainStoryboard.storyboard file, and update the “Class” property on the Christmas Keeper Table to this class (ChristmasListTableViewController).

Take a deep breath. And build and run.

If everything went as planned, then: Our app. Is. Done!

Time to party like it’s the end of the year!

A good Christmas gift for iOS and iPhone Developers!  :]

Where To Go From Here?

Here is an example project with all of the code from this tutorial.

Update 4/6/13: Here’s an updated version from Blake Loizides – modified to remove deprecation warnings in iOS 6.

Congratulations, you’ve covered the basics of security in iOS 5 and are ready to start using it in your own app – no more excuses! Hopefully this tutorial has gotten the point across that security is necessary but shouldn’t be intrusive (I’ve tried to sneak security features past you and then talk about them afterwards to demonstrate my point).

We’ve learned A LOT about security here – the iOS Keychain, Data Protection APIs, hashing and cryptography algorithms, and the new Secure Alert Views! Give yourself a pat on the back – it was a journey.

I hope you enjoyed this tutorial! As always, if you have any questions or comments on this tutorial or security in general, please join the discussion below!


This is a post by Chris Lowe, an iOS application developer and aspiring game developer living in San Antonio, Texas.

Chris Lowe

By day, Chris is a Social Media/Mobile Technical Lead at USAA. By night, an UI and iOS consultant through his company, Hashtag Studios. He is available for speaking, training, or consulting at HashtagStudios[at]gmail.com. Visit his site for more thoughts.

When not working, you can find Chris spending time with his wife, young daughter, and 2 crazy dogs.

User Comments

24 Comments

[ 1 , 2 ]
  • Hello Chris and others,

    I have a (probably beginners) question. I am releasing a word game, in which I use a sqlite file which contains my wordlist. I would like to protect it so users with hacked Iphones can not simply read the data in the sqlite file.
    I am not saving ANY user data in the sqlite. Just a "readonly" wordlist from which the game can tell if a played word exists in that language.

    Before I read all the stuff about encryption and decryption, I encrypted the words (with my own key) in the sqlite myself, and added some code which converted the word played to the string to lookup in the sqlite file.
    I also understood recently that files in the filesystem, such as sqlite files are encrypted automatically on the iphone by hardware. So I even wouldn't have to do that. Even although I read thats not waterproof.

    Is it true that you can encrypt your own data just as long as you don't store any userdata ? I read that you might need an export compliance (CCATS) when you export an app to the US which uses its own encryption. Does someone know if thats the case if you encrypt only to protect your own software data ?

    I know these might be some hard questions, but it is hard to find any information about this on the web. Just a lot of (good) tutorials about protection user data.

    Thanks in advance

    Remco
    denico
  • Many thanks for this wonderful and well crafted tutorial!

    A minor remark. It looks to me that if at first login the user enters the username and password, these are stored even if the Cancel button is pressed instead of the Done button. I think this extra delegate method should solve the problem:

    Code: Select all
    #pragma mark - Delegates

    - (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
    {
        //  intercept the Cancel button to reset the PIN and prevent it from being stored
        if(buttonIndex == 0 && alertView.tag == kAlertTypeSetup) {
            UITextField *passwordField = [alertView textFieldAtIndex:1];
            passwordField.text = nil;
        }
    }
    xiaodidi
  • Awesome work and I very much appreciate it!

    I have a couple conceptual questions:

    1. What if I want to reset or change the login/password?

    I guess that's the only question for now, as the other ones are to be addressed in part 3.
    Morkrom
  • hi Chris
    thanks a lot for that wonderful tutorial i was looking for that and it came in right place.

    just a question if the user would like to change his password or both username and password how it can be done

    thanks again
    elieS
  • I'm very new to objective-c so please bear with me if I ask something stupid.

    I was raising a question in stackoverflow and someone pointed me to this tutorial:
    http://stackoverflow.com/questions/1642 ... s-keychain

    I'm thinking if this app is bulletproof or not.

    let's say I first launched this app and enter my username and password as:
    username: josh
    password: mypassword

    I created some christmas presents (by pressing the + button) and quit the app. So the present is saved. So I assume that no one can open this app, without knowing my username and password.

    A hacker tried to edit the preference plist (which isn't too hard to do so by using tools like iFunbox, even a non-jailbreak device will work) and he or she removed the PIN_SAVED (ie. hasSavePin) node. Now the app thought that no one has ever created the username and password before, therefore it will popup the username and password UIAlert, and ask for these pieces of info.

    So now hacker entered something and saved the login info, and he or she can review the xmas presents data!

    I guess in order to make it safe, we have to encrypt/decrypt the presents data/object by using the login password. which means as soon as the username and password is saved to the keychain, everytime when a xmas present object is added, we can make use of this keychain info to encrypt the xmas present object. So even though hacker can easily delete/amend the preference plist or the keychain (not sure if this is possible to iphone but for mac it's possible by using the keychain access), the presents data/object will be garbage.

    Just my 2cents
    josh h
  • Do not use KeychainWrapper from Christmas Keeper in any shipping app. It's dangerously insecure. The problem is that it hardcodes the salt used for hashing the PIN in the macro SALT_HASH. It's defined in ChristmasConstants.h.
    Code: Select all
    // Used to help secure the PIN
    // Ideally, this is randomly generated, but to avoid unneccessary complexity and overhead of storing the Salt seperately we will standardize on this key.
    // !!KEEP IT A SECRET!!
    #define SALT_HASH @"FvTivqTqZXsgLLx1v3P8TGRyVHaSOB1pvfm02wvGadj7RLHV8GrfxaZ84oGA8RsKdNRpxdAojXYg9iAj"

    The salt should be randomly generated at runtime and saved to the keychain as well. As it's seen here this is about as secure as doing it this way. Never reuse a salt.
    orkoden
  • only a note. The code:

    [[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey] ofItemAtPath:jsonPath error:&error];

    does NOT work on device (on simulator is normal..) if you do not use Entitlements. try adding:

    Application supports iTunes file sharing


    to the plist, to allow to see files via iTunes. Files are in plain text.
    ingconti
  • Hi Chrin
    Maybe You or someone can help me with my question - what's happen to data stored in keychain when user update his iOS version to new system ? i.e user have ios 6 and stored some information from my App in Keychain, are those information will be still avaiable after install ios 7 ??

    BR
    Kivu
  • Can I use kSecClassKey to store in Keychain, If I have store some Keys specific to my application. Security is not my concern here. Could you please help me by posting here some sample code to add some data using the kSecClassKey key.
    vaibhav_
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

... 49 total!

Update Team

Editorial Team

  • Alexis Gallagher
  • Ryan Nystrom

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Miguel Angel
  • Zihan Xu

... 33 total!

Subject Matter Experts

... 4 total!