2011年12月21日星期三

Basic Security in iOS 5 Tutorial Part 1

Basic Security in iOS 5 Tutorial Part 1

This is a post by Chris Lowe, an iOS application developer and aspiring game developer living in San Antonio, Texas. He warns that this tutorial is pretty Epic in length, but promises you’ll learn a thing or two along the way. :]
Learn how to store passwords securely in the Keychain!
Learn how to store passwords securely in the Keychain!
One of the most important aspects of software development also happens to be considered one of the most mysterious and scary (and is thus avoided like the plague): that of securing an application.
Users expect and demand that applications run as intended and keep their data safe and secure, whether it be from intruders, other users, or even from the correct user making mistakes.
But app security, while it can be time-consuming, need not be scary or mysterious!
Good app security has a couple of qualifications:
  • Users must not consciously know they are being protected. (That is, don’t get in the way! Users aren’t there to evaluate your data security practices; they want to use your app!)
  • Your security must be difficult to work around or break. (Nothing is impossible to hack, given enough time and horsepower.)
  • Don’t be Citibank.
Luckily for us, Apple has created several APIs for our use to help make our apps the best they can be. These include:
  • Secure text entry
  • An encrypted keychain
  • Great hashing algorithms; and
  • The Data Protection API.
In this tutorial, we’re going to cover all of this and more, as we build an application to keep track of the holiday presents that we are going to buy our friends and family. We’ll call it “Christmas Keeper.”
As an added bonus, we’re going to use TONS of awesome new iOS5 features, including Twitter, Storyboards, ARC, JSON, Alert Views, Static Table Views, and a simple Custom UI.
So keep reading to learn more – by the end of this tutorial series, you will be a better, more security-conscious developer!

Getting Started (Plus, a Little Housekeeping)

Launch Xcode and create a new project with the iOS\Application\Single View Application template. Fill out the template fields as follows:
ChristmasKeeperTemplate
  • Product Name: ChristmasKeeper
  • Company Identifier: the identifier that you use for your apps, in reverse domain notation
  • Class Prefix: leave this empty
  • Device Family: iPhone
  • Use Storyboard: yes, please
  • Use Automatic Reference Counting: Ditto
  • Include Unit Tests: this should be unchecked
Ok, we have our project setup with all of the default settings. This is a great start, but it doesn’t make our app great, so let’s make a few tweaks.
First, download the images we are going to be using and drag and drop them into the project.
Now on the Project Summary page, you should see that Xcode already picked up the Default and Default@2x.png files for your splash images. Drag the Icon.png and Icon@2x.png files into their respective App Icons boxes (Xcode will complain about the images, but just click Yes).
CKSettings
While we are on this screen, scroll down (expand if necessary) to the Linked Framework and Libraries section. Go ahead and add the Twitter and Security Frameworks here.
CKFrameworks
Jump over to the Info tab (at the top of the Project Settings). Expand the “Icon Files (iOS 5)” option and the Primary Icons option. Take a second and change “Icon already includes gloss effect” to YES.
In Xcode 4.2/iOS5, Apple decided that you could have some icons with and without icon gloss so the generic item of “Icon already includes gloss effect” – that you would put as a new key on the root level – no longer applies.
Also, while you’re here, change the “Bundle Display Name” to something more friendly, like “Christmas” for example, or even something more sneaky like “Boring App” to throw off any spies. :]
Lastly, add a new key (by selecting the last entry and clicking the + button that appears). Select “Status bar is initially hidden” from the drop down, make sure the type is set to BOOL, and set it to YES. This will give us maximum screen real estate for our app.
CKInfo
Our tweaking here is done! Go ahead and hit Build and Run to make sure we didn’t fudge anything.
Christmas Keeper Launch Image

Storyboard Magic

