📜 ⬆️ ⬇️

Playing Encrypted On-the-Fly Encrypted Files on iOS

image

In the process of developing an application on the Sencha Touch framework for the iOS platform, it was necessary to implement the playback of local video and audio files that must be encrypted on the server before downloading to the mobile device’s memory. An additional condition was the ban on creating a decrypted version of the file on the disk, thus it became necessary to decrypt and read the data in RAM. Therefore, the standard plug-in from Cordova to play local media files did not fit, although I didn’t have development experience on Objective-C, I decided to create my own, with the required functionality.

The search for a solution led to the AVURLAsset class of the AVFoundation framework, which initializes the media object for the AVPlayer component. To load an AVURLAsset resource, it uses its own resourceLoader object of the AVAssetResourceLoader class, this object works via AVAssetResourceLoaderDelegate, in which you need to define two methods:

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest; - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest; 

The first is used at the beginning and during the boot process, and the second is called when the boot process is canceled. If an unknown resource loading scheme is specified, the resourceLoader will use the methods defined by the developer.
')
Thus, having defined the first method, it is possible to transfer decrypted data in the form of NSData.

An example implementation of the method of loading data via AVAssetResourceLoaderDelegate:

 - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { loadingRequest.contentInformationRequest.contentType = (__bridge NSString *)kUTTypeQuickTimeMovie; loadingRequest.contentInformationRequest.contentLength = movieLength; loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES; [loadingRequest.dataRequest respondWithData:[decryptedData subdataWithRange:NSMakeRange((NSUInteger)loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.requestedLength)]]; [loadingRequest finishLoading]; return YES; } 

In this code, decryptedData contains the result of decrypting the data that was downloaded from an encrypted file.

Below I described an example of player initialization:

Fake path to the local file, the important point is the custom scheme "encryptedfile: //":

 resourceURL = [NSURL URLWithString:[@"encryptedfile://" stringByAppendingString:fake-path-to-file]]; 

The real encrypted file is opened using NSFileHandle:

 fileHandle = [NSFileHandle fileHandleForReadingFromURL:resourceURL error:nil]; 

Below we initialize the player and delegate our own resourceLoader:

 assetPlayer = [AVURLAsset assetWithURL:resourceURL]; [assetPlayer.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; itemPlayer = [AVPlayerItem playerItemWithAsset:assetPlayer]; avPlayer = [AVPlayer playerWithPlayerItem:itemPlayer]; 

Next, create a controller for the player:

 controller = [[AVPlayerViewController alloc] init]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; controller.player = avPlayer; controller.player.actionAtItemEnd = AVPlayerActionAtItemEndNone; [avPlayer play]; 

In essence, this code is fully working, but another problem remains - the memory limitation on mobile devices. We cannot load the decrypted data of a large video file into RAM.

So I decided to encrypt the source files in blocks, in my case 16 megabytes in order to be able to access any necessary block without decrypting the entire file.

I modified the method of the resourceLoader object, which is called when the resource is loaded:

 - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { loadingRequest.contentInformationRequest.contentType = (__bridge NSString *)kUTTypeQuickTimeMovie; loadingRequest.contentInformationRequest.contentLength = movieLength; loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES; if(chunkMode){ NSUInteger offset = (NSUInteger)loadingRequest.dataRequest.requestedOffset; if(currentOffset != offset){ currentOffset = offset; NSUInteger requestedBlock = floor(currentOffset/blockSize); if(currentBlockIndex != requestedBlock){ currentBlockIndex = requestedBlock; // Loading other block of data decryptedData = [self getDataFromFile:currentBlockIndex]; } } if(currentOffset > blockSize*currentBlockIndex){ offset = currentOffset - blockSize*currentBlockIndex; } else { offset = 0; } NSUInteger maxLength = [decryptedData length] - offset; if(loadingRequest.dataRequest.requestedLength < maxLength && loadingRequest.dataRequest.requestedLength <= [decryptedData length]){ maxLength = loadingRequest.dataRequest.requestedLength; } [loadingRequest.dataRequest respondWithData:[decryptedData subdataWithRange:NSMakeRange(offset, maxLength)]]; } else { [loadingRequest.dataRequest respondWithData:[decryptedData subdataWithRange:NSMakeRange((NSUInteger)loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.requestedLength)]]; } [loadingRequest finishLoading]; return YES; } 

In the code, the chunkMode parameter indicates that the file was encrypted in blocks and you need to check the requestedOffset and requestedLength parameters to load the necessary block from the file and to decrypt it. The getDataFromFile function is responsible for this:

 - (NSMutableData *) getDataFromFile:(NSUInteger) index { if(fileHandle){ [fileHandle seekToFileOffset:index*chunksInBlock*chunkSize]; return [NSMutableData dataWithData:[AESCrypt decryptData:[fileHandle readDataOfLength:chunksInBlock*chunkSize] password:PASSWORD chunkSize:blockSize iv:IV]]; } return nil; } 

In my case, I use the AES-128 CBC algorithm for encryption, and the AEScrypt-ObjC library for decryption.

I added one method to decrypt the necessary blocks from an encrypted file (more universal, since in this particular case the size of the necessary block is always equal to the size of the encrypted block):

DecryptData method
 + (NSData*) decryptData:(NSData*)data password:(NSString *)password chunkSize:(NSUInteger)chunkSize { return [self decryptData:data password:password chunkSize:chunkSize offsetBlock:0 countBlock:0 iv:nil]; } + (NSData*) decryptData:(NSData*)data password:(NSString *)password chunkSize:(NSUInteger)chunkSize iv: (id) iv { return [self decryptData:data password:password chunkSize:chunkSize offsetBlock:0 countBlock:0 iv:iv]; } + (NSData*) decryptData:(NSData*)data password:(NSString *)password chunkSize:(NSUInteger)chunkSize offsetBlock:(NSUInteger)offsetBlock countBlock:(NSUInteger)countBlock iv: (id) iv { NSUInteger length = [data length]; if (chunkSize > length) { chunkSize = floor(length/16)*16; } if(countBlock > 0){ length = (offsetBlock+countBlock)*chunkSize; } if(length > [data length]){ length = [data length]; } NSUInteger offset = offsetBlock * chunkSize; NSMutableData *decryptedData = [NSMutableData alloc]; NSData* encryptedPartOfData; do { NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset; NSData* partOfData = [data subdataWithRange:NSMakeRange(offset, thisChunkSize)]; if(iv == nil){ encryptedPartOfData = [partOfData decryptedAES256DataUsingKey:[[password dataUsingEncoding:NSUTF8StringEncoding] SHA256Hash] error:nil]; } else { encryptedPartOfData = [partOfData decryptedAES256DataUsingKey:[password dataUsingEncoding:NSUTF8StringEncoding] initializationVector:iv error:nil]; } [decryptedData appendData:encryptedPartOfData]; offset += thisChunkSize; } while (offset < length); return decryptedData; } 


As a result, we got a plugin that can play large encrypted files without creating the original version of the file on the device disk. At the same time, the use of the device’s RAM has decreased, which is also an important advantage.

Useful links:

AEScrypt-ObjC Library
Article on Habré, which helped to understand the principles of AVAssetResourceLoaderDelegate

Source: https://habr.com/ru/post/319732/


All Articles