Using Keychain to Secure Data - iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

Chapter 18. Using Keychain to Secure Data

Securing sensitive user data is a critical and often-overlooked step of software development. The technology news is constantly plagued by stories of large companies storing password or credit card information in plain text. Users put their trust in developers to treat sensitive information with the care and respect it deserves. This includes encrypting local copies of that information to prevent unauthorized access. It is the duty of every developer to treat users’ data as they would like their own secure information to be handled.

Apple has provided a security framework called Keychain to store encrypted information on an iOS device. The Keychain also has several additional benefits beyond standard application and data security. Information stored in the Keychain persists even after an app has been deleted from the device, and Keychain information can even be shared among multiple apps by the same developer.

This chapter demonstrates the use of Apple’s KeychainItemWrapper class to secure and retrieve sensitive information. Although it is completely acceptable and occasionally required to write a Keychain wrapper from the ground up, leveraging Apple’s libraries can be a tremendous timesaver and will often provide all the functionality required. This chapter does not cover creating a custom Keychain wrapper class but leverages Apple’s provided code to quickly add Keychains to an iOS app.


Tip

The most up-to-date version of Apple’s KeychainItemWrapper class can be found at http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html.


It is important to remember that while securing information on disk, it is only a small part of complete app security; other factors such as transmitting data over a network and password enforcement are just as critical in providing a well-rounded secure app.

Introduction to the Sample App

The Keychain sample app is a single-view app that will secure a credit card number along with relevant user information, such as name and expiration date. To access the information, the user sets a PIN on first launch. Both the PIN and the credit card information are secured using the Keychain.


Note

The Keychain does not work on the iOS simulator as of iOS 7. The wrapper class provided by Apple and used in this chapter does make considerable efforts to properly simulate the Keychain behaviors. In addition, since code being executed on the simulator is not code signed, it is important to keep in mind that there are no restrictions on which apps can access Keychain items. It is highly recommended that Keychain development be thoroughly debugged on the device after it’s working correctly on the simulator.


The sample app itself is really quite simple. It consists of four text fields and a button. The majority of the sample code not directly relating to the Keychain handles laying out information.


Note

Deleting the app from a device does not remove the Keychain for that app, which can make debugging considerably more difficult. The simulator does have a Reset Contents and Settings option, which will wipe the Keychain. It is highly recommended that a Keychain app not be debugged on a device until it is functional on the simulator.


Setting Up and Using Keychain

Keychain is part of the Security.framework and has been available for iOS starting with the initial SDK release. Keychain has its roots in Mac OS X development, where it was first introduced with OS X 10.2. However, Keychain’s history predates even OS X with roots back into OS 8.6. Keychain was initially developed for Apple’s email system called PowerTalk.

Keychain can be used to secure small amounts of data such as passwords, keys, certificates, and notes. However, if an app is securing large amounts of information such as encoded images or videos, implementing a third-party encryption library is usually a better fit than Keychain. Core data also provides encryption capabilities, and is worth exploring if the app will be Core Data–based.

Before working with Keychain, the Security.framework must be added to the project and <Security/Security.h> needs to be imported to any classes directly accessing Keychain methods and functions.

Setting Up a New KeychainItemWrapper

On iOS Keychains are unlocked based on the code signing of the app that is requesting it. Since there is no systemwide password as seen on OS X, there needs to be an additional step to secure data. Since the app controls which Keychain data can be accessed to truly secure information, the app itself should be password protected. This is done through the sample app using a PIN entry system.

When the app is launched for the first time, it will prompt the user to enter a new PIN and repeat it. To securely store the PIN, a new KeychainItemWrapper is created.

pinWrapper = [[KeychainItemWrapper alloc]initWithIdentifier:@"com.ICF.Keychain.pin" accessGroup:nil];

Creating a new KeychainItemWrapper is done using two attributes. The first attribute is an identifier for that Keychain item. It is recommended that a reverse DNS approach be used here such as com.company.app.id. accessGroup is set to nil in this example, and theaccessGroup parameter is used for sharing Keychains across multiple apps. Refer to the section “Sharing a Keychain Between Apps” for more information on accessGroups.

The next attribute that should be set on a new KeychainItemWrapper is the kSecAttrAccessible. This controls when the data will be unlocked. In the sample app the data becomes available when the device is unlocked, securing the data for a locked device. There are several possible options for this parameter, as detailed in Table 18.1.