One of the coolest parts of iOS5 is Storyboards. With it, we can mock up an entire application in a matter of minutes and, best of all, we don’t have to deal with the Application Delegate, Navigations Controllers, Tab Bar Controllers, or any other messy stuff that can waste a lot of development time!
So let’s head into the MainStoryboard.storyboard file and dig in. We are going to spend a bit of time here, so bear with me. It will be well worth it!
This is what our app is going to look like:
CKStoryboard
The default Storyboard is kind enough to give us a blank UIViewController to start with, so let’s be gracious hosts and use it! This view will serve as our landing view when the app is launched, and will be where we prompt the user for their PIN.
Add a UIImageView to the Controller and let it expand to the size of the window. If you happen to have a status bar still showing, snip that by going to the Attributes Inspector and changing the Status Bar Simulated Metric to None.
Set the image on the UIImageView to Default.png. iOS should auto-detect if you are on the Retina Display and change the graphic for us, since one is available.
CKLaunchImage
Next, drop a UITableViewController onto the Storyboard. It’s kind of ugly and it needs a title. So let’s put it in a Navigation Controller!
Do this by using the new “Embed In…” feature of Interface Builder. With the Table View selected, go to Editor menu > Embed In… > Navigation Controller. You’ll notice that a new Navigation Controller is added and automatically linked to the Table View.
CKEmbedIn
Now, let’s spice it up a little bit with some color. Select the Navigation Controller, then the Navigation Bar (you have to be zoomed in to 100% level to select it) and change its tint to green (you might need to select the Table View before its Nav Bar will pick up the color change). Then select the Navigation Bar on the Table View and set its title to “Christmas Presents.”
CKNavTitle
Let’s hook up the first View Controller we made with the new Table View. Control-Drag from the first View Controller (the one with the Image View on it) to the Navigation Controller. You can also drag it to the Table View Controller but I find it easier to understand this way.
Select Modal as the Segue type, then select the newly created Segue and change its transition style to Cross Dissolve. Set the Identifier as “ChristmasTableSegue.”
We’re going to create a custom Table View cell right inside of the Storyboard, and we’re going to use the one that is already provided for us from when we dropped in the UITableViewController. Select the white box below the “Prototype Cells” header and set the height to 90px. Set the Identifier to “ChristmasListCell,” and the Accessory type to Disclosure Indicator.
CKTableCell
Now drag a UIImageView onto the cell. Set the width and height to 90px, move it to the left corner (you’ll see all of the nice blue alignment lines appear), set the Image to “present.png”, and set the View Mode to Aspect Fit.
Then add a UILabel next to the image and set the width to 202 and the height to 90 (this should fill in nicely from the edge of the image to the Disclosure Indicator, again with the alignment lines appearing). Set the number of lines to 0 and set the font to custom, Helvetica Neue Light, size 14.
One last quick step here: add a UIBarButtonItem to the Navigation Bar on the Table View and set it to the “Add” Identifier.
CKCellArrows
Ok, take a breath. Blink a few times. We have a few more steps, and then our UI will be DONE! All thanks to the power of Storyboards!
Drag out another UITableViewController and embed it inside of a Navigation Controller, just like the last one. Set the Tint on this Controller as well. Maybe a nice festive red would fit the bill?
Next, create a Segue from the + (Add) UIBarButtonItem you added to the Christmas Presents Table and Control-Drag to the new Navigation Controller. Use a Modal action again, except leave the default Transition Style (we want it to pop up like normal). Set this segue’s Identifier to “AddChristmasPresent.”
Give the new UITableViewController a title of “New Present” and add “Cancel” and “Done” Bar Button Items.
Now, select the entire Table View, set it to Static and of type Grouped. It will give you three cells to work with, but we only need two, so delete one of the extras.
Select the first table cell, expand it to 236px, set the Identifier to NewPresentImageCell, and the Selection Style to None. Add a UIImageView to the cell, size 300×236; set the Aspect Fit and Image to “present.png.”
Select the second table cell, expand it to 156px, set the Identifier to NewPresentTextCell, and the Selection Style to None. Add a UITextView to that cell, size 300×156. Set the font to Custom, Helvetica Neue Light, size 14 here as well. Set the Return key to Done, and the default text to “Enter the details of your Christmas Present here!”
CKAddPresent
Finally, we want to view the presents that we have on the List. We want to view them in the same way that we added them, so we’ll cheat here and copy the “Add Present” Table View and reuse it as the “Present Details” Table View. To do that, click on the Table View Controller inside of the New Present Scene, and copy and paste it back into the Storyboard.
CKTableCopy
You’ll notice that it is pasted overtop of the Add Present Table View. Drag it off to its own spot and create a Segue from the Christmas Presents table cell to this view, selecting Push as the action. Set the Identifier to “ChristmasDetailsSegue.”
You should see the Table View pick up the existing Navigation Controller and color. Just a few changes needed here – remove the Cancel button, change the Done button to an Action button, change the title to “Present Details,” and uncheck “Editable” in the UITextView.
There, those are all of the changes you need to reuse the “Add Present” Table View as the “Present Details” Table View!
Lastly, hit Build and Run to make sure we didn’t break anything in the process. Of course, none of the transitions will work because we haven’t finished coding them yet, but we have the scaffolding in place!

