1

The RNDecryptor class in ObjectiveC at HERE has a feature to decrypt file in chunks as follows:

- (IBAction)decryptWithSemaphore:(id)sender {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

__block int total = 0;
int blockSize = 32 * 1024;

NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *input = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"zhuge.rncryptor"];
NSString *output = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:@"zhuge.decrypted.pdf"];

NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:input];
__block NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:output append:NO];
__block NSError *decryptionError = nil;

[cryptedStream open];
[decryptedStream open];

RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:@"12345678901234567890123456789012" handler:^(RNCryptor *cryptor, NSData *data) {
    @autoreleasepool {
        NSLog(@"Decryptor recevied %d bytes", data.length);
        [decryptedStream write:data.bytes maxLength:data.length];
        dispatch_semaphore_signal(semaphore);

        data = nil;
        if (cryptor.isFinished) {
            [decryptedStream close];
            decryptionError = cryptor.error;
            // call my delegate that I'm finished with decrypting
        }
    }
}];

while (cryptedStream.hasBytesAvailable) {
    @autoreleasepool {
        uint8_t buf[blockSize];
        NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize];
        if (bytesRead > 0) {
            NSData *data = [NSData dataWithBytes:buf length:bytesRead];

            total = total + bytesRead;
            [decryptor addData:data];
            NSLog(@"New bytes to decryptor: %d Total: %d", bytesRead, total);

            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }
    }
}

[cryptedStream close];
[decryptor finish];

dispatch_release(semaphore);

}

And the addData method of RNDecryptor is as follows:

- (void)addData:(NSData *)theData
{
  if (self.isFinished) {
    return;
  }

  [self.inData appendData:theData];
  if (!self.engine) {
    [self consumeHeaderFromData:self.inData];
  }
  if (self.engine) {
    NSUInteger HMACLength = self.HMACLength;
    if (self.inData.length > HMACLength) {
      NSData *data = [self.inData _RNConsumeToIndex:self.inData.length - HMACLength];
      [self decryptData:data];
    }
  }
}

I dont understand here what this line is actully trying to do which is called for every chunk of encrypted stream:

  NSData *data = [self.inData _RNConsumeToIndex:self.inData.length - HMACLength];

Lets say I have a Block size of 1000 Bytes and HMACLength is 32.

If I try to decrypt file larger than the size of block size, lets say 5000 bytes, then this addData method will run first iteration as this

NSData *data = [self.inData _RNConsumeToIndex:1000 - 32];

which is after consuming headers the encrypted bytes from index 0 to (1000-32), but the hash is written at the end of the encrypted stream, the last few bytes, not with every chunk. and, in the next iteration, the inputstream will be reading next 1000 bytes, what will happen to the 32 bytes that were trimmed from the first iteration chunk?

May be I am confused as this code is proven but I want to understand this.

Thanks in advance.

user2319247
  • 88
  • 1
  • 9

1 Answers1

1

The issue is that streams normally don't know how much data is left. In your case, it seems that the authentication tag (HMAC value) was put at the end of the ciphertext by the side sending the ciphertext.

Now the issue is that you should only update the data, not the authentication tag. As you don't know how much data is still available, it may be that you are already reading in the authentication tag at the end. Obviously the HMAC calculation will fail if you include the output of the HMAC itself in the calculation.

So basically you read the stream and update the HMAC state until the end. Then you perform the last HMAC update until the end of the ciphertext. You extract the given authentication tag from the end of the stream and you compare the calculated and given value. If they are the same then the ciphertext (and thus the plaintext) is checked for integrity and authentication - given that the secret key was never revealed to an attacker of course.

If the code is correct (and given the code of Rob, that's extremely likely) then the 32 bytes are included in the MAC calculation unless they are indeed part of the authentication tag. In other words, you always have to buffer at least the size of the authentication tag if you put the authentication tag at the end.

You could rewrite the scheme in such a way that the length of the length of the ciphertext is known in advance. You could for instance start the stream by a 64 bit number representing the length of the ciphertext. Then you would not have to do the awkward buffering, at the expense of 64 additional bits. Higher level protocols rely on ASN.1/DER encoding or even XML to separate the message and the authentication tag.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks for the answer @owlstead. I am now trying to write cipher text size inside the header so that my calculation becomes easy and accurate. I'll update you as soon as I implement that. Thanks again. – user2319247 Aug 29 '13 at 11:48
  • As you understand this very clearly can you please have a look at my another question at http://stackoverflow.com/questions/18458425/encryption-between-net-and-iphone - Thanks – user2319247 Aug 29 '13 at 11:58
  • I am facing some strange problem now, the hash in ios in being calculated of different bytes and in .NET its something else. I think the Read method of CryptoStream(.NET) has some incompatibility with ios write( I suspect its block length that is creating some problem). The postion of encrypted file stream increases in multiples of 32 even if I read bytes less than 32. Please tell me what I am doing wrong. Let me know If you want me to put my source code. Thanks – user2319247 Aug 31 '13 at 07:41
  • For block ciphers modes of operation it is normal to process ciphertext by multiples of the block size. You cannot process partial blocks as the underlying cipher only knows how to process one block at the time. The bytes that are not yet required as plaintext are buffered by the stream implementation. Please don't use comments to followup on questions though, simply ask a new question instead - if you cannot find the answer on this site already of course. – Maarten Bodewes Aug 31 '13 at 10:39
  • Thanks for the reply @owlstead. I have posted a new question, please check out at http://stackoverflow.com/questions/18590183/cryptostream-in-net-does-not-decrypt-encrypted-file-from-objective-c – user2319247 Sep 03 '13 at 10:43