[pinWrapper setObject:kSecAttrAccessibleWhenUnlocked forKey: (id)kSecAttrAccessible];

Image

Table 18.1 All Possible Constants and Associated Descriptions to Be Supplied to kSecAttrAccessible

The app now knows the identifier for the Keychain as well the security level that is required of it. However, an additional parameter needs to be set before data can begin to be stored. The kSecAttrService is used to store a username for the password pair that will be used for the PIN. A PIN does not have an associated password; for the purposes of the sample app, pinIdentifer is used here. Although Keychains will often work while the kSecAttrService is omitted, having a value set here corrects many hard-to-reproduce failures.

[pinWrapper setObject:@"pinIdentifer" forKey: (id)kSecAttrAccount];

Storing and Retrieving the PIN

After a new KeychainItemWrapper has been configured in the manner described in the preceding section, data can be stored into it. Storing information in a Keychain is very similar to storing data in a dictionary. The sample app first checks to make sure that both of the PIN text fields match; then it calls setObject: on the pinWrapper that was created in the preceding section. For the key identifier kSecValueData is used. This item is covered more in depth in the section “Keychain Attribute Keys”; for now, however, it is important to use this constant.

if([pinField.text isEqualToString: pinFieldRepeat.text])
{
[pinWrapper setObject:[pinField text] forKey:kSecValueData];
}

After a new value has been stored into the Keychain, it can be retrieved in the same fashion. To test whether the user has entered the correct PIN in the sample app, the following code is used:

if([pinField.text isEqualToString: [pinWrapper objectForKey:kSecValueData]])

After the PIN number being entered has been confirmed as the PIN number stored in the Keychain, the user is allowed to access the next section of the app, described in the section “Securing a Dictionary.”

Keychain Attribute Keys

Keychains are stored much like NSDictionaries; however, they have very specific keys that can be associated with them. Unlike an NSDictionary, a Keychain cannot use any random string for a key value. Each Keychain is associated with a Keychain Class; if using Apple’sKeychainItemWrapper, it defaults to using CFTypeRef kSecClassGenericPassword. However, other options exist for kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, and kSecClassIdentity. Each class has different associated values attached to it. For the purposes of this chapter as well as for the KeychainItemWrapper, the focus will be on kSecClassGenericPassword.

kSecClassGenericPassword contains 14 possible keys for storing and accessing data, as described in Table 18.2. It is important to keep in mind that these keys are optional and are not required to be populated in order to function correctly.

Image

Image

Table 18.2 Keychain Attribute Keys Available When Working with kSecClassGenericPassword

Securing a Dictionary

Securing a more complex data type such as a dictionary follows the same approach taken to secure the PIN in earlier sections. The Keychain wrapper only allows for the storage of strings; to secure a dictionary, it is first turned into a string. The approach chosen for the sample code is to first save the dictionary to a JSON string using the NSJSONSerialization class. (See Chapter 7, “Working with and Parsing JSON,” for more info.)

NSMutableDictionary *secureDataDict = [[[NSMutableDictionary alloc] init] autorelease];

NSError *error = nil;

if(numberTextField.text)
[secureDataDict setObject:numberTextField.text forKey:@"numberTextField"];

if(expDateTextField.text)
[secureDataDict setObject:expDateTextField.text forKey:@"expDateTextField"];

if(CV2CodeTextField.text)
[secureDataDict setObject:CV2CodeTextField.text forKey:@"CV2CodeTextField"];

if(nameTextField.text)
[secureDataDict setObject:nameTextField.text forKey:@"nameTextField"];

NSData *rawData = [NSJSONSerialization dataWithJSONObject:secureDataDict
options:0
error:&error];

if(error != nil)
{
NSLog(@"An error occurred: %@", [error localizedDescription]);
}