Time To Code!

Enough with the Storyboard editor for now – let’s code something before we go cross-eyed hooking up Segues and tweaking table view cells! :]
We’ll start with the first View Controller that we tweaked in the Storyboard. Create a new file with the iOS\Cocoa Touch\UIViewController subclass template, name it ChristmasRootViewController, and make it a subclass of UIViewController. Leave the bottom two options unchecked.
Open ChristmasRootViewController.h and add a property for a BOOL that will tell us whether the user is authenticated. Call it “pinValidated.” Also, since we are going to be using several text fields in this class, go ahead and add yourself as a UITextField delegate ().
#import <UIKit/UIKit.h>
 
@interface ChristmasRootViewController : UIViewController <UITextFieldDelegate>
 
@property (nonatomic) BOOL pinValidated;
 
@end
Jump over to ChristmasRootViewController.m and synthesize the property we just created:
@synthesize pinValidated;
Next add the implementations for viewDidLoad and viewDidAppear:
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.pinValidated = NO;
}
 
- (void)viewDidAppear:(BOOL)animated 
{
    [super viewDidAppear:animated];
    [self presentAlertViewForPassword];
}
Two lines of code were added here. First we initialize our pinValidated property to NO. Then, once our view is on the screen, we call a method called presentAlertViewForPassword.
What does this do? Well, exactly as the method signature indicates, we are going to prompt the user for their PIN if they have one, or require them to set one up if they don’t. Add this above those two methods:
- (void)presentAlertViewForPassword 
{
 
    // 1
    BOOL hasPin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
 
    // 2
    if (hasPin) {
        // 3
        NSString *user = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
        NSString *message = [NSString stringWithFormat:@"What is %@'s password?", user];
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Enter Password" 
                                                        message:message  
                                                       delegate:self 
                                              cancelButtonTitle:@"Cancel" 
                                              otherButtonTitles:@"Done", nil];
        // 4
        [alert setAlertViewStyle:UIAlertViewStyleSecureTextInput]; // Gives us the password field
        alert.tag = kAlertTypePIN;
        // 5
        UITextField *pinField = [alert textFieldAtIndex:0];
        pinField.delegate = self;
        pinField.autocapitalizationType = UITextAutocapitalizationTypeWords;
        pinField.tag = kTextFieldPIN;
        [alert show];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Setup Credentials" 
                                                        message:@"Secure your Christmas list!"  
                                                       delegate:self 
                                              cancelButtonTitle:@"Cancel" 
                                              otherButtonTitles:@"Done", nil];
        // 6
        [alert setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
        alert.tag = kAlertTypeSetup;
        UITextField *nameField = [alert textFieldAtIndex:0];
        nameField.autocapitalizationType = UITextAutocapitalizationTypeWords;
        nameField.placeholder = @"Name"; // Replace the standard placeholder text with something more applicable
        nameField.delegate = self;
        nameField.tag = kTextFieldName;
        UITextField *passwordField = [alert textFieldAtIndex:1]; // Capture the Password text field since there are 2 fields
        passwordField.delegate = self;
        passwordField.tag = kTextFieldPassword;
        [alert show];
    }
}
This looks like a lot of code, but actually it’s quite simple. Let’s go over it step by step.
  1. First we check if the user has set up a PIN before. We do that by checking for a BOOL that we saved to the NSUserDefaults. Notice that we are checking for a key called PIN_SAVED. That seems kind of odd – it’s actually a macro from a Constants class we will get to in a moment.
  2. Then we evaluate if we have the BOOL or not. If we do, that means the user has launched this app before and gone through the setup process. If not, then the user hasn’t been here or setup the PIN before, so let’s prompt him again.
  3. Next we get the user’s name, also stored in NSUserDefaults, and then we ask “What is [your name here]‘s password?” Notice we also assign a value to alert.tag, from an enum also in the Constants class.
  4. A great new feature in iOS5 is that we now have multiple types of UIAlertViews at our disposal! Here we are going to use the UIAlertViewStyleSecureTextInput type, which simply gives us an alert view with a text field for entering a password.
  5. Another great new thing in iOS5: out of the box, we get access to the UITextFields that are shown in an alert view! No more hacks to navigate the view hierarchy, etc. Notice that the UIAlertView class now has a nice method for us called “textFieldAtIndex,” which gives us the UITextFields available to us! Also, we can do ANYTHING that you can do on a normal UITextField – here we are setting the delegate, the capitalization settings, and the tag.
  6. Like #4 above, we are going to use another new Alert View style, but this time have two text fields for a user name and password. Again, we can customize the UITextField to our liking. So, for example, you could set the keyboard type on the Password input if you want to restrict the password to numbers only.
