1

I am trying to use the following snippet to decrypt file, which was encrypted with 32 byte key. So when I am trying to encrypt file data, everything is going ok. But when I am trying do decrypt, program even don't pass the condition if (cryptStatus == kCCSuccess) in CryptoExtensions.m. I am really tired figuring out a problem. I was trying to use Base64 decoding, but my file is saved in UTF8 encoding, so I have no problems when I just put it's content in log from NSData:

NSData *fileData = [[[NSData alloc] initWithContentsOfFile:destPath] autorelease];

NSLog(@"File data:%@",[[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]);

But when I am trying to decrypt it, I am getting nil from CryptoExtensions method:

NSData *aesResponse = [fileData AES256DecryptWithKey:@"4QXcCZlgRAIchiaqkMVpF3nkpARmdL3z"];
NSLog(@"AES:%@",[[NSString alloc] initWithData:aesResponse encoding:NSUTF8StringEncoding]);

This is contents of crypto snippet:

CryptoExtensions.h

#import <Foundation/Foundation.h>
@interface NSData (CryptoExtensions)
- (NSData*)AES256EncryptWithKey:(NSString*)key;
- (NSData*)AES256DecryptWithKey:(NSString*)key;
@end

CryptoExtensions.m

#import "CryptoExtensions.h"
#import <CommonCrypto/CommonCryptor.h>
@implementation NSData (CryptoExtensions)
- (NSData*)AES256EncryptWithKey:(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 numBytesEncrypted    = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* 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 {
    // '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;
}
@end
jscs
  • 63,694
  • 13
  • 151
  • 195
kokoko
  • 2,705
  • 3
  • 17
  • 28
  • 1
    You are mixing AES256 and AES128 everywhere. Shouldn't you use the same algorithm, block size etc. for all? – MByD Mar 20 '12 at 20:29
  • As I understood the main thing from [this post](http://stackoverflow.com/questions/1400246/aes-encryption-for-an-nsstring-on-the-iphone), it doesn't matter what AES algorithm is used. Anything is about key size. btw: there are no methods implemented for 256 key, only 128. And only kCCKeySize256 is available. – kokoko Mar 20 '12 at 20:40
  • 1
    The *algorithm* is AES128. That means it has a 128 byte block size. This is the only supported block size in AES. The *keysize* can be 256 bits. The mix of constants is normal. – Rob Napier Mar 20 '12 at 21:54

1 Answers1

5

This code has significant security issues. It incorrectly constructs the AES key, drastically reducing the keyspace and has no IV, creating problems for the first block. I strongly recommend against this code.

I'm most of the way through developing a replacement for this commonly-used snippet. See https://github.com/rnapier/RNCryptor. If it doesn't work for you out of the box, let me know. I'm trying to make it easy enough to use for all the common cases that people will stop using AES256EncryptWithKey.

For a much longer discussion about the problems with this code, see Properly encrypting with AES with CommonCrypto. I love that someone wrapped up AES encryption into an easy to use category. I just wish that it hadn't had so many security issues.


EDIT: Coming back to your actual question, did you use AES256EncryptWithKey to encrypt this data? If not, then the specific format is likely radically different. Almost every AES encryption implementation uses a different approach in generating its input parameters and then generating its output (most don't document this well, either). You have to match the parameters and format. For example, you can't use AES256EncryptWithKey to decrypt something generated with openssl enc.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I've tried to use this snippet, but how can I cast resulting NSData to NSString? I was trying to use base64encode and NSUTF8encoding, but I can't get not null result in NSLog(@"%@", [[NSString alloc] initWithData... usingEncoding:NSUTF8Encoding...]). But I really see that encrypted data is transmitted to NSData from its size. – kokoko Mar 21 '12 at 07:33
  • 1
    It would be surprising if encrypted data encoded a readable UTF8String (many times it won't even encode a *legal* UTF8String, which is why you're getting `nil`). Encrypted data is indistinguishable from random bytes (if it were distinguishable from random, it would by definition not be a secure cipher). If you need to convert random data into a 7-bit ASCII string, that's what Base64 encoding is for. You can also convert it to hex bytes. http://stackoverflow.com/questions/1305225/best-way-to-serialize-a-nsdata-into-an-hexadeximal-string – Rob Napier Mar 21 '12 at 13:01