Question

Using RNCryptor to encrypt and decrypt a file and have an issue that I don't seem to be getting the complete file back.

My encrypt is as follows

- (void) encryptDownloadedFile:(NSString*)filename
{
    NSString *outputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov", filename]];

    int blockSize = 32 * 1024;

    __block NSInputStream *plainTextStream = [NSInputStream inputStreamWithData:[downloadFileStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]];
    __block NSOutputStream *encryptedStream = [NSOutputStream outputStreamToFileAtPath:outputTmpFilePath append:NO];
    __block NSMutableData *downloadedFileData = [NSMutableData data];

    [plainTextStream open];
    [encryptedStream open];

    __block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
    __block RNEncryptor *encryptor = nil;

    dispatch_block_t readStreamBlock = ^{
        [data setLength:blockSize];
        NSInteger bytesRead = [plainTextStream read:[data mutableBytes] maxLength:blockSize];

        if (bytesRead < 0) {
            // Throw an error
        }

        else if (bytesRead == 0) {
            [encryptor finish];

            [downloadedFileData writeToFile:outputTmpFilePath atomically:YES];

            [plainTextStream close];
            [encryptedStream close];
            [downloadFileStream close];
            plainTextStream = nil;
            encryptedStream = nil;
            downloadFileStream = nil;

        }
        else {
            [data setLength:bytesRead];
            [encryptor addData:data];
        }
    };

    encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                         password:@"blah"
                                          handler:^(RNCryptor *cryptor, NSData *data)     {

                                                  [downloadedFileData appendBytes:data.bytes length:data.length];

                                                  if (cryptor.isFinished) {

                                                  }
                                                  else {
                                                      readStreamBlock();
                                                  }
                                              }];
    readStreamBlock();
}

Pretty much standard from the example on the RNCryptor git page. The input file is a downloaded file that was grabbed earlier with

downloadFileStream = [[NSOutputStream alloc] initToMemory];

Decrypt for completeness is here

- (void) decryptDownloadedFile:(NSString*)filename
{
    NSString *inputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov", filename]];
    NSString *outputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"decrypted%@.mov", filename]];

    int blockSize = 32 * 1024;

    NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:inputTmpFilePath];
    NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:outputTmpFilePath append:NO];

    [cryptedStream open];
    [decryptedStream open];

    __block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
    __block RNDecryptor *decryptor = nil;

    dispatch_block_t readStreamBlock = ^{
        [data setLength:blockSize];
        NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
        if (bytesRead < 0) {
            // Throw an error
        }
        else if (bytesRead == 0) {
            [decryptor finish];

            [decryptedStream close];
        }
        else {
            [data setLength:bytesRead];
            [decryptor addData:data];
        }
    };

    decryptor = [[RNDecryptor alloc] initWithPassword:@"blah"
                                          handler:^(RNCryptor *cryptor, NSData *data)     {
                                                  [decryptedStream write:data.bytes maxLength:data.length];
                                                  if (cryptor.isFinished) {

                                                  }
                                                  else {
                                                      readStreamBlock();
                                                  }
                                              }];
    readStreamBlock();    
}

Again very similar to the git page.

However, I am 60 bytes short at the tail when I encrypt and decrypt the same file. Not a huge issue until I start to finesse this to support resumable downloads. Then the missing bytes are critical as they appear in the centre of the file.

I have checked what is coming in and out as below

  • Bytes being sent to the encryptor 19,615,005 (the size of the file)
  • Encrypted bytes created by the encryptor 19,615,026
  • Bytes received by the decrypt or 19,615,026
  • Decrypted bytes from the decryptor 19,614,944

I have tried

  1. Changing the .options = kCCOptionPKCS7Padding in RNCryptor.h
  2. Tried padding to the block size and to a 16 byte boundary myself
  3. Changed the encryptor finish and readStreamBlock calls to make sure its not a timing issue
  4. Investigated finishWithError in RNCryptorEngine, but there are no errors

Padding to the block size sort of works as I can then clean up the zeros that I padded with, but its not ideal.

I am scratching my head. Anyone been through this loop before?

Update:

Spent a long time scratching my head and realised I don't have the capability to figure out the issue in a timely fashion. However, I stepped backwards to the simple solution and this works, but not for me as this gives me memory issues.

- (void) simpleEncrypt:(NSString*)filename
{
    NSLogDebug(@"simpleEncrypt");

    NSString *outputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov", filename]];

    NSData *data = [downloadFileStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
    NSError *error;
    NSData *encryptedData = [RNEncryptor encryptData:data
                                    withSettings:kRNCryptorAES256Settings
                                        password:@"blah"
                                           error:&error];

    [encryptedData writeToFile:outputTmpFilePath options:NSDataWritingAtomic error:&error];

    NSLogDebug(@"simpleEncrypt isFinished");
}

- (void) simpleDecrypt:(NSString*)filename
{
    NSLogDebug(@"simpleDecrypt");

    NSString *inputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov", filename]];
    NSString *outputTmpFilePath = [downloadCacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"decrypted%@.mov", filename]];

    NSData *encryptedData = [[NSFileManager defaultManager] contentsAtPath:inputTmpFilePath];

    NSError *error;
    NSData *decryptedData = [RNDecryptor decryptData:encryptedData
                                    withPassword:@"blah"
                                           error:&error];

    [decryptedData writeToFile:outputTmpFilePath options:NSDataWritingAtomic error:&error];

    NSLogDebug(@"simpleDecrypt isFinished");
}

If I mix it with the previous solution I get the same problem.

Was it helpful?

Solution

You moved the stream close, and this is probably the problem. In the example linked, in readStreamBlock, there is this code:

else if (bytesRead == 0) {
 [decryptor finish];
}

then inside the decryption handler, there is this code:

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

You moved the close into readStreamBlock:

    else if (bytesRead == 0) {
        [decryptor finish];

        [decryptedStream close];
    }

That means that the stream is closed before the asynchronous decryption completes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top