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];
}
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;
}