Core Data Encryption

I recently wondered how to encrypt the attributes of a Core Data
application. The encryption should be transparent to the application,
which means the entity objects should do all of the encryption/
decryption work and the rest of the application doesn’t even know
that there is something happening. This way, you can continue using
bindings with Interface Builder and all the other convenient stuff
which Core Data offers without worrying about your encryption/
decryption.

You can download the Demo Project (created with Xcode 3.1 beta).
Note: The Demo Project includes the SSCrypto Framework. Please
respect it’s copyright.

But how do you actually encrypt stuff using Cocoa? Obviously Cocoa
doesn’t offer any suitable objects – but Mac OS X does. Mac OS X
ships with OpenSSL, a powerful and robust tool which offers many
important cryptographic functions. OpenSSL is also a full-featured
library (libcrypto). This library is exactly what we need to perform
encryption within a Core Data application. If you take a closer look
at the OpenSSL library functions, you may find them a bit difficult
to use (especially if you have only a basic knowledge of all the
cryptographic stuff), but furtunatly there is SSCrypto – an easy-to-use
Cocoa Framework wich wraps around OpenSSL and does all the
tricky stuff for us.

This sounds pretty good, does it? So let’s see, how to put all this
together in a Core Data application.

Core Data doesn’t support encryption as well. If you’re coding for
Leopard only, maybe you can use an Atomic Store to encrypt/decrypt
your whole Persistent Store (I haven’t tried this yet, since my apps
have to be Tiger compatible).

Instead we will use Transient Attributes to transparently encrypt the attributes of our entities.
Each of the attributes we want to encrypt must be transient and
must have a corresponding “Data Attribute” of type “Binary Data”
which is persistent and actually holds the encrypted data. In XCode
your model could look like this:

Now we need to create a NSManagedObject subclass which represents
the entity. If you create this class with the XCode wizard, don’t let it
create Objective-C 2.0 Properties since we need to add custom
accessor methods. The class interface looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface Item :  NSManagedObject  
{
}
 
- (NSString *)itemName;
- (void)setItemName:(NSString *)value;
 
- (NSData *)creationDateData;
- (void)setCreationDateData:(NSData *)value;
 
- (NSData *)itemNameData;
- (void)setItemNameData:(NSData *)value;
 
- (NSNumber *)itemId;
- (void)setItemId:(NSNumber *)value;
 
- (NSData *)itemIdData;
- (void)setItemIdData:(NSData *)value;
 
- (NSDate *)creationDate;
- (void)setCreationDate:(NSDate *)value;
 
@end

We don’t have to touch the accessors for the “Data Attributs” but the
accessors for the transient attributes need to be customized in order
to perform encryption/decryption.
Let’s take a look at -(NSString *)itemName which performs
decryption of the attribute “itemName”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (NSString *)itemName 
{
    NSString * tmpValue;
 
    [self willAccessValueForKey:@"itemName"];
    tmpValue = [self primitiveValueForKey:@"itemName"];
    [self didAccessValueForKey:@"itemName"];
 
	if(!tmpValue || [tmpValue isEqualToString:@""]) {
		NSData *encryptedData = [self valueForKey:@"itemNameData"];
 
		/* if there are any encrypted data, decrypt it */
		if(encryptedData) {
			/* init SSCrypto with the stored key from the PasswordController */
			SSCrypto *crypto = [[SSCrypto alloc] initWithSymmetricKey:[[PasswordController sharedInstance] keyData]];
			[crypto setCipherText:[NSKeyedUnarchiver unarchiveObjectWithData:encryptedData]];
 
			NSData *decryptedData = [crypto decrypt:@"aes256"];
 
			/* some debugging output. maybe useful. you can wrap them with #ifdef/#endif directives to enable them only in debug configuration */
			NSLog(@"Using symmetric key: %@",		[[crypto symmetricKey] hexval]);
			NSLog(@"Archived itemName: %@",			[encryptedData hexval]);
			NSLog(@"Encrypted itemName: %@",		[[crypto cipherTextAsData] hexval]);
			NSLog(@"Decrypted itemName: %@",		[crypto clearTextAsString]);
 
			tmpValue = [[NSString alloc] initWithData:decryptedData	encoding:NSUTF8StringEncoding];
			[self setPrimitiveValue:tmpValue forKey:@"itemName"];
 
			[crypto release];
		}
 
		else {
			/* insert the defalt value here */
			tmpValue = @"Default Value";
			[self setItemName:tmpValue];
		}
	}
 
    return tmpValue;
}

Basically, this method loads the encrypted data (if it exists) and
decrypts it. The decrypted data is stored in the primitive value so
it doesn’t need to be decrypted each time it is accessed – only when
it is read for the first time. If there is no encrypted data (a new
entity is inserted), then the default value is used.

