# AES128 Decryption in Objective-C just slightly off

I have been asked to integrate a home-grown CRM system with a 3rd-party data service provider (DSP). The DSP has encrypted the information they are providing. I only have their decryption instructions, which are as follows (verbatim):

Encryption Type: AES128
Mode: CBC
Padding: PKCS5 (.net usesPKCS7)


decryption example as provided by the DSP

The DSP instructions also contain some sample data, namely:

Sample Encrypted data:

h6KlotyqYvipjQHICwJK25mqY+2GC6qc/qf5fCBoGYaxe2zQad/j6Dxo6H259PretcjkUcBsgMl+vFkYPXGwH vbZMqMPCIznBrdZeSWP+Hie7YdLRYp9V5/JKLTIZZlZAG7Vii8DepV5KXjbat4qGxNTVRAvDQ8tT9xZbzEbzY 7Sb9q/f9TE/yardrrgDntw9WywTDbU4Yfg3BEC0jIKFg==


Sample Expected Decrypted data:

123456^CustIDLookup^John^Doe^Placeholder Name^ATS^217 General Patton Ave.^Suite 208^Mandeville^LA^70471^United States^985-809-0600^postmaster@atsleads.com


So, my mission, should I choose to accept it, is to arrive at the decrypted example. This is for use within a native iOS app, so I need to code the decryption in Objective-C.

So far, my results for the decrypted data NSString look like this:

__NSCFString *  @"(\0\x1d\r\x040D^CustIDLookup^First Name^Last Name^Title^Company^Address 1^Address 2^City^State^Zip^Country^Phone^Email"   0x00000001780ee700


i.e. I am getting this result string:

\0\x1d\r\x040D^CustIDLookup^First Name^Last Name^Title^Company^Address 1^Address 2^City^State^Zip^Country^Phone^Email


I have shown my code below. I can get very close to the expected decryption, but I can't match it exactly - the first 5 chars are wrong.

Can anyone shed any light on this issue, and perhaps steer me in a direction that would allow my decryption code to decipher the data correctly? I'm no expert on encryption, but it seems strange to me that I'm very close but am not yet enjoying my cigar. In my reading so far I have seen comments stating that AES128 and PKCS5 don't make sense together, but I don't if this has any bearing on things.

I have coded an NSMutableData category (extension) as follows:

@implementation NSMutableData (AES)

- (NSMutableData*)DecryptAES: (NSString*)key
{
int blockSize = 16; // Actually kCCKeySizeAES128, but hard-coded for experimentation
char  keyPtr[blockSize];
bzero( keyPtr, sizeof(keyPtr) );
bool rc1 = [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
if (rc1==NO){
NSLog(@"getCString failed");
}

size_t numBytesEncrypted = 0;
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + blockSize;
void *buffer_decrypt = malloc(bufferSize);
NSMutableData *output_decrypt = [[NSMutableData alloc] init];
char ivBytes[8] = {0,0,0,0,0,0,0,kCCModeCBC};
//char ivBytes[8] = {0,0,0,0,0,0,0,0}; // same results with this line
int ivLength = sizeof(ivBytes);
NSData* initialisationVector = [[NSData alloc] initWithBytes:ivBytes length:ivLength];

//CCCryptorStatus result = CCCrypt(operation, algo, optarg, key, keylength, iv, datain, datainlength, dataout, dataoutavailable, dataoutmoved)
CCCryptorStatus result = CCCrypt(kCCDecrypt , kCCAlgorithmAES, kCCOptionPKCS7Padding , keyPtr, blockSize, initialisationVector.bytes, [self mutableBytes], [self length], buffer_decrypt, bufferSize, &numBytesEncrypted);

if (result!=kCCSuccess){
NSLog(@"CCCrypt did not work");
}

output_decrypt = [NSMutableData dataWithBytesNoCopy:buffer_decrypt length:numBytesEncrypted];

if(result == kCCSuccess) {
return output_decrypt;
}
return NULL;

}

@end


I am then calling this category as follows:

NSMutableData* originalStringAsData = [[NSMutableData alloc] initWithBase64EncodedString:self.originalString options:nil];

NSMutableData* decryptedData  = [originalStringAsData DecryptAES:@"jayjay"];

NSString* decryptedString1 = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];