OK, from here let’s create our Constants class (this will house all of our Macros, enums, etc.), update our Storyboard, then hit our first real Build and Run!
For the Constants class, create a new file with the iOS\Cocoa Touch\Objective-C class template, set the class as ChristmasConstants, and make it a subclass of NSObject. For this tutorial, we only need the .h, so you can delete the .m.
Then replace ChristmasConstants.h with the following:
// Used for saving to NSUserDefaults that a PIN has been set, and is the unique identifier for the Keychain.
#define PIN_SAVED @"hasSavedPIN"
 
// Used for saving the user's name to NSUserDefaults.
#define USERNAME @"username"
 
// Used to specify the application used in accessing the Keychain.
#define APP_NAME [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]
 
// Used to help secure the PIN.
// Ideally, this is randomly generated, but to avoid the unnecessary complexity and overhead of storing the Salt separately, we will standardize on this key.
// !!KEEP IT A SECRET!!
#define SALT_HASH @"FvTivqTqZXsgLLx1v3P8TGRyVHaSOB1pvfm02wvGadj7RLHV8GrfxaZ84oGA8RsKdNRpxdAojXYg9iAj"
 
// Typedefs just to make it a little easier to read in code.
typedef enum {
    kAlertTypePIN = 0,
    kAlertTypeSetup
} AlertTypes;
 
typedef enum {
    kTextFieldPIN = 1,
    kTextFieldName,
    kTextFieldPassword
} TextFieldTypes;
Basically, all this file does is contain strings that we’ll use throughout the application.
We use enums here (remember our ChristmasKeeperRootViewController class) so that we can easily identify the Alert Type. We don’t have to remember that 0 means password only and 1 means username/password. The same goes for the TextFieldTypes enum.
Ignore the SALT_HASH meaning for now – we’ll get to that soon.
Now, go back to ChristmasRootViewController.h and import this class:
#import "ChristmasConstants.h"
Finally, jump back to the MainStoryboard.storyboard, select our first View Controller, and set its Class type under the Identity Inspector to ChristmasKeeperRootViewController (its likely still set to ViewController by default).
w00t – it’s finally time to BUILD AND RUN!!! When it starts up you should see a nice popup appear:
CKAlertSetup

