I have an iOS app that encrypts data using AES 128 CBC. I can decrypt that data within the app in objective-c, so that at least tells me that the encryption works correctly in that context (Common Crypto). The problem is that I need to send this encrypted data over to the server and decrypt it via PHP. That's where I am failing.
I manually did the encryption on the command line using openssl. Using that output and feeding it back into this php function, I can decrypt correctly, so I know that the php function works correctly, at least relative to openssl. So, the issue is how to get Objective-C to output ciphertext in the same way that openssl does (or how to get php to decrypt the ciphertext from Common Crypto). In other words, each of these functions 'works' in its own context.
<?php
function jsonEncode ($result, $message) {
$arr = array('result' => $result, 'message' => $message);
echo json_encode($arr);
}
//$ciphertext = base64_decode($_POST['ciphertext']);
//$iv = $_POST['iv'];
// overriding the http post values with values copied in from console output for testing
$ciphertext = base64_decode('dwb7MWCQUUiVuJLzL2EzYm0NcodwORP47qPhLzaolAk=');
// openssl on the command line yields ciphertext of
// 'gRiOXseXTjhpPCiiHgnaQQoal3a9E87Gx3FVpZPtR1I=' which decrypts successfully
$iv = 'hkPDfznq1t1UpKrW';
$key = 'T8ZvJba0HHsmiVSD';
//$plaintext = openssl_decrypt($ciphertext, 'aes-128-cbc', $key, false, $iv);
//jsonEncode('success', $plaintext);
// this is the line that fails
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_CBC, $iv);
//$padding = ord($plaintext[strlen($plaintext) - 1]);
//jsonEncode('success', substr($plaintext, 0, -$padding));
jsonEncode('success', $plaintext);
?>
EDIT: Based on the very helpful comment from @Zaph below, I've re-written my encryption function in Objective-C to do the padding manually. I think that this is correct. However, when the php function returns, the mcrypt_decrypt
function evaluates to false. Here is the new Objective C function:
- (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv
{
char keyPtr[kCCKeySizeAES128+1];
bzero(keyPtr, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
char ivPtr[kCCBlockSizeAES128 + 1];
bzero(ivPtr, sizeof(ivPtr));
if (iv) {
[iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
}
NSUInteger dataLength = [self length];
int diff = kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128);
int newSize = 0;
if(diff > 0) {
newSize = (int)(dataLength + diff);
}
// manually add padding to the end of the data array
char dataPtr[newSize];
memcpy(dataPtr, [self bytes], [self length]);
for(int i = 0; i < diff; i++) {
dataPtr[i + dataLength] = diff;
}
dataPtr[newSize] = '\0';
size_t bufferSize = newSize + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
// print out the padded array for verification
NSLog(@"diff: %d new size: %d", diff, newSize);
for (int i=0; i<newSize; i++)
printf("0x%x ", dataPtr[i]);
printf("\n");
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
0x0000, //No padding
keyPtr,
kCCKeySizeAES128,
ivPtr,
dataPtr,
sizeof(dataPtr),
buffer,
bufferSize,
&numBytesEncrypted);
if(cryptStatus == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
return nil;
}
Objective C Console Output:
diff: 12 new size: 32
0x65 0x6e 0x63 0x72 0x79 0x70 0x74 0x69 0x6f 0x6e 0x20 0x69 0x73 0x20 0x74 0x72 0x69 0x63 0x6b 0x79 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc
encrypted text: dwb7MWCQUUiVuJLzL2EzYm0NcodwORP47qPhLzaolAk=
The plain text is 'encryption is tricky'.
php does not support kCCOptionPKCS7Padding
, it has it's own idea of how padding should be done. So you will have to provide the padding prior to CCCrypt
. Or perform PKCS#7 padding in your code for php. (BTW: PKCS#5 and PKCS#7 padding are essentially the same.)
You should see that the blocks proior to the first one decrypt in php OK, if not there is another problem.
Here is my null (php) padding method:
+ (NSData *)phpPadData:(NSData *)data {
NSMutableData *newData = [data mutableCopy];
NSUInteger paddLength = kCCBlockSizeAES128 - (data.length % kCCBlockSizeAES128);
[newData increaseLengthBy:paddLength];
return [newData copy];
}
When I use this to encode "encryption is tricky" I get:
ciphertextData: 81188e5e c7974e38 693c28a2 1e09da41 a5d64983 e124b73c 36da520a 8198d3e7 ciphertextBase64: gRiOXseXTjhpPCiiHgnaQaXWSYPhJLc8NtpSCoGY0+c=
Which is exactly the same I get if I use the online site: http://aes.online-domain-tools.com (which is probably using php mcrypt). This differs from theOpenSSL in the question.
Here is my test code, I separate the string to/from data encoding from the encryption. I put thes class methods in a class names Test, any class will do.
+ (NSString *)encryptCleartextString:(NSString *)cleartextString keyString:(NSString *)keyString ivString:(NSString *)ivString {
NSData *cleartext = [cleartextString dataUsingEncoding:NSUTF8StringEncoding];
NSData *key = [keyString dataUsingEncoding:NSUTF8StringEncoding];
NSData *iv = [ivString dataUsingEncoding:NSUTF8StringEncoding];
NSData *ciphertext = [Test phpEncryptCleartext:cleartext key:key iv:iv];
NSLog(@"ciphertext: %@", ciphertext);
NSString *ciphertextString = [ciphertext base64EncodedStringWithOptions:0];
NSLog(@"ciphertextString: %@", ciphertextString);
return ciphertextString;
}
+ (NSData *)phpEncryptCleartext:(NSData *)cleartext key:(NSData *)key iv:(NSData *)iv {
NSLog(@"phpEncryptCleartext");
NSLog(@"cleartext: %@", cleartext);
NSLog(@"key: %@", key);
NSLog(@"iv: %@", iv);
NSData *cleartextPadded = [self phpPadData:cleartext];
NSLog(@"cleartextPadded: %@", cleartextPadded);
CCCryptorStatus ccStatus = kCCSuccess;
size_t cryptBytes = 0; // Number of bytes moved to buffer.
NSMutableData *ciphertext = [NSMutableData dataWithLength:cleartextPadded.length];
ccStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
0,
key.bytes,
kCCKeySizeAES128,
iv.bytes,
cleartextPadded.bytes,
cleartextPadded.length,
ciphertext.mutableBytes,
ciphertext.length,
&cryptBytes);
if (ccStatus == kCCSuccess) {
ciphertext.length = cryptBytes;
}
else {
NSLog(@"kEncryptionError code: %d", ccStatus); // Add error handling
ciphertext = nil;
}
NSLog(@"ciphertext: %@", ciphertext);
return ciphertext;
}
// Test code
NSString *cleartextString = @"encryption is tricky";
NSString *ivString = @"hkPDfznq1t1UpKrW";
NSString *keyString = @"T8ZvJba0HHsmiVSD";
NSString *encryptedTextBase64 = [Test encryptCleartextString:cleartextString keyString:keyString ivString:ivString];
NSLog(@"encryptedTextBase64: %@", encryptedTextBase64);