Encrypt/decrypt .plist file ios
Using the code at https://web.archive.org/web/20150612123348/http://blog.objectgraph.com/index.php/2010/04/20/encrypting-decrypting-base64-encode-decode-in-iphone-objective-c/ (the link that you provided in comment), you can encrypt your plist by:
NSData *plistFileData = [NSData dataWithContentsOfFile:plistPath];
NSData *encryptedData = [plistFileData AESEncryptWithPassphrase:password];
[encryptedData writeToFile:encryptedPath atomically:YES];
plistPath is a NSString containing the path to the plist file you want to encrypt
password is the encryption key you would like to use
encryptedPath is where you want to save the encrypted file
to decrypt:
NSData *encryptedData = [NSData dataWithContentsOfFile:encryptedPath];
NSData *plistFileData = [plistFileData AESDecryptWithPassphrase:password];
[plistFileData writeToFile:plistPath atomically:YES];
encryptedPath is a NSString containing the path to the encrypted plist file
password is the encryption key you would like to use
plistPath is where you want to save the decrypted plist file
The link provided by howanghk contains code with a bug. Apply fix provided by InoriXu on that webpage to resolve the issue. You have to modify both encrypt and decrypt functions.
So after a line:
const char *password = [pass UTF8String];
add:
const int passwordLen = [pass length];
And change line:
key[i] = password != 0 ? *password++ : 0;
into:
key[i] = i < passwordLen != 0 ? *password++ : 0;
The code itself still adds some space padding behind, but if you need it to encrypt a property list, you'll be fine.
Here’s a very simple answer to this, hope this simplifies the problem, if any;
First of you need to download the NSData+AES files for here. You just need the NSData+AES.h & NSData+AES.m along with cipher.h & cipher.m files. Once in custody, add the files to your Xcode project and remove #import Cocoa/Cocoa.h> header from NSData+AES.h and cipher.h (only for those who intend to program for iOS, if for MacOS please let the header be). Import NSData+AES.h in your file where you fetch and write your plist file.
Now that the initial basics have been laid down, we take on the usage of these important files. What you need to understand is that the way you want to decrypt and encrypt your data. At the first run you need to copy your plist to the documents folder and then encrypt it. Note, if you copy it and try to decrypt it straight it will throw and exception, so to cater that, we’ll use a UserDefaults Boolean value and skip the decryption on the first run. Also you need to define a preprocessor directive constant string to entertain the secret key for encryption and decryption. Here’s what you’ll have in your DataHandler class;
#import <Foundation/Foundation.h>
#import "NSData+AES.h"
#define MY_SECRET_KEY @"MY_SECRET_KEY"
static NSMutableDictionary *dataDictionary_ = nil;
static NSMutableDictionary *allSettings_ = nil;
@implementation DataHandler
- (id)init
{
if(self = [super init])
{
[self copyPlistData];
}
return self;
}
// Encrypt File
- (NSData*)encryptFile:(NSMutableDictionary *)plistDict
{
NSError *err = nil;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 options:0 error:&err];
NSData *file = [data encryptWithString:MY_SECRET_KEY];
return file;
}
// Decrypt File
- (NSMutableDictionary *)decryptFile:(NSData *)data
{
NSError *err = nil;
NSData* newData = [data decryptWithString:MY_SECRET_KEY];
NSPropertyListFormat format;
NSMutableDictionary *file = [NSPropertyListSerialization propertyListWithData:newData options:NSPropertyListMutableContainersAndLeaves format:&format error:&err];
return file;
}
- (void) copyPlistData
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent: @"myData.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL fileExists = [fileManager fileExistsAtPath:path];
//check if the file exists already in users documents folder
//if file does not exist copy it from the APPLICATION bundle Plist file
if (!fileExists)
{
NSLog(@"copying database to users documents");
NSString *pathToSettingsInBundle = [[NSBundle mainBundle] pathForResource:@"mydata" ofType:@"plist"];
BOOL copySuccess = [fileManager copyItemAtPath:pathToSettingsInBundle toPath:path error:&error];
if(copySuccess)
{
noCopyError_ = YES;
}
}
//if file is already there do nothing
else
{
noCopyError_ = YES;
NSLog(@"users database already configured");
}
BOOL firstRun = [[NSUserDefaults standardUserDefaults] boolForKey:@"IS_FIRST_RUN"];
if(noCopyError_ && firstRun)
{
dataDictionary_ = [self decryptFile:[NSData dataWithContentsOfFile:path]];
}
else
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"IS_FIRST_RUN"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"mydata.plist"];
dataDictionary_ = (NSMutableDictionary*)[[NSDictionary alloc ] initWithContentsOfFile:plistPath];
NSMutableDictionary *data = (NSMutableDictionary*)[dictionaryDATA_ objectForKey:@"Data"];
allSettings_ = [data objectForKey:@"AllSettings"];
}
}
- (NSMutableDictionary*) properties
{
NSMutableDictionary * props = [[NSMutableDictionary alloc]init];
[props setObject: allSettings_ forKey:@"AllSettings"];
NSMutableDictionary * data = [NSMutableDictionary dictionaryWithObject:props forKey:@"Data"];
return data;
}
- (void)persistData
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"mydata.plist"];
NSMutableDictionary *dict = [self properties];
NSData *encryptedDict = [self encryptFile:dict];
[encryptedDict writeToFile:plistPath atomically:YES];
}
But the first time the dataDictionary_ is populated, we have to force ably persist it in AppDelegate.m in didFinishLaunching:
DataHandler *dataHandler = [[DataHandler alloc] init];
[dataHandler persistData];
The data will always be encrypted at all times but in the copyPlist method you will populate your models with respect to the dataDictionary_ and interact with those models. When done, you'll persist your models and encrypt again hence no errors will occur. It's simple and a pretty workable solution without any hassle. Cheers.