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