Do Not Pass Alert View, Do Not Collect $200

So far so good, except there’s one small problem – it doesn’t do anything!
As you’ve probably surmised by now, we need to handle the user entering text input as well as hitting the buttons from the Alert View. Luckily, there is an easy fix to this problem – delegate methods!!
We get textFieldDidEndEditing: and alertView:didDismissButtonWithIndex: methods sent to us for FREE, and all we had to do was pay postage and shipping (and by that, I mean setting ourselves as the delegate).
Lets start with the Alert View delegate method. Add this inside ChristmasRootViewController.m:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex 
{
    if (alertView.tag == kAlertTypePIN) {
        if (buttonIndex == 1 && self.pinValidated) { // User selected "Done"
            [self performSegueWithIdentifier:@"ChristmasTableSegue" sender:self];
            self.pinValidated = NO;
        } else { // User selected "Cancel"
            [self presentAlertViewForPassword];
        }
    } else if (alertView.tag == kAlertTypeSetup) {
        if (buttonIndex == 1 && [self credentialsValidated]) { // User selected "Done"
            [self performSegueWithIdentifier:@"ChristmasTableSegue" sender:self];
        } else { // User selected "Cancel"
            [self presentAlertViewForPassword];
        }
    }
}
Remember that enum (enumerator for you more literal types) that we created from our ChristmasConstants class? Well, now it definitely comes in handy since we can easily (as reading humans) understand which Alert View we are talking about – just the password Alert View or the alert with username/password.
So first we check which view it is. Done. Then we check to see if the user hit the “Done” button AND if what was entered is correct.
If we fail that check, we prompt the user again for their credentials because we must authenticate someone. If we pass the check, however, we let them through. We do this via a performSegueWithIdentifier: sender: method, which will kick off our Cross Dissolve modal view that we created (way) earlier.
So now we have handled the Alert View. But “Wait!” you say. “How did we get pinValidated to YES, or validate the credentials the user entered?” The latter problem is simple to solve: we check for an existing username and password that has been written to the NSUserDefaults.
Add this right above alertView:didDismissWithButtonIndex:
// Helper method to congregate the Name and PIN fields for validation.
- (BOOL)credentialsValidated 
{
    NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
    BOOL pin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
    if (name && pin) {
        return YES;
    } else {
        return NO;
    }
}
That takes care of the credentials check (used for first time setup), but what about pinValidated? This is where the textField delegate method comes in to save the day – and we finally get to see some security code!
Add the following method to ChristmasRootViewController.m:
#pragma mark - Text Field + Alert View Methods
- (void)textFieldDidEndEditing:(UITextField *)textField 
{
    // 1
    switch (textField.tag) {
        case kTextFieldPIN: // We go here if this is the 2nd+ time used (we've already set a PIN at Setup).
            NSLog(@"User entered PIN to validate");
            if ([textField.text length] > 0) {
                // 2
                NSUInteger fieldHash = [textField.text hash]; // Get the hash of the entered PIN, minimize contact with the real password
                // 3
                if ([KeychainWrapper compareKeychainValueForMatchingPIN:fieldHash]) { // Compare them
                    NSLog(@"** User Authenticated!!");
                    self.pinValidated = YES;
                } else {
                    NSLog(@"** Wrong Password :(");
                    self.pinValidated = NO;
                }
            }
            break;
        case kTextFieldName: // 1st part of the Setup flow.
            NSLog(@"User entered name");
            if ([textField.text length] > 0) {
                [[NSUserDefaults standardUserDefaults] setValue:textField.text forKey:USERNAME];
                [[NSUserDefaults standardUserDefaults] synchronize];
            }
            break;
        case kTextFieldPassword: // 2nd half of the Setup flow.
            NSLog(@"User entered PIN");
            if ([textField.text length] > 0) {
                NSUInteger fieldHash = [textField.text hash];
                // 4
                NSString *fieldString = [KeychainWrapper securedSHA256DigestHashForPIN:fieldHash];
                NSLog(@"** Password Hash - %@", fieldString);
                // Save PIN hash to the keychain (NEVER store the direct PIN)
                if ([KeychainWrapper createKeychainValue:fieldString forIdentifier:PIN_SAVED]) {
                    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:PIN_SAVED];
                    [[NSUserDefaults standardUserDefaults] synchronize];
                    NSLog(@"** Key saved successfully to Keychain!!");
                }                
            }
            break;
        default:
            break;
    }
}
There’s a lot to take in here, so let’s step through it together.
  1. First we use our enum again to determine which UITextField we are talking about.
  2. Get the “hash” of our password. A “hash” is simply a fixed-length number generated from another number or string (in our case, the user’s password). We’ll discuss more about why we’re using hashes in a second.
  3. Here we invoke a method called “compareKechainValeuForMatchingPIN:” from a class called KeychainWrapper. We should implement this so that you can see what it does. :] Right now, what you need to know is that it returns a BOOL of whether the value that the user entered matches what we have saved. If it does, then pinValidated is YES, else NO.
  4. We do something similar to step #3, but this time we are generating the hash ourselves and saving that to the NSUserDefaults for our future use.
