CCCrypt fails when decrypting in another application - objective-c

I have some Encryption and Decryption class methods in my application. I can encrypt 'data' using 'key' and it works great. I can write that data to disk and then read the data in at a later time using the application and call decryptWithKey to decrypt the data block. Everything works great. However, if I include this .m class file into another application, compile that application and try to decrypt that same data which was encrypted with the first application, the CCCrypt(decrypt) call fails...well, not exactly, it returns Success but the data is not decrypted. I have compared the data and key values in both applications and they are identical, down to the byte.
Any ideas?
+(BOOL)encryptWithKey:(NSMutableData*)data withKey:(NSString *)key
{
CCCryptorStatus result = kCCSuccess;
#try
{
char keyPtr1[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr1, sizeof(keyPtr1) ); // fill with zeroes (for padding)
[key getCString: keyPtr1 maxLength: sizeof(keyPtr1) encoding: NSUTF8StringEncoding];
size_t numBytesEncrypted = 0;
size_t dataInLength = [data length];
size_t dataOutLength = 18*dataInLength;
[data setLength:dataOutLength];
result = CCCrypt( kCCEncrypt,kCCAlgorithmAES128,kCCOptionPKCS7Padding,
(const void*)keyPtr1, kCCKeySizeAES256, 0 /* initialization vector (optional) */,
[data mutableBytes], dataInLength, /* input */
[data mutableBytes], dataOutLength, /* output */
&numBytesEncrypted );
[data setLength:numBytesEncrypted];
}
#catch (NSException *exception)
{
WDCATCH(exception);
}
return ( result == kCCSuccess );
}
+(BOOL)decryptWithKey:(NSMutableData*)data plaintext:(NSString**)plaintext withKey:(NSString *)key
{
CCCryptorStatus result = kCCSuccess;
#try
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
char szPlaintext[1024]={0};
bzero( keyPtr, sizeof(keyPtr) ); // fill with zeroes (for padding)
[key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding];
size_t numBytesEncrypted = 0;
result = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
(const void*)keyPtr, kCCKeySizeAES256,
0 /* initialization vector (optional) */,
[data mutableBytes], [data length], /* input */
szPlaintext,1024,// [data mutableBytes], dataOutLength, /* output */
&numBytesEncrypted );
if (result == kCCSuccess)
{
szPlaintext[numBytesEncrypted] = 0;
*plaintext = [[NSString alloc] initWithFormat:#"%s",szPlaintext];
}
}
#catch (NSException *exception)
{
WDCATCH(exception);
} return ( result == kCCSuccess );
}

I'm not an Objective-C expert, but char szPlaintext[1024]={0}; in the decryptWithKey seems very suspicious to me. First you allocate 1024 bytes, but then you assign the pointer to an array containing the element 0? The generation of plaintext as a string looks pretty suspicious as well, but it probably works for ASCII encoded plaintext (?).

Related

CCCrypt not work on xcode 5 for ios 7

