10

I have a confusing problem, where decrypting a file which was encrypted using CCCrypt's AES-CBC mode with a randomized, 16byte IV produces the exact same output whether I pass in the same correct IV used for encryption or none at all.

What I expect: using a NULL IV for decrypting should not result in a correct decryption. What I observe: using a NULL IV results in the same result as with the IV used for encryption.

Below for sake of completeness, here's the important code snippets, iv is passed in as 16-byte securely randomized NSData.

What am I not understanding here? Is CCCrypt somehow figuring out the IV from the encrypted data by itself? I couldn't find anything around that in the docs.

- (NSData *)encryptedDataForData:(NSData *)rawData
                         withKey:(NSData *)key
                              iv:(NSData *)iv
                           error:(NSError __autoreleasing**)error
{
    size_t outLength;
    NSMutableData *cipherData = [NSMutableData dataWithLength:rawData.length + kAlgorithmBlockSize];

    CCCryptorStatus result = CCCrypt(kCCEncrypt,
                                     kCCAlgorithmAES128,
                                     kCCOptionPKCS7Padding | kCCModeCBC,
                                     key.bytes,
                                     key.length,
                                     iv.bytes,
                                     rawData.bytes,
                                     rawData.length,
                                     cipherData.mutableBytes,
                                     cipherData.length,
                                     &outLength);
    if (result == kCCSuccess) {
        cipherData.length = outLength;
        return cipherData;
    } else {
        return nil;
    }
}

- (NSData *)decryptedDataForData:(NSData *)encryptedData withKey:(NSData *)key error:(NSError __autoreleasing**)error
{
    size_t outLength;
    NSMutableData *decryptedData = [NSMutableData dataWithLength:encryptedData.length];

    // this line is just to illustrate how setting the exact same iv here - if this one
    // was used for encryption - results in same output as when passing iv = NULL
    NSData *iv = [Cryptor dataForHex:@"724a7fc0 0d8ac9d5 f09ff4c1 64d2d1bb"];

    CCCryptorStatus result = CCCrypt(kCCDecrypt,
                                     kCCAlgorithmAES128,
                                     kCCOptionPKCS7Padding | kCCModeCBC,
                                     key.bytes,
                                     key.length,
                                     iv.bytes, // iv OR NULL --> same result o_O
                                     encryptedData.bytes,
                                     encryptedData.length,
                                     decryptedData.mutableBytes,
                                     decryptedData.length,
                                     &outLength);
    if (result == kCCSuccess) {
        decryptedData.length = outLength;
        return decryptedData;
    } else {
        return nil;
    }
}

EDIT:

To elaborate on this, no matter which IV I use for decryption (tried out a couple of different randomized IV's) I always do get byte for byte the identical results. Even when I decrypt only some partial chunk of the encrypted file somewhere from the middle of the encrypted file.

Is this maybe related to the actual data I am en/decrypting (mp3 files)?

When I just pass some arbitrary chunk of the actual encrypted file to the decryptor, shouldn't it require the block right before that chunk of data (which I do not provide explicitly as the IV) to do the proper decryption? The only explanation I could think of here personally is that CCCrypt just always uses the first 16-bytes as the IV and does not decrypt those but drops them in the output.

EDIT 2:

Output of en/decryption, showing the first two blocks of in and output data, the key and the iv:

# encryption
data <4cd9b050 30c04bf9 2a0cb024 19010a31 400c2261 0069196a d77bcae6 9799ae26>
iv <724a7fc0 0d8ac9d5 f09ff4c1 64d2d1bb>
key <78656a1a 337fddd6 fa52e34d 9156d187>
encrypted <cf85cdbe 10a87309 a6fb4c4e ce640619 8f445b70 3738018a e78291a7 b4ea26ce>

# decryption with correct IV
data <cf85cdbe 10a87309 a6fb4c4e ce640619 8f445b70 3738018a e78291a7 b4ea26ce>
iv <724a7fc0 0d8ac9d5 f09ff4c1 64d2d1bb>
key <78656a1a 337fddd6 fa52e34d 9156d187>
decrypted <4cd9b050 30c04bf9 2a0cb024 19010a31 400c2261 0069196a d77bcae6 9799ae26>

# decryption with zero IV
data <cf85cdbe 10a87309 a6fb4c4e ce640619 8f445b70 3738018a e78291a7 b4ea26ce>
iv <00000000 00000000 00000000 00000000>
key <78656a1a 337fddd6 fa52e34d 9156d187>
decrypted <4cd9b050 30c04bf9 2a0cb024 19010a31 400c2261 0069196a d77bcae6 9799ae26>

# decryption with different IV
data <cf85cdbe 10a87309 a6fb4c4e ce640619 8f445b70 3738018a e78291a7 b4ea26ce>
iv <12345678 9abcdef1 23456789 abcdef12>
key <78656a1a 337fddd6 fa52e34d 9156d187>
decrypted <4cd9b050 30c04bf9 2a0cb024 19010a31 400c2261 0069196a d77bcae6 9799ae26>

EDIT 3:

The code for -dataForHex: is:

+ (NSData *)dataForHex:(NSString *)hex
{
    NSString *hexNoSpaces = [[[hex stringByReplacingOccurrencesOfString:@" " withString:@""]
            stringByReplacingOccurrencesOfString:@"<" withString:@""]
            stringByReplacingOccurrencesOfString:@">" withString:@""];

    NSMutableData *data = [[NSMutableData alloc] init];
    unsigned char whole_byte = 0;
    char byte_chars[3] = {'\0','\0','\0'};
    for (NSUInteger i = 0; i < [hexNoSpaces length] / 2; i++) {
        byte_chars[0] = (unsigned char) [hexNoSpaces characterAtIndex:(NSUInteger) (i * 2)];
        byte_chars[1] = (unsigned char) [hexNoSpaces characterAtIndex:(NSUInteger) (i * 2 + 1)];
        whole_byte = (unsigned char)strtol(byte_chars, NULL, 16);
        [data appendBytes:&whole_byte length:1];
    }
    return data;
}
Dennis
  • 2,164
  • 3
  • 25
  • 32
  • Note that "If CBC mode is selected and no IV is provided, an IV of all zeroes will be used." So if you compare with an IV of all zero's with NULL then you would expect the same result. If you want better answers, please show sample IV's and decryption results in hexadecimals. – Maarten Bodewes Nov 19 '14 at 23:59
  • I've added example outputs (first two blocks) from encrypting and decrypting my example file. It shows the key, iv and data as they're passed in / out of CCCrypt using the code above. – Dennis Nov 20 '14 at 11:30
  • It seems that the iv is not being used. With the iv the encrypted data is: `d2c2efee 54e43781 549eec03 9db688e1 7c4248e7 e2ac1d80 7105ffae 4043ffb3`. This is using my test code, see new answer. – zaph Nov 20 '14 at 12:21
  • Where is the code for `Cryptor dataForHex`? – zaph Nov 20 '14 at 12:28
  • `CCCrypt` is a simple function with only a few input parameters, there is no magic, it is know to work. If it is not providing the correct results the input parameters are not correct. – zaph Nov 20 '14 at 12:32

4 Answers4

7

It's also worth pointing out that one should never include the mode with the padding options. I've seen this around quite a bit, and I've actually fallen into the same pitfall trying to be a "explicit as possible".

kCCOptionPKCS7Padding | kCCModeCBC

Should be:

kCCOptionPKCS7Padding

Fun fact 1: Both kCCModeEBC and kCCOptionPKCS7Padding share the same value: 1, and would actually evaluate to using kCCOptionPKCS7Padding, which would then default to kCCModeCBC.

Fun fact 2: Using kCCOptionPKCS7Padding | kCCModeCBC evaluates to both kCCOptionPKCS7Padding flag and kCCOptionECBMode being set, which actually results in kCCModeEBC being used.

ocgully
  • 71
  • 1
  • 2
  • @Dennis Actually mentions this up above in the comments, but I did not see it. This perhaps clarifies the problem I ran into as well. – ocgully Mar 05 '15 at 22:33
  • At least I'm not the only one falling into this trap ;) good summary. – Dennis Mar 06 '15 at 09:00
  • using `kCCOptionPKCS7Padding` gives me extra characters on PHP. I've already added `pkcs7_unpad`. (decryption) but still getting extra characters in the beginning. – majidarif Apr 29 '16 at 23:37
6

Used for formatting comment.

With iv:

clear data:   <4cd9b050 30c04bf9 2a0cb024 19010a31>
iv data:      <724a7fc0 0d8ac9d5 f09ff4c1 64d2d1bb>
key data:     <78656a1a 337fddd6 fa52e34d 9156d187>
crypt data:   <d2c2efee 54e43781 549eec03 9db688e1 7c4248e7 e2ac1d80 7105ffae 4043ffb3>
decrypt data: <4cd9b050 30c04bf9 2a0cb024 19010a31>

With iv of 0's:

clear data:   <4cd9b050 30c04bf9 2a0cb024 19010a31>
iv data:      <00000000 00000000 00000000 00000000>
key data:     <78656a1a 337fddd6 fa52e34d 9156d187>
crypt data:   <cf85cdbe 10a87309 a6fb4c4e ce640619 6be7b155 9db3f066 97e461e7 ced7960f>
decrypt data: <4cd9b050 30c04bf9 2a0cb024 19010a31>

It is clear that the iv is not being used in the OP code.

Code for above:

CCCryptorStatus ccStatus   = kCCSuccess;
size_t          cryptBytes = 0;
NSMutableData  *dataOut    = [NSMutableData dataWithLength:dataIn.length + kCCBlockSizeAES128];

ccStatus = CCCrypt( encryptOrDecrypt, // kCCEncrypt or kCCDecrypt
                   kCCAlgorithmAES128,
                   kCCOptionPKCS7Padding,
                   key.bytes, 
                   kCCKeySizeAES128,
                   iv.bytes,
                   dataIn.bytes,
                   dataIn.length,
                   dataOut.mutableBytes,
                   dataOut.length,
                   &cryptBytes);

dataOut.length = cryptBytes;
zaph
  • 108,117
  • 19
  • 176
  • 215
  • That's some very interesting insight... I've added the code for `dataForHex:`. My CCCrypt code basically looks just like yours and when I print out the IV NSData generated by this helper method, it does print out exactly the output I'd expect. Non-zero hex values exactly the way they were put in. – Dennis Nov 20 '14 at 14:56
  • 3
    Gosh... Figured it out thanks to your correct example data. I was passing in `kCCModeCBC`. I understand that CBC is default, but I wanted to be explicit for documentation purpose. However `kCCModeCBC` (with value 2) is not of a `CCOptions` but a `CCMode`. And `CCMode` of value 2 happens to equal `kCCOptionECBMode` in `CCOptions`... So I was accidentally turning on ECB mode when I just wanted to be explicit. Thanks! – Dennis Nov 20 '14 at 15:08
  • Wow, why are they the same type? – Thomas M. DuBuisson Mar 22 '16 at 17:11
  • 1
    @D makes it clear that they are indeed different types. They are different types: `CCCrypt` has `CCOptions options` which has no CBC mode option it is the default. `CCCryptorCreateWithMode` has `CCMode` which includes `kCCModeCBC`. Reading the documentation, [Apple Man Pages](https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CCCrypt.3cc.html) is always an option—as is failure. – zaph Mar 23 '16 at 00:49
4

The iv is only used for the first block on decryption, further blocks use the cipher text from the previous block so it is somewhat self-synchronizing.

Wikipedia image: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

From Wikipedia Block cipher mode of operation.

So, picking up decryption in the middle of a CBC encrypted stream on a block boundary works except for the first block.

zaph
  • 108,117
  • 19
  • 176
  • 215
  • Thanks for you answer; this still does not explain to me why the first block is correctly decrypted though, no matter what I use for the IV. I expect this first block to basically be random garbage with a wrong IV. Same when decrypting anywhere in the middle, since CCCrypt can't now that there is - in theory - data before what I pass it to. So it couldn't access the prior block. – Dennis Nov 19 '14 at 14:57
  • The iv must be correct for the fist block to decrypt correctly. Do some debugging like `NSLog(@"iv: %@", iv);`, I suspect: `Cryptor dataForHex:`. Add to the question an example that exhibits this including: encryptedData (first two blocks is fine), key, iv and decryptedData (first two blocks is fine) in hex dump format. – zaph Nov 19 '14 at 15:22
0

You can read the correct answer here: http://www.remote-exploit.org/archives/2012/01/09/the_apple_idioten_vektor_iv/

Apple made an error in their Crypto Library that assumes that if the IV vector is not provided they automatically set the IV to a zero vector instead of returning an error. An IV should always be provided to ensure the best security and Apple should not be doing their zero assumption as it greatly weakens security and makes it vulnerable to attacks.

USTD
  • 23
  • 3
  • 3
    No, it is a developer error not to provide all inputs and of the correct lengths. From the [Apple Man Pages](https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CCCrypt.3cc.html): *If CBC mode is selected and **no IV is provided, an IV of all zeroes will be used**.* But what weakens security is those who are writing security code without understanding cryptography. – zaph Mar 22 '16 at 15:18
  • 2
    Generally it's only a security error if the same key is used for more than one message without changing the IV between messages. If your application has a different key for each message, than having a fixed IV, even all zeros, is acceptable. However, this puts the onus on ensuring that there are different keys, and ensuring the entire key space is used. (eg: randomly generated rather than plain text strings) – Chris Cogdon Apr 18 '16 at 21:01