Question

The PHP API I’m calling from within my iOS app requires the payload to be encrypted in a certain customised way. I’m having troubles replicating that approach in Objective-C, with RNCryptor.

Here is the PHP code used to encrypt a string:

function encrypt($string) {
    $key = 'some-random-key';
    return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
}

And this how I’m trying to achieve the same encryption result in Objective-C:

+ (NSData*)encryptData:(NSData*)sourceData {

    NSString *keyString = @"some-random-key";
    NSData *key = [[keyString MD5String] dataUsingEncoding:NSUTF8StringEncoding];
    NSData *iv = [[[keyString MD5String] MD5String] dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableData *encryptedData = [NSMutableData data];

    RNCryptorEngine *cryptor = [[RNCryptorEngine alloc] initWithOperation:kCCEncrypt settings:kRNCryptorAES256Settings key:key IV:iv error:nil];

    [encryptedData appendData:[cryptor addData:sourceData error:nil]];
    [encryptedData appendData:[cryptor finishWithError:nil]];

    return encryptedData;

}

But the results from the two functions never match. E.g., for the same one-word string, the PHP code returns J39gRcuBEaqMIPP1VlizdA8tRjmyAB6za4zG5wcOB/8=, while in Objective-C (after running base64EncodedStringWithOptions: on the resulting NSData) I’m getting 1FGpZpVm2p4z3BBY6KW2fw==.

Is there something I need to further tweak in the RNCryptor settings to make it work?

UPDATE

I’ve played around the the native iOS CommonCrypto framework directly, without using the third party RNCryptor lib altogether. I’m consisently getting the same result as with RNCryptor though. I even tried implementing AES128 in both my Objective-C and PHP snippets, but even that never made the results from the two environments match…

UPDATE 2

The MD5String method I‘m using is a category on NSString and is defined as follows:

- (NSString *)MD5String {
    const char *cstr = [self UTF8String];
    unsigned char result[16];
    CC_MD5(cstr, strlen(cstr), result);

    return [[NSString stringWithFormat:
            @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ] lowercaseString];
}
Was it helpful?

Solution 4

Zaph’s note, that

The data will be padded with \0 characters to a multiple of block size

set me on track and helped me figure out part of the problem.

Essentially, PHP’s mcrypt only uses \0 padding for the data, while Apple’s CommonCryptor lets you choose between PKCS7 padding (kCCOptionPKCS7Padding) or no padding whatsoever. This was one of the reasons I could never get the data to match: it was always padded differently before it was about to get encrypted.

The solution for this is to either make PHP perform PKCS7 padding of the data before running mcrypt (example solution) or to make Objective-C perform PHP-style \0 padding and make sure to remove kCCOptionPKCS7Padding (pass NULL to CCCrypt options):

NSMutableData *dataToEncrypt = [sourceData mutableCopy];
NSUInteger dataLength = [dataToEncrypt length];

// See how much padding is required
NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

// Add that many \0’s (there could be a more efficient way to do this)
for (int i=0; i<padding; i++)
    [dataToEncrypt appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

// Recalculate the data length
dataLength = dataToEncrypt.length;

I ended up ditching RNCryptor and working with the native CommonCryptor API directly instead, so the end result looked something like this (the encryptedBase64String method here is a category on NSString in my application, because I only ever need to encrypt strings this way; also note the kHSEncryptionKey constant representing the freeform key string):

- (NSString*)encryptedBase64String {

    // Prepare the data

    NSMutableData *sourceData = [[self dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // Process the key

    NSString *key = [[kHSEncryptionKey MD5String] substringWithRange:NSMakeRange(0, 16)];

    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    // Process the iv

    NSString *iv = [[[kHSEncryptionKey MD5String] MD5String] substringWithRange:NSMakeRange(0, 16)];

    char ivPtr[kCCKeySizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];

    // Pad the data, PHP style

    NSUInteger dataLength = [sourceData length];
    NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

    for (int i=0; i<padding; i++)
        [sourceData appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

    dataLength = sourceData.length;

    // Buffer for the resulting data

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void* buffer = malloc(bufferSize);

    // Run the encryption

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, NULL,
                                          keyPtr, kCCKeySizeAES128,
                                          ivPtr,
                                          sourceData.bytes, dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);

    if (cryptStatus == kCCSuccess) {
        NSData *encyptedData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        return [encyptedData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    }

    free(buffer);
    return nil;
}

OTHER TIPS

Although most of the answers focus on the MD5 hash, what's most likely to cause issues is the fact that MCRYPT_RIJNDAEL_256 is not AES. It does not specify a key size of 256 but a block size of 256, while AES always has a block size of 128 bits. As for the other parameters, print out their values in hexadecimal right before the encryption routine in both sides to find out their values.

I realize I'm late to the party here and you've probably already moved on. But I want to point out that there's now a full PHP port of RNCryptor that is bytestream compatible with the Objective-C implementation. I myself contributed it last summer, and I am currently maintaining it. :-)

So it should be quite easy now to encrypt with RNCryptor PHP and decrypt with RNCryptor Objective-C, or vice-versa. Check out the main RNCryptor project for details and a list of sub-projects.

It is uncommon to return an md5 as a hex-ascii in other than scripting languages, even php provides a binary output option.

There are a few n on-standard things about the php mcrypt_encrypt:

  1. Padding the key with \0 if it is smaller than the required key size.
  2. The data will be padded with \0 characters to a multiple of block size, generally a padding such as pkcs7 is used.
  3. Not specified is how an iv that is not a block size is handled, one could guess that is is also padded with trailing \0 characters.

Of these the iv will be the correct length due to the hex-ascii out put of the md5 method. But the that leaves the data length and the padding which is non-standard.

The key will be 32 bytes so will be padded with 32 \0 characters in php. This begs the question of using AES256 with a 128 bit key.

From the underlying CommonCrypto "CommonCryptor.h":

keyLength: Length of key material. Must be appropriate for the selected operation and algorithm.

The key length is not correct.

Things to do:
1. Handle the data length/padding
2. Handle the key length

For further help please provide sample data you are using and the hex-ascii out put of mcrypt_encrypt prior to base64 encoding.

For reference see:
mcrypt-encrypt.php and md5.php

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top