Any help would be hugely appreciated.

• (1) you seem confused about the difference between a data block and the key. In AES the key size can be 128 192 or 256 bits, which is 16 24 or 32 bytes. The data block size is always 16 bytes regardless of the key. The IV is always 16 bytes (as @pancho notes) regardless of the key. Padding is up to a block, so the encryption buffer should be 16 bytes larger regardless of the key; the decryption buffer does not need to be larger at all. But all this pales beside ... – dave_thompson_085 Feb 8 '16 at 17:35
• ... (2) your example link uses the key as IV also (00-padded for both). This is horribly atrociously bad; it turns a chosen-plaintext attack and perhaps even a known-varying-plaintext attack into key recovery. With code to do IV=key=jayjay I decrypt correctly. – dave_thompson_085 Feb 8 '16 at 17:40
• Ok, so if I pass keyPtr as the iv then it works correctly for me too. Regarding your attack comments, are you saying that the Data Provider is encrypting the data in a non-secure, or compromised way? Can you elaborate in an answer (which I'd be very happy to accept as you have solved my problem) – Journeyman Feb 8 '16 at 18:07
• (Sorry for delay) They are definitely not following good practice. Whether it's actually insecure depends. The generic attack I was thinking of is to get 2^64 or slightly more known-plaintext encryptions which by the birthday paradox is likely to have a collision between the first block of one ciphertext call it CA_1 and a nonfirst block of another CB_i, then just compute CB_(i-1) xor PB_i xor PA_1 to get the IV and thus key; 64bits is today feasible for a high-end attacker like NSA or PLA, and will undoubtedly get cheaper in the future. ... – dave_thompson_085 Feb 11 '16 at 20:19
• ... Another and worse possibility I thought of later: if there is a padding oracle with one known first block PA_1-CA_1 you can POODLE-style vary CB_1[15],CB_2=CA_1 until pass so PB_2[15] is 01, then adjust and vary CB_1[14] until PB_2[14:15]=0202 and so on until you have the whole block, then CB_1 xor all-10 xor PA_1 gives the IV as above. But regardless of the actual (in)security of this feature, the fact that they designed a scheme that is obviously poor practice would make me worry about the rest of their system. And I realize with an external supplier you probably can't fix that. :-( – dave_thompson_085 Feb 11 '16 at 20:34

When you get the first part of the decryption wrong, but the rest correct, it almost always means that you got the IV wrong.

However, with CBC mode, what usually happens is that you get the first 16 bytes wrong; instead, you got only 6 bytes wrong; that may indicate that you got the first 6 bytes of the IV wrong (and the other 10 bytes correct).

In addition, I notice that, in your code, you're handing an 8 byte IV to the API; this is odd, because CBC mode takes a 16 byte IV.

I don't know the API you're using (and I certainly don't know the assumptions that the DSP has used), and so I'm at a loss at helping you any further -- however, you were looking for suggestions as to where to look...

• thanks very much for your help, that is useful to know. I have tried various 16 byte iv values, and these yield similar results. How would I go about determining exactly what iv bytes I should be using? Is there a defined set of 16 bytes for AES128 for CBC? – Journeyman Feb 8 '16 at 17:30
• @Journeyman: In CBC mode, the IV is simply XORed with the first block of plaintext before encryption / after decryption. (This is also why it's so important for security that the IV be random and unpredictable in CBC encryption.) So if you know the plaintext you should be getting, you can just XOR that with the plaintext you are getting, and then XOR that with the IV you're using to obtain the correct IV. – Ilmari Karonen Feb 11 '16 at 12:04

So, dave_thompson_085 correctly answered this by stating that the iv value simply needs to be the key value. Whatever the merits/issues of the DSP adopting this approach (outside my knowledge) then this works (with a 16 byte iv), so problem solved.

I don't suppose this will be of much relevance to anyone else, but posting this answer anyway. @dave_thompson_085 - if you post an answer I'll happily accept your answer as the correct answer.

• No problem; you've got the answer you need, and while I feel the other issues are worth noting they're not actually answers. – dave_thompson_085 Feb 11 '16 at 20:30