After reading the above code block, you might think to yourself, “why bother with all of this hash business, and not just use the password directly?”
Storing a user’s password directly is always a bad idea. If you do so, it gives a malicious user or attacker the potential capability of retrieving the user’s raw password, which could lead to all sorts of problems for your users.
Very Important: If you learn nothing else from this tutorial, learn this: NEVER, EVER, EVER, THEN ANOTHER EVER, store the password directly. ALWAYS use some other means of hiding the user’s password, such as a hash – it still uniquely identifies what the user entered, but is not the plain value in clear text.
The same can go for the username, but it is more generally accepted that this can be less restrictive. You also want to apply this to sensitive data like credit card numbers, Social Security numbers, dates of birth – really anything that your user may not want easily exposed.
OK, so now that you know how the overall plan works, let’s implement the security code in the KeychainWrapper class so that we can see this thing in action!
Create a new file with the iOS\Cocoa Touch\Objective-C class template, enter KeychainWrapper for the Class, and make it a subclass of NSObject. Open up KeychainWrapper.h and replace it with the following:
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <CommonCrypto/CommonHMAC.h>
 
@interface KeychainWrapper : NSObject
 
// Generic exposed method to search the keychain for a given value. Limit one result per search.
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier;
 
// Calls searchKeychainCopyMatchingIdentifier: and converts to a string value.
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier;
 
// Simple method to compare a passed in hash value with what is stored in the keychain.
// Optionally, we could adjust this method to take in the keychain key to look up the value.
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash;
 
// Default initializer to store a value in the keychain.  
// Associated properties are handled for you - setting Data Protection Access, Company Identifer (to uniquely identify string, etc).
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
 
// Updates a value in the keychain. If you try to set the value with createKeychainValue: and it already exists,
// this method is called instead to update the value in place.
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
 
// Delete a value in the keychain.
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier;
 
// Generates an SHA256 (much more secure than MD5) hash.
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash;
+ (NSString*)computeSHA256DigestForString:(NSString*)input;
 
@end
This class is going to do two things for us:
  • Handle read/writes to/from the keychain.
  • Manage our encryption generation methods.
Next switch to KeychainWrapper.m and add the implementation. I’m going to give you all the code at once (and there’s a lot of it!) but don’t worry – we’ll go over it in detail afterwards (plus it has a ton of comments):
#import "KeychainWrapper.h"
#import "ChristmasConstants.h"
 
