Core Data + Passwords in Keychain

In an iPhone app project I’ve been working on recently, we needed to store sensitive information in a Core Data database. Without going into too much detail, let’s say we wanted to fill an Account table with usernames and passwords, but, of course, we couldn’t just store the passwords in clear text in the database.

Researching about best practices to do this, I came across this little great article. According to the author, “the keychain is about the only place that an iPhone application can safely store data”, referring to the Keychain Services in iOS. The author also provides some useful methods to make accessing the Keychain less of a pain.

Based on that article, I’ll show you an example of how to combine both Core Data with the Keychain to store passwords securely and seamlessly in NSManagedObject classes.

Store everything in Core Data, except for sensitive information

The key is to create a Core Data entity with all the attributes you need except for the sensitive ones. In this example, we have an entity called Account with two attributes: username and password. But, instead of adding both of these attributes in the Core Data entity, we’ll only add the username attribute

When that’s ready, we create a class to manage the entity, named AccountBase. The class looks like this:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface AccountBase : NSManagedObject

@property (nonatomic, retain) NSString *username;

@end
#import "AccountBase.h"

@implementation AccountBase

@dynamic username;

@end

We called this class AccountBase because we’ll then create another class called Account that extends from it. This way, we avoid overwriting our class and losing changes if we modify the data model and need to regenerate the AccountBaseclass.

So, create a class, call it Account, make it inherit from AccountBase, and add an NSString property called password:

#import "AccountBase.h"

@interface Account : AccountBase

@property (nonatomic, assign) NSString *password;

@end
#import "Account.h"

@implementation Account

- (NSString*)password
{
}

- (void)setPassword:(NSString*)aPassword
{
}

@end

As you can see, the Account class now has an additional property named password. The Keychain will then be used inside the setter and getter of this property to store the password securely.

But before we do that, let’s go ahead and create a KeychainHelper class (based on the article mentioned above).

Keychain Helper

We’ll add a new class named KeychainHelper which will provide class methods to set, retrieve, and delete passwords given a key or identifier. The identifier would be the primary key of your entity, which, in this case, is the username.

Before we can do that, we must add a reference to the “Security.framework” framework in our project.

This is how the KeychainHelper class looks:


@interface KeychainHelper : NSObject

+ (NSString*)getPasswordForKey:(NSString*)aKey;
+ (void)setPassword:(NSString*)aPassword forKey:(NSString*)aKey;
+ (void)removePasswordForKey:(NSString*)aKey;

@end

As for the implementation, we’ll use a slightly modified version of the example shown in the article mentioned above. We’ll store and retrieve passwords based on a given key, using a constant service name. This is just an identifier used to isolate your stored values from other applications. The implementation file looks like this. Notice that we need to import the <Security/Security.h> header file:

#import "KeychainHelper.h"
#import <Security/Security.h>

@interface KeychainHelper ()
+ (NSMutableDictionary*)dictionaryForKey:(NSString*)aKey;
@end

@implementation KeychainHelper

static const NSString *SERVICE_NAME = @"com.domain.myapplication";

+ (NSMutableDictionary*)dictionaryForKey:(NSString*)aKey
{
    NSData *encodedKey = [aKey dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];

    [searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    [searchDictionary setObject:encodedKey forKey:(id)kSecAttrGeneric];
    [searchDictionary setObject:encodedKey forKey:(id)kSecAttrAccount];
    [searchDictionary setObject:SERVICE_NAME forKey:(id)kSecAttrService];

    return searchDictionary;
}

+ (NSString*)getPasswordForKey:(NSString*)aKey
{
    NSString *password = nil;

    NSMutableDictionary *searchDictionary = [self dictionaryForKey:aKey];
    [searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    [searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

    NSData *result = nil;
    SecItemCopyMatching((CFDictionaryRef)searchDictionary, (CFTypeRef*)&result);

    if (result)
    {
        password = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
        [result release];
    }
    return password;
}

+ (void)removePasswordForKey:(NSString*)aKey
{
    NSMutableDictionary *keyDictionary = [self dictionaryForKey:aKey];
    SecItemDelete((CFDictionaryRef)keyDictionary);
}

+ (void)setPassword:(NSString*)aPassword forKey:(NSString*)aKey
{
    [KeychainHelper removePasswordForKey:aKey];

    NSData *encodedPassword = [aPassword dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableDictionary *keyDictionary = [self dictionaryForKey:aKey];
    [keyDictionary setObject:encodedPassword forKey:(id)kSecValueData];
    SecItemAdd((CFDictionaryRef)keyDictionary, nil);
}

@end

As you can see, there’s an additional private method called dictionaryForKey:, which creates the basic dictionary necessary to execute queries in the Keychain. The rest of the code just inserts, retrieves, and deletes the values using the Security framework.

Now, back to our password getter and setter…

Implementing the password property

The idea is to set and retrieve the password of the User using the KeychainHelper class, instead of using Core Data. To do this, we simply implement the getter and setter like this:

#import "Account.h"
#import "KeychainHelper.h"

@implementation Account

- (NSString*)password
{
    if (self.username)
        return [KeychainHelper getPasswordForKey:self.username];
    return nil;
}

- (void)setPassword:(NSString*)aPassword
{
    if (self.username)
        [KeychainHelper setPassword:aPassword forKey:self.username];
}

@end

In addition, we must remove the password stored for that account from the Keychain when the entity is deleted. To do this, we need to override the prepareForDeletion method of the Account class to ask the KeychainHelper to remove the password:

#import "Account.h"
#import "KeychainHelper.h"

@implementation Account

- (NSString*)password
{
    if (self.username)
        return [KeychainHelper getPasswordForKey:self.username];
    return nil;
}

- (void)setPassword:(NSString*)aPassword
{
    if (self.username)
        [KeychainHelper setPassword:aPassword forKey:self.username];
}

- (void)prepareForDeletion
{
    if (self.username)
        [KeychainHelper removePasswordForKey:self.username];
}

@end

And that’s it! Now we can use the Account entity’s password attribute just as we would use any regular attribute, knowing that it is securely stored.

Reference:

Advertisements

9 thoughts on “Core Data + Passwords in Keychain

  1. Hello, thanks for the awesome writeup. I followed all the instructions to the “T”, but I am getting all sorts of errors when I created the KeychainHelper class. The errors are showing up in the implementation file. And when I mean errors, I mean red warnings next to certain lines of code in the implementation file. If you think you could help me with this I would really appreciate it.

      • Jose,

        I think the problem is that I enabled ARC when setting up my project. I am getting several ARC Casting Rules “error messages” in my KeychainHelper.m file. Xcode seems to provide a fix-it solution, but I haven’t tried it yet. thanks again for the great tutorial.

  2. Hi, this is a great starter, I found an ARC-version and followed your steps, but I have a question… How do you now instantiate an object? If you are using core data you must of course use the insertNewObjectForEntityForName method to do so, but you can’t do this with the Account class because it doesn’t have a valid mapping in the store…?

    • Hey Stephen. I believe what you have to do is set “Account” as the “Class” name in your Core Data entity, instead of “AccountBase”. Maybe that’s what’s going on? Let me know 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s