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
I have tried
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.
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.