In order to store private data in an iOS Core Data database, there are several methods available for encryption, including:
- iOS-level data protection based on the device passcode
- open source projects like SQLCipher for iOS that encrypt the database file
However, neither of these options is sufficient for if you need to use multiple encryption keys, encrypt only certain attributes, or preserve decrypted data while the device is locked.
Encryption Transformer Class
Instead, it’s fairly simple and straight-forward to perform lazy decryption on only certain database fields using the special Transformable Core Data attribute type. Transformable attributes are configured with an NSValueTransformer subclass that you write that specifies:
- a method for converting one object into another
- an optional method for reversing that process (and whether reversing is supported)
- the class of an object after transformation
Here’s an example class that converts from a decrypted NSData object to an encrypted one. It relies on a key method to get the encryption key, and a couple NSData category methods that perform AES-256 encryption and decryption.
EncryptionTransformer.h
@interface EncryptionTransformer : NSValueTransformer
{}
/**
* Returns the key used for encrypting / decrypting values during transformation.
*/
- (NSString*)key;
@end
EncryptionTransformer.m
@implementation EncryptionTransformer
+ (Class)transformedValueClass
{
return [NSData class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (NSString*)key
{
// Your version of this class might get this key from the app delegate or elsewhere.
return @"secret key";
}
- (id)transformedValue:(NSData*)data
{
// If there's no key (e.g. during a data migration), don't try to transform the data
if (nil == [self key])
{
return data;
}
if (nil == data)
{
return nil;
}
return [data dataAES256EncryptedWithKey:[self key]];
}
- (id)reverseTransformedValue:(NSData*)data
{
// If there's no key (e.g. during a data migration), don't try to transform the data
if (nil == [self key])
{
return data;
}
if (nil == data)
{
return nil;
}
return [data dataAES256DecryptedWithKey:[self key]];
}
@end
The key method might get the secret key from any number of places, such as a password requested at login and stored in the app delegate. I’ll touch on the NSData category methods that actually perform the encryption in a little bit.
This encryption class can then easily be subclassed to handle different data types, like strings, dates, numbers, etc. Here’s an example string encryption class that converts between NSString and NSData in order to use its parent class transformation methods.
EncryptionTransformer.h
@interface StringEncryptionTransformer : EncryptionTransformer
{}
EncryptionTransformer.m
@implementation StringEncryptionTransformer
+ (Class)transformedValueClass
{
return [NSString class];
}
- (id)transformedValue:(NSString*)string
{
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
return [super transformedValue:data];
}
- (id)reverseTransformedValue:(NSData*)data
{
if (nil == data)
{
return nil;
}
data = [super reverseTransformedValue:data];
return [[[NSString alloc] initWithBytes:[data bytes]
length:[data length]
encoding:NSUTF8StringEncoding]
autorelease];
}
@end
Once these classes are set up, the Core Data model editor lets you assign an entity attribute type of Transformable and a name of the NSValueTransformer class, such as StringEncryptionTransformer.
Now, these attributes can be written to in code just like any other (e.g. clark.secretIdentity = @"superman"
), but when they are persisted to the underlying SQLite database (or other sort of data store), the appropriate NSValueTransformer class will be called to encrypt the values before writing them to the data store.
Likewise, at the time a persisted object is read from the data store, the NSValueTransformer class will decrypt it. Encryption and decryption is thus lazy and only performed when an object is used or updated — no need to decrypt an entire file or database during app startup.
AES-256 encryption category
There are a number of example classes that make performing AES-256 encryption as simple as shown above, such as Jim Dovey’s NSData+CommonCrypto category and this unattributed snippet. The better ones use encryption libraries provided by Apple, which may (or may not, IANAL) mean that you don’t need a CCATS form for app submission.
However, one major caveat to encrypting attributes individually is that patterns from short, repeated values will naturally rise. If you are encrypting only a couple different possible values for an attribute (e.g. "superhero" and "evildoer"), simple encryption using the same key will result in only two encrypted values. That is, for the key "top secret
", the value "Superman
" will always encrypt to "?b64JzJ4aC0IhKaf7xeTWaglC6L/3VFxwA5XVrfDRntxebO4rFUSdNNrzfVFIU3yZH0F?64b
" (Javascrypt is a handy online tool for encryption).
Enter the initialization vector (IV), which like the salt for a password helps hide patterns by randomizing encryption input. The IV should be:
- a random number, for example the result of arc4random()
- different for every attribute, or at least every entity
- stored alongside the encrypted value, since the same IV used for encryption is necessary for decryption
- public, that is it need not (and should not) be encrypted
Unfortunately, many example code snippets that call CCCrypt (including one linked above) leave the initialization vector parameter as simply:
NULL / initialization vector (optional) /,
Rather than follow suit, it’s simple to update our EncryptionTransformer to generate a IV and prepend it to the encrypted data:
EncryptionTransformer.mEncryptionTransformer.m
- (id)transformedValue:(NSData*)data
{
...
// Use another NSData category method (left as an exercise
// to the reader) to randomly generate the IV data
NSData* iv = [NSData randomDataOfLength:32];
data = [data dataAES256EncryptedWithKey:[self key] Iv:iv];
// Return a data object that includes the IV with the
// encrypted data appended
NSMutableData* mutableData = [NSMutableData dataWithData:iv];
[mutableData appendData:data];
return mutableData;
}
- (id)reverseTransformedValue:(NSData*)data
{
...
// The IV was stored in the first 32 bytes of the data
NSData* iv = [data subdataWithRange:NSMakeRange(0, 32)];
// Remove the IV from the encrypted data and decrypt it
NSMutableData* mutableData = [NSMutableData dataWithData:data];
[mutableData replaceBytesInRange:NSMakeRange(0, 32) withBytes:NULL];
return [mutableData dataAES256DecryptedWithKey:[self key] Iv:iv];
}
Now, every time an attribute value is saved, its encrypted version includes an extra stage of randomization that prevents comparisons between different attributes encrypted with the same key.
Thus, with fairly minimal code that ensures resilient encryption, we have an easy way to store private information in Core Data attributes with as many different keys as necessary.