NSString *dataString = [[[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding] autorelease];

After the value of the dictionary has been converted into a string representation of the dictionary data, it can be added to the Keychain in the same fashion as previously discussed.

KeychainItemWrapper *secureDataKeychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.ICF.keychain.securedData" accessGroup:nil];

[secureDataKeychain setObject:@"secureDataIdentifer" forKey: (id)kSecAttrAccount];

[secureDataKeychain setObject:kSecAttrAccessibleWhenUnlocked forKey: (id)kSecAttrAccessible];

[secureDataKeychain setObject:dataString forKey:kSecValueData];

[secureDataKeychain release];

To retrieve the data in the form of a dictionary, the steps must be followed in reverse. Starting with an NSString from the Keychain, it is turned into a NSData value. The NSData is used with NSJSONSerialization to retrieve the original dictionary value. After the dictionary is re-created, the text fields that display the user’s credit card information are populated.

KeychainItemWrapper *secureDataKeychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.ICF.keychain.securedData" accessGroup:nil];

NSString *secureDataString = [secureDataKeychain objectForKey:kSecValueData];

if([secureDataString length] != 0)
{
NSData* data = [secureDataString dataUsingEncoding:NSUTF8StringEncoding];

NSError *error = nil;

NSDictionary *secureDataDictionary = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];

if(error != nil)
{
NSLog(@"An error occurred: %@", [error localizedDescription]);
}

numberTextField.text = [secureDataDictionary objectForKey:@"numberTextField"];

expDateTextField.text = [secureDataDictionary objectForKey:@"expDateTextField"];

CV2CodeTextField.text = [secureDataDictionary objectForKey:@"CV2CodeTextField"];

nameTextField.text = [secureDataDictionary objectForKey:@"nameTextField"];
}

else
{
NSLog(@"No Keychain data stored yet");
}

Resetting a Keychain Item

At times it might be necessary to wipe out the data in a Keychain while not replacing it with another set of user data. This can be done using Apple’s library by invoking the resetKeyChainItem method on the Keychain wrapper that needs to be reset.

[pinWrapper resetKeychainItem];

Sharing a Keychain Between Apps

A Keychain can be shared across multiple iOS apps if they are published by the same developer and under certain conditions. The most important requirement for sharing Keychain data between two apps is that both apps must have the same bundle seed. For example, consider two apps with the bundle identifiers 659823F3DC53.com.ICF.firstapp and 659823F3DC53.com.ICF.secondapp. These apps would be able to access and modify each other’s Keychain data. Keychain sharing with a wildcard ID does not seem to work, although the official documentation remains quiet on this situation. Bundle seeds can be configured from the developer portal when new apps are created.

When you have two apps that share the same bundle seed, each app will need to have its entitlements configured to allow for a Keychain access group. Starting with Xcode 4.5, Keychain Groups are configured from the summary tab of the target, as shown in Figure 18.1. Before Xcode 4.5, the bundle ID with seed needed to be added to the entitlements file under the array for the key keychain-access-groups. When using the newer Keychain Group entries, you should omit the bundle seed.

Image

Figure 18.1 Setting up a new Keychain Group using Xcode 4.5 or newer.

For the shared Keychain to be accessed, the Keychain group first needs to be set. With a modification of the PIN example from earlier in the chapter, it would look like the following code snippet:

[pinWrapper setObject:@"659823F3DC53.com.ICF.appgroup" forKey:(id)kSecAttrAccessGroup];


Note

Remember that when setting the access group in Xcode, you do not need to specify the bundle seed. However, when you are setting the kSecAttrAccessGroup property, the bundle seed needs to be specified and the bundle seed of both apps must match.


After an access group has been set on a KeychainItemWrapper, it can be created, modified, and deleted in the typical fashion discussed throughout this chapter.

Keychain Error Codes

The Keychain can return several specialized error codes depending on any issues encountered at runtime. These errors are described in Table 18.3.

Image

Table 18.3 Keychain Error Codes and Their Descriptions

Summary

This chapter covered using Keychain to secure small amounts of app data. The sample app covered setting and checking a PIN number for access to an app on launch. It also covered the storage and retrieval of multiple fields of credit card data.

Keychain and data security is a large topic, and this chapter merely touches the tip of the iceberg. The development community is also seeking security professionals, especially in the mobile marketplace. Keychain is an exciting and vast topic that should now be much less intimidating. Hopefully this introduction to securing data with Keychain will set you as a developer down a path of conscious computer security and prevent yet another story in the news about the loss of confidential information by a careless developer.

Exercises

1. Modify the sample app to not only store one set of credit card data but also allow the user to create and retrieve multiple cards.

2. Create two new apps that share a Keychain Group, and experiment with sharing Keychain data between the two apps.