I try to use CCCrypt method, but it has different result from XCode4 and XCode5
- (NSData *)AES256DecryptWithKey:(NSString *)key
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesDecrypted );
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free( buffer ); //free the buffer
return nil;
}
when I call this method with this lines ... different result
NSString *password = #"E7VRcIXn8yb2Ab+t/in9UzRof6vOpOYebgKbpt1GOcfDF8rpc5nZXngx1G8QfbDqsVrwZw26609GVwruUBrOirCI/WUT8U87fbD6lSy/zPwFIYC113LgXIEylYgzIWO4";
NSString *pwd = [password AES256DecryptWithKey: #"abcd"];
if (pwd) {
NSString *checkKey = #"0sSBf7Ncyov+uzvDikOBiA==";
NSString *uncryptChk = [checkKey AES256DecryptWithKey: pwd];
In XCode4 the result is "abcd", whereas in XCode5 the result is "".
Ran into this issue myself. it seems they fixed a bug from iOS6 to iOS7 regarding [NSString keyCString:maxLength:encoding] The old method on iOS6 would cut off part of the buffer to fill the keyPtr.
An easy fix would be to increase the keyPTr size to
char keyPtr = kCCKeySizeAES256 * 2 + 1;
However keep in mind that when you upgrade apps form 6 to 7, while it should work. the keyCString truncates the length to match the size of keyPtr. and replaces the first character with 0. So to make sure that it works on both platforms. Add the above code, and set keyPtr[0] = 0;

AES128 decryption with NSData as key

I'm having a hard time decrypting NSData with NSData key. While decrypting with NSString everything works fine, yet with data as key, the method returns null NSData, eventhough status is ok, and key is also correct for that data. Here is my call
NSData *decrypted = [AES AES128DecryptWithKey:data key:mute];
NSLog(#"DECRYPTED >> %#", decrypted);
And my method
+ (NSData*)AES128DecryptWithKey:(NSData*) data key:(NSData*)key {
NSUInteger dataLength = [data length];
NSLog(#"trying to decrypt >> %# with key >> %#", data, key );
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void* buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
[key bytes], kCCKeySizeAES128,
NULL /* initialization vector (optional) */,
[data bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesDecrypted);
NSLog(#"cryptstatus %d", cryptStatus);
if (cryptStatus == kCCSuccess)
{
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil;
}
NSLog output:
trying to decrypt >> <152f052e 79436003 7a9c1a59 3b82f1c4>
with key >> <1b510e76 ac0000a1 027af26a e25ad24a>
cryptstatus 0
DECRYPTED >> <>
Looked into RNEncryptor, yet it only has aes256, while i need 128. Any ideas?
It could be that the single byte block only contains padding bytes. In that case zero bytes would be the expected outcome.
This is not the case for your ciphertext, in your case no padding was applied during encryption, so you will get a failure when you try and unpad the data.

OSX AES Encryption is not working

In my application, i need to encrypt the file before sending it over the network and on the other end, it will get decrypt, This is my code,
-(void)doEncryptTest:(NSString *)pFileName{
// read the NSData;
NSStringEncoding encoding =NSUTF8StringEncoding;
NSString *pFileContent = #"xaaaaaaxxaaaaaax";
NSString *pKey = #"01234567012345670123456701234567";
NSData *pData = [pFileContent dataUsingEncoding:encoding];
NSData *pEncryptedData = [pData AES256EncryptWithKey:pKey];
NSData *decrypted=[pEncryptedData AES256DecryptWithKey:pKey Data:pEncryptedData];
NSString* pDecryptedDataStr = [[NSString alloc] initWithData:decrypted
encoding:encoding];
}
This is working fine , only and only if Data size is 16 byte, in real time case, when i sent the file of size 151186 bytes, size of [pEncryptedData] is 15200 , and in fact size of decrypted data is same as original data,
But pDecryptedDataStr is blank, any guess what is going wrong,
please refer below, Encryption and decryption function,
int keySize = kCCKeySizeAES256;
int padding = kCCOptionPKCS7Padding;
char ivKey[16]={0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0};
//////////////*Encryption*///////////////////
- (NSData *)AES256EncryptWithKey:(NSString *)key{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[keySize + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );
char ivVector[kCCBlockSizeAES128+1];
// fetch key data
[key getCString:ivVector maxLength:sizeof( ivVector ) encoding:NSUTF8StringEncoding];
bzero( ivVector, sizeof( ivVector ) ); // fill with zeroes (for padding)
const void *iv=NULL;
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, padding,
keyPtr, keySize,
ivKey /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted );
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free( buffer ); //free the buffer
return nil;
}
- (NSData *)AES256DecryptWithKey:(NSString *)key Data:(NSData*)EncryptedData{
bool same =[self isEqualToData:EncryptedData];
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[keySize+1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [EncryptedData length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength +kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );
const void *iv=NULL;
char ivVector[kCCBlockSizeAES128+1];
// fetch key data
[key getCString:ivVector maxLength:sizeof( ivVector ) encoding:NSUTF8StringEncoding];
bzero( ivVector, sizeof( ivVector ) ); // fill with zeroes (for padding)
size_t numBytesDecrypted = 0;
NSData *output_decrypt = [[NSData alloc] init];
CCCryptorStatus cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, padding,
keyPtr, keySize,
ivKey /* initialization vector (optional) */,
[EncryptedData bytes], dataLength, /* input */
buffer,bufferSize ,//bufferSize, /* output */
&numBytesDecrypted );
output_decrypt = [NSMutableData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
same =[self isEqualToData:output_decrypt];
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
NSData *pData = [[NSData alloc]initWithBytes:buffer length:numBytesDecrypted];
return pData;
}
free( buffer ); //free the buffer
return nil;
}
Thanks for looking at this, this was the problem,
output_decrypt = [NSMutableData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
same =[self isEqualToData:output_decrypt];
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
**NSData *pData = [[NSData alloc]initWithBytes:buffer length:numBytesDecrypted];**
return pData;
}
and solutions was;
output_decrypt = [NSMutableData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
same =[self isEqualToData:output_decrypt];
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return output_decrypt;
}
Not sure, why it was causing problem, of-course i need to change return type and do some handling in the calling function to release the memory.

How to use hex decimal key with AES encryption?

My question is I need to use the following hexadecimal key to encrypt but it assumes it as a string?
key = 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11
I had tried using [key bytes] instead of keyptr in the CCrypt function but it does't work...
- (NSData *)AES128EncryptWithKey:(NSString *)key theData:(NSData *)Data {
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES128]={'1'};; // room for terminator (unused) // oorspronkelijk 256
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSLog(#"keyPtr %s",keyPtr);
NSUInteger dataLength = [Data length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode,
keyPtr,
kCCKeySizeAES128, // oorspronkelijk 256
nil, /* initialization vector (optional) */
[Data bytes],
dataLength, /* input */
buffer,
bufferSize, /* output */
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSLog(#"Encrypt SUCCESS");
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free(buffer); //free the buffer;
return nil;
}
- (NSData *)AES128DecryptWithKey:(NSString *)key theData:(NSData *)Data{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused) // oorspronkelijk 256
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSASCIIStringEncoding];
NSUInteger dataLength = [Data length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionECBMode+kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES128, // oorspronkelijk 256
NULL /* initialization vector (optional) */,
[Data bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
NSLog(#"Decrypt SUCCESS");
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil;
}
You can use NSScanner, like this:
NSString *keyStr = #"0x11,0x12,0x13,0x14,0x13,0x1a,0x1b,0x1c,0x1f,0x11,0x11,0x11,0x11,0x11,0x11,0xfe";
NSScanner *scan = [NSScanner scannerWithString:keyStr];
[scan setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#","]];
unsigned int tmp;
char key[16];
for (int i = 0 ; i != 16 ; i++) {
[scan scanHexInt:&tmp];
key[i] = (char)tmp;
}
At this point, the array key contains 16 bytes of your key converted from hex to bytes.

AES with CommonCrypto uses too much memory - Objective-C

My goal is to be able to, being given a file/folder and a password, encrypt and decrypt it in AES using Objective-C. I'm no crypto nerd or anything, but I chose AES because I found it was pretty standard and very secure. I am using a NSMutableData category which has methods for encrypting and decrypting it's data. Here it is:
- (NSInteger)AES256EncryptionWithKey: (NSString*)key {
// The key should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// Fetch key data
if (![key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding])
{ return 2; } // Length of 'key' is bigger than keyPtr
NSUInteger dataLength = [self length];
// See the doc: For block ciphers, the output size will always be less than or
// equal to the input size plus the size of one block.
// That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void* buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL , // initialization vector (optional)
[self bytes], dataLength, // input bytes and it's length
buffer, bufferSize, // output buffer and it's length
&numBytesEncrypted); // ??
if (cryptStatus == kCCSuccess) {
// The returned NSData takes ownership of the buffer and will free it on deallocation
[self setData: [NSData dataWithBytesNoCopy: buffer length: numBytesEncrypted]];
return 0;
}
free(buffer); // Free the buffer;
return 1;
}
- (NSInteger)AES256DecryptionWithKey: (NSString*)key {
// The key should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// Fetch key data
if (![key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding])
{ return 2; } // Length of 'key' is bigger than keyPtr
NSUInteger dataLength = [self length];
// See the doc: For block ciphers, the output size will always be less than or
// equal to the input size plus the size of one block.
// That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void* buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL, // initialization vector (optional)
[self bytes], dataLength, // input
buffer, bufferSize, // output
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
// The returned NSData takes ownership of the buffer and will free it on deallocation
[self setData: [NSData dataWithBytesNoCopy: buffer length: numBytesDecrypted]];
return 0;
}
free(buffer); // Free the buffer;
return 1;
}
The problem with this code is that it uses about !! 5 !! times in memory the size of the file (opened with NSMutableData) that the user chooses. This is completely unacceptable from the user's perspective (imagine encrypting a file which is 2Gb - 10Gb in memory??), but I am really at a loss here.
Can you suggest any modification that would solve this problem? Probably encrypting one chunk at a time (that way only one chunck or two is in memory at the same time, not the entire file * 5). The big problem with that is that I don't know how to do it. Any ideas?
Thanks
PS: When I use this category, I do it this way:
NSMutableData* data = [NSMutableData dataWithContentsOfFile: #"filepath"];
[data AES256EncryptionWithKey: #"password"];
[data writeToFile: #"newname" atomically: NO];
And just these 3 lines create such a big memory problem.
OH, by the way: do I need an initialization vector? I think it is more secure, or something, but I don't know. If there is really a need, could you tell me how to do it?
EDIT
This is now what I am doing:
NSMutableData* data = [NSMutableData dataWithContentsOfMappedFile: #"filepath"];
[data SafeAES256EncryptionWithKey: #"password"];
[data writeToFile: #"newname" atomically: NO];
And the new method in the category:
- (void)SafeAES256EncryptionWithKey: (NSString*)key {
// The key should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// Fetch key data
if (![key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding])
{ return 2; } // Length of 'key' is bigger than keyPtr
CCCryptorRef cryptor;
CCCryptorStatus cryptStatus = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL, // IV - needed?
&cryptor);
if (cryptStatus != kCCSuccess) {
; // Handle error here
}
NSInteger startByte;
size_t dataOutMoved;
size_t dataInLength = kChunkSizeBytes; // #define kChunkSizeBytes (16)
size_t dataOutLength = CCCryptorGetOutputLength(cryptor, dataInLength, FALSE);
const void* dataIn = malloc(dataInLength);
void* dataOut = malloc(dataOutLength);
for (startByte = 0; startByte <= [self length]; startByte += kChunkSizeBytes) {
if ((startByte + kChunkSizeBytes) > [self length]) { dataInLength = [self length] - startByte; }
else { dataInLength = kChunkSizeBytes; }
NSRange bytesRange = NSMakeRange(startByte, (int)dataInLength);
[self getBytes: dataIn range: bytesRange];
CCCryptorUpdate(cryptor, dataIn, dataInLength, dataOut, dataOutLength, &dataOutMoved);
if (dataOutMoved != dataOutLength) {
NSLog(#"dataOutMoved != dataOutLength");
}
[self replaceBytesInRange: bytesRange withBytes: dataOut];
}
CCCryptorFinal(cryptor, dataOut, dataOutLength, &dataOutMoved);
[self appendBytes: dataOut length: dataOutMoved];
CCCryptorRelease(cryptor);
I can't understand why this sometimes works and other times it doesn't. I am really at a loss here. Could someone please check this code?
In order not to load all the file into memory at once, I use -dataWithContentsOfMappedFile, and then call -getBytes:range:, because I saw here that that way it wouldn't load all the file into real memory at once, only the specified range.
EDIT 2
Please see my answer for what I am doing now.
I decided to leave the confortable Objc-C land and rewrote the second NSMutableData category up above with C functions. I did my best, but it would not surprise me if there are flaws in this code, so please make suggestions! I also dropped the category 'scheme' and decided to do a stand-alone method instead. Here:
// What do you think this number should be? 16B, 256B...? 1KB, 1MB? Please tell me
#define kChunkSizeBytes (1024*1024) // 1 MB
- (BOOL)cryptFile: (NSString*)oldFPath
toFile: (NSString*)newFPath
withPassword: (NSString*)password
andOperation: (CCOperation)operation
{
// READ PASSWORD
// The key should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// Fetch key data
if (![password getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding])
{ return FALSE; } // Length of 'key' is bigger than keyPtr
// CREATE CRYPTOR
CCCryptorRef cryptor;
CCCryptorStatus cryptStatus = CCCryptorCreate(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL, // IV - needed?
&cryptor);
if (cryptStatus != kCCSuccess) {
return FALSE; // Handle error here
}
// OPEN OLD FILE AND READ SIZE
FILE* oldFile = fopen([oldFPath UTF8String], "rb");
if(oldFile == NULL) {
return FALSE; // Could not open old file
}
fseek(oldFile, 0, SEEK_END);
size_t oldFileSize = ftell(oldFile);
fseek(oldFile, 0, SEEK_SET);
// OPEN NEW FILE
FILE* newFile = fopen([newFPath UTF8String], "ab");
if(newFile == NULL) {
return FALSE; // Could not open new file
}
// ..CRYPT
NSInteger byteOffset;
size_t dataOutMoved;
size_t dataInLength = kChunkSizeBytes;
size_t dataOutLength = CCCryptorGetOutputLength(cryptor, dataInLength, FALSE);
const void* dataIn = malloc(dataInLength);
void* dataOut = malloc(dataOutLength);
// ..crypt data one chunk at a time
for (byteOffset = 0; byteOffset <= oldFileSize; byteOffset += kChunkSizeBytes) {
if ([[NSThread currentThread] isCancelled]) { break; }
if ((byteOffset + kChunkSizeBytes) > oldFileSize) { dataInLength = oldFileSize - byteOffset; }
else { dataInLength = kChunkSizeBytes; }
fseeko(oldFile, byteOffset, SEEK_SET);
fread(dataIn, 1, dataInLength, oldFile);
CCCryptorUpdate(cryptor, dataIn, dataInLength, dataOut, dataOutLength, &dataOutMoved);
fwrite(dataOut, 1, dataOutMoved, newFile);
}
// If thread hasn't been cancelled, finalize
if (![[NSThread currentThread] isCancelled]) {
CCCryptorFinal(cryptor, dataOut, dataOutLength, &dataOutMoved);
fwrite(dataOut, 1, dataOutMoved, newFile);
}
// CLOSE AND RELEASE
free(dataIn);
free(dataOut);
fclose(oldFile);
fclose(newFile);
CCCryptorRelease(cryptor);
return TRUE;
}
I know there is no error checking inside the 'for' loop, and that may also be the case elsewhere. Suggestions on that please! There is some code there that checks if a thread has been cancelled. That's because this code is run on a separate thread which my class controls. Whenever the user clicks the "Cancel" button, the thread I created is sent the cancel message. Those if's make sure that the thread actually cancels. Feel free to make suggestions (once again!) and to use this code wherever you feel like it :)
PS: I have tested this, both with encryption and decryption and it has worked flawlessly so far. My initial problem (too much memory) seems to be solved as well!
Here is an modified implementation of Alex's original code using a category on NSMutableData. It has been thoroughly unit tested with in-memory data, as well as [NSMutableData dataWithContentsOfMappedFile:"filePath"]; with file of varying length, from a few bytes to 50 MB.
NSMutableData+Crypto.h
#interface NSMutableData (Crypto)
- (BOOL)encryptWithKey:(NSString *)key;
- (BOOL)decryptWithKey:(NSString *)key;
#end
And NSMutableData+Crypto.m
#define kChunkSizeBytes (1024 * 1024) // 1 MB
#implementation NSMutableData (Crypto)
/**
* Performs a cipher on an in-place buffer
*/
-(BOOL) doCipher:(NSString *)key operation: (CCOperation) operation
{
// The key should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// Fetch key data
if (![key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]) {return FALSE;} // Length of 'key' is bigger than keyPtr
CCCryptorRef cryptor;
CCCryptorStatus cryptStatus = CCCryptorCreate(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL, // IV - needed?
&cryptor);
if (cryptStatus != kCCSuccess) { // Handle error here
return FALSE;
}
size_t dataOutMoved;
size_t dataInLength = kChunkSizeBytes; // #define kChunkSizeBytes (16)
size_t dataOutLength = CCCryptorGetOutputLength(cryptor, dataInLength, FALSE);
size_t totalLength = 0; // Keeps track of the total length of the output buffer
size_t filePtr = 0; // Maintains the file pointer for the output buffer
NSInteger startByte; // Maintains the file pointer for the input buffer
char *dataIn = malloc(dataInLength);
char *dataOut = malloc(dataOutLength);
for (startByte = 0; startByte <= [self length]; startByte += kChunkSizeBytes) {
if ((startByte + kChunkSizeBytes) > [self length]) {
dataInLength = [self length] - startByte;
}
else {
dataInLength = kChunkSizeBytes;
}
// Get the chunk to be ciphered from the input buffer
NSRange bytesRange = NSMakeRange((NSUInteger) startByte, (NSUInteger) dataInLength);
[self getBytes:dataIn range:bytesRange];
cryptStatus = CCCryptorUpdate(cryptor, dataIn, dataInLength, dataOut, dataOutLength, &dataOutMoved);
if (dataOutMoved != dataOutLength) {
NSLog(#"dataOutMoved (%d) != dataOutLength (%d)", dataOutMoved, dataOutLength);
}
if ( cryptStatus != kCCSuccess)
{
NSLog(#"Failed CCCryptorUpdate: %d", cryptStatus);
}
// Write the ciphered buffer into the output buffer
bytesRange = NSMakeRange(filePtr, (NSUInteger) dataOutMoved);
[self replaceBytesInRange:bytesRange withBytes:dataOut];
totalLength += dataOutMoved;
filePtr += dataOutMoved;
}
// Finalize encryption/decryption.
cryptStatus = CCCryptorFinal(cryptor, dataOut, dataOutLength, &dataOutMoved);
totalLength += dataOutMoved;
if ( cryptStatus != kCCSuccess)
{
NSLog(#"Failed CCCryptorFinal: %d", cryptStatus);
}
// In the case of encryption, expand the buffer if it required some padding (an encrypted buffer will always be a multiple of 16).
// In the case of decryption, truncate our buffer in case the encrypted buffer contained some padding
[self setLength:totalLength];
// Finalize the buffer with data from the CCCryptorFinal call
NSRange bytesRange = NSMakeRange(filePtr, (NSUInteger) dataOutMoved);
[self replaceBytesInRange:bytesRange withBytes:dataOut];
CCCryptorRelease(cryptor);
free(dataIn);
free(dataOut);
return 1;
}
- (BOOL)encryptWithKey:(NSString *)key {
return [self doCipher:key operation:kCCEncrypt];
}
- (BOOL)decryptWithKey:(NSString *)key {
return [self doCipher:key operation:kCCDecrypt];
}
Note to Alex: your original attempt simply failed to take into account the CCCryptorUpdate may not return the same number of bytes on output as it does input. For example, decrypting 1024 bytes often returns 1008 bytes, as 16 bytes are used for padding. Your example using C file functions took this into account by writing to a new file, rather than replacing memory in-place. I've just implemented a file position pointer similar to your file output method. Thanks for getting me started!