The set accessor for the itemName attribute works in a
similar way. It performs the following steps:

  • If the new value equals the old one, return. This avoids any
    unnecessary encryption which dramatically reduces cpu load, since
    Core Data acesses the attributes quite heavily

  • Store the new value in the primitive value
  • Encrypt the new value and store it in the “Data Attribute”

    Here’s the code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    - (void)setItemName:(NSString *)value 
    {
        NSString * tmpValue;
     
        [self willAccessValueForKey:@"itemName"];
        tmpValue = [self primitiveValueForKey:@"itemName"];
        [self didAccessValueForKey:@"itemName"];
     
    	/* if the new value is equal to the old value, return. this improves the performance a lot, since core data
    	 accesses the attributes quite heavily and encrypting them would stress the cpu when it's not really necessary*/
        if([tmpValue isEqualToString:value])
    	return;
     
        [self willChangeValueForKey:@"itemName"];
        [self setPrimitiveValue:value forKey:@"itemName"];
        [self didChangeValueForKey:@"itemName"];
     
        SSCrypto *crypto = [[SSCrypto alloc] initWithSymmetricKey:[[PasswordController sharedInstance] keyData]];
        [crypto setClearTextWithData:[value dataUsingEncoding:NSUTF8StringEncoding]];
        NSData *encryptedData = [crypto encrypt:@"aes256"];
     
        NSLog(@"Encrypting itemName: %@",				value);
        NSLog(@"Encrypting itemName: %@ with key: %@",	value, [[crypto symmetricKey] hexval]);
        NSLog(@"Encrypted itemName: %@",				[encryptedData hexval]);
        NSLog(@"Archived itemName: %@",					[[NSKeyedArchiver archivedDataWithRootObject:encryptedData] hexval]);
     
    	/* archiving the encrypted value ensures, that it can be saven as plain-text xml */
        [self setPrimitiveValue:[NSKeyedArchiver archivedDataWithRootObject:encryptedData] forKey:@"itemNameData"];
     
        [crypto release];		
    }

    In prinziple, this is how the encryption works. But what key is
    used to encrypt the data and where does it come from? In our
    example the key is entered by the user. I have created a class
    called PasswordController which stores the key data.
    This class is designed to be a singleton because only one instance
    of it is needed within the whole application.

    When the application is launched, I use the
    -applicationDidFinishLaunching method to request the
    key from the user and store the data in the PasswordController.
    When you take a look at the console, while you launch the app,
    you will notice, that there is happening a lot of stuff before
    the user has entered the key. This is because Core Data accesses
    the attributes right after the Nib-Files have been loaded. But the
    -applicationDidFinishLaunching method is called after that.
    I haven’t found any issues with this yet. If you find any problems,
    please let me know.

    Another aspect I’d like to mention is, that the demo application
    cannot determine if the user has entered the wrong key. In that
    case, the decryption simply does not work and any newly created
    items are encrypted with the other key, which produces a
    currupted Persistent Store (it’s not really “damaged”, but unusable.
    Try it, you’ll see what I mean).

    This issue coud be addressed by including a digest (sha1, or
    something) of the key in the Persistent Store and compare it each
    time the user has entered the key. The Metadata Dictionary of the
    persistent Store would be a good place to put it in. This is not yet
    included in the demo application since I didn’t find the time yet.
    Maybe I will do it in some future version. Sorry for that!

  • This entry was posted in Core Data and tagged , , , . Bookmark the permalink.

    Warning: count(): Parameter must be an array or an object that implements Countable in /home/httpd/vhosts/shrdlu.ch/shrdlu.ch/wp-includes/class-wp-comment-query.php on line 405

    5 Responses to Core Data Encryption

    1. David says:

      There are two other, possibly better, ways to achieve this. First, you can subclass awakeFromFetch and fill in all your transient values in one place. It would be a lot less code.

      An even cleaner solution is to place your decryption in a custom NSValueTransformer. Instead of creating a shadow transient attribute you just set the attribute’s type to “transformable” in the model editor and specify the name of your reversible decrypt/encrypt transformer. In theory, that’s all you would have to do. You wouldn’t even have to subclass NSManagedObject.

    2. Brian says:

      Hi,

      Just wondering what is the best way to encrypt/decrypt core data used in a iPhone project. I saw you using SSCrypto but don’t know if it works for iPhone apps.

      Any help is appreciated
      Thanks

    3. michael says:

      Hi,

      I haven’t testet Core Data on the iPhone nor did I test SSCrypro. I’m not sure if it is really necessary to encrypt a data store on the iPhone because of the application’s sandbox. It should not be possible to read the files without hacking (Jailbreak) your iPhone.

      Cheers,
      Michael

    4. Grant says:

      There are still apps that need to encrypt data on the iPhone because hacking stolen iPhones is a real possibility. Working on a medical app currently. So far we have avoided storing any possibly sensitive data but if we do have store some sensitive data I will be trying out your code on the iPhone.

      Cheers,
      Grant

    5. michael says:

      You’re right Grant. This is a serious issue and encrypting your data is one way to minimize the impact.
      Please let me know, what you experience with Core Data encryption on the iPhone.

      Cheers,
      Michael

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.