@implementation KeychainWrapper
// *** NOTE *** This class is ARC compliant - any references to CF classes must be paired with a "__bridge" statement to 
// cast between Objective-C and Core Foundation Classes.  WWDC 2011 Video "Introduction to Automatic Reference Counting" explains this.
// *** END NOTE ***
+ (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier {
 
    // Setup dictionary to access keychain.
    NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];  
    // Specify we are using a password (rather than a certificate, internet password, etc).
    [searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    // Uniquely identify this keychain accessor.
    [searchDictionary setObject:APP_NAME forKey:(__bridge id)kSecAttrService];
 
    // Uniquely identify the account who will be accessing the keychain.
    NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
    [searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
 
    return searchDictionary; 
}
 
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier 
{
 
    NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
    // Limit search results to one.
    [searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
 
    // Specify we want NSData/CFData returned.
    [searchDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
 
    // Search.
    NSData *result = nil;   
    CFTypeRef foundDict = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &amp;foundDict);
 
    if (status == noErr) {
        result = (__bridge_transfer NSData *)foundDict;
    } else {
        result = nil;
    }
 
    return result;
}
 
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier 
{
   NSData *valueData = [self searchKeychainCopyMatchingIdentifier:identifier];
    if (valueData) {
        NSString *value = [[NSString alloc] initWithData:valueData
                                                   encoding:NSUTF8StringEncoding];
        return value;
    } else {
        return nil;
    }
}
 
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier 
{
 
    NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
    NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
    [dictionary setObject:valueData forKey:(__bridge id)kSecValueData];
 
    // Protect the keychain entry so it's only valid when the device is unlocked.
    [dictionary setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
 
    // Add.
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
 
    // If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
    if (status == errSecSuccess) {
        return YES;
    } else if (status == errSecDuplicateItem){
        return [self updateKeychainValue:value forIdentifier:identifier];
    } else {
        return NO;
    }
}
 
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier 
{
 
    NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
    NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
    NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
    [updateDictionary setObject:valueData forKey:(__bridge id)kSecValueData];
 
    // Update.
    OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
                                    (__bridge CFDictionaryRef)updateDictionary);
 
    if (status == errSecSuccess) {
        return YES;
    } else {
        return NO;
    }
}
 
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier 
{
    NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
    CFDictionaryRef dictionary = (__bridge CFDictionaryRef)searchDictionary;
 
    //Delete.
    SecItemDelete(dictionary);
}
 
 
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash 
{
 
    if ([[self keychainStringFromMatchingIdentifier:PIN_SAVED] isEqualToString:[self securedSHA256DigestHashForPIN:pinHash]]) {
        return YES;
    } else {
        return NO;
    }    
}
 
// This is where most of the magic happens (the rest of it happens in computeSHA256DigestForString: method below).
// Here we are passing in the hash of the PIN that the user entered so that we can avoid manually handling the PIN itself.
// Then we are extracting the username that the user supplied during setup, so that we can add another unique element to the hash.
// From there, we mash the user name, the passed-in PIN hash, and the secret key (from ChristmasConstants.h) together to create 
// one long, unique string.
// Then we send that entire hash mashup into the SHA256 method below to create a "Digital Digest," which is considered
// a one-way encryption algorithm. "One-way" means that it can never be reverse-engineered, only brute-force attacked.
// The algorthim we are using is Hash = SHA256(Name + Salt + (Hash(PIN))). This is called "Digest Authentication."
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash 
{
    // 1
    NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
    name = [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // 2
    NSString *computedHashString = [NSString stringWithFormat:@"%@%i%@", name, pinHash, SALT_HASH];
    // 3
    NSString *finalHash = [self computeSHA256DigestForString:computedHashString];
    NSLog(@"** Computed hash: %@ for SHA256 Digest: %@", computedHashString, finalHash);
    return finalHash;
}
 
// This is where the rest of the magic happens.
// Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA256 encryption method.
+ (NSString*)computeSHA256DigestForString:(NSString*)input 
{
 
    const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
    NSData *data = [NSData dataWithBytes:cstr length:input.length];
    uint8_t digest[CC_SHA256_DIGEST_LENGTH];
 
    // This is an iOS5-specific method.
    // It takes in the data, how much data, and then output format, which in this case is an int array.
    CC_SHA256(data.bytes, data.length, digest);
 
    // Setup our Objective-C output.
    NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
 
    // Parse through the CC_SHA256 results (stored inside of digest[]).
    for(int i = 0; i &lt; CC_SHA256_DIGEST_LENGTH; i++) {
        [output appendFormat:@&quot;%02x&quot;, digest[i]];
    }
 
    return output;
}
 
@end
By the time you get to this line of text, you may be shaking or cross-eyed, or feeling uneasy about your future as a security-conscious developer!
It’s actually not that bad. Sure, it looks intimidating, but we’ll get through it in due time. For right now, let’s concentrate on the securedSHA256DigestHashForPIN method (second to last method), which will actually create the value we are going to store in the keychain.
  1. First we get the name the user has entered. This is going to be paired with other values to uniquely create our key and add some extra security to it. Then we Percent Escape the name to avoid any attempted attacks with special characters, etc. The iOS Keychain is UTF8 compliant (only), so we baseline everything to that.
  2. Next, we piece together the user’s name, the hash of the value they entered for their password, and the SALT_HASH string from our ChristmasConstants class. A “salt hash” is just an additional hash (remember, a hash is a fixed-length number based on a number or string) that can be used to augment and enhance the security of the password. The reason that you want to add this is to prevent what’s called a “dictionary attack,” where someone has a whole list of pre-defined passwords and goes about trying every one of them against your password field. If an attacker doesn’t know your salt hash, then they can never generate your password (provided your salt hash is long enough). To add more security, it would be better if it was generated randomly each time, but that’s another day’s tutorial!
  3. Once we have our strong hash built, we pass it over to our computeSHA256DigestForString: method so that we can harden our password even more before storing it.
Now take a look at the computeSHA256DigestForString: method. It takes in a string (like the one from our securedSHA256 method) and puts it inside of a C-Style Character Array. Then it turns around and makes an NSData object of the same size in preparation of the data coming back. The digest array will actually hold our data.
Then we use a convenient CC_SHA256 method (CC being CommonCrypto, Apple’s kind-of-but-not-too-friendly cryptography framework), in which we pass in the data to be encrypted, its length, and where we want the encrypted bytes to be stored.
Once that is done, we work backwards, extract the data out of the digest array, and build a string out of it. And that is what we return.
Just a dozen lines of code (or so) is all it takes to get a pretty strongly-defended password!
One last step – go to the top of ChristmasRootViewController.m and import the KeychainWrapper class:
#import "KeychainWrapper.h"
Let’s build this bad boy one more time to see our security abilities in action!
CKStart –> CKStart2 –> CKTable1
You should be able to run it and create a username and password, and run it a second time and use the same username and password to log in. And best of all, it’s done in a secure manner!

Where to Go From Here?

Here is an example project with all of the code from the tutorial series so far.
That wraps up Part One of this tutorial. To recap, we built out 99% of our UI and workflow via Storyboards, and then we worked on prompting the user for a password.
From a security perspective, you now know a bit about how to properly handle sensitive data, as well as how to generate strongly-encrypted data in order to provide maximum protection for your users. Remember, this is what they expect!
Stay tuned for part two of the series, where we wrap up this project with a few more security features and new iOS5 APIs. We’ve got a bit more explaining to do of what’s inside of the KeychainWrapper class, but I think you’ll find it a great tool for your arsenal of reusable code!
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.

没有评论:

发表评论