Ioshls m3u8 custom AES-128 key chain or link decryption process (play, download, play after downloading local)

Posted by james_4k2 on Mon, 28 Oct 2019 03:53:51 +0100

Ioshls m3u8 custom AES-128 key chain or link decryption process (play, download, play after downloading local)

In fact, in theory, apple supports native direct play AES-128 encryption, as long as it conforms to Apple's encryption standard, but in the actual use process, because there are Android, H5, or based on the existing interface data, or want to customize the key chain security method, or want to encrypt play encrypted link, encrypt key chain link, based on the above requirements, use AVP directly. Lay obviously can't meet all the requirements, so apple is adding a new Api to redirect playback links.

There are few encrypted HLS data on the Internet, especially for local download of encrypted HLS links, and less local playback. I also feel the stones and cross the river. These are some of the data I summarized and my whole learning process.

1. Basic part: how to play unencrypted HLS M3U8 audio or video

In the following, I use audio as an example to add the layer of AVPlayer to the View. There are many examples on the Internet that will not be described in detail.

I use the seven bull link as the test link theory. As long as your play link can be played directly on safri, it can also be played normally with AVPlayer.

    AVURLAsset * asset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"]];
    AVPlayerItem * item = [AVPlayerItem playerItemWithAsset:asset];
    AVPlayer * player = [AVPlayer playerWithPlayerItem:item];
    [player play];    

So look at the content format of unencrypted M3u8 data

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=232370,CODECS="mp4a.40.2, avc1.4d4015"
gear1/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=649879,CODECS="mp4a.40.2, avc1.4d401e"
gear2/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=991714,CODECS="mp4a.40.2, avc1.4d401e"
gear3/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1927833,CODECS="mp4a.40.2, avc1.4d401f"
gear4/prog_index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=41457,CODECS="mp4a.40.2"
gear0/prog_index.m3u8   

The audio header contains the played link and content. AVPlayer will automatically match

2. Advanced: how to play encrypted HLS M3U8 audio or video

In fact, the difference between encrypted and unencrypted is mainly the difference between header files.
This is about Apple's requirements for standard HLS links
https://developer.apple.com/documentation/http_live_streaming/about_the_common_media_application_format_with_http_live_streaming?language=objc

If it is a standard encrypted AVPlayer, it supports native playback in the same way as above
I'll show you how to play non-standard encryption: for example, your key is a part of the service save, or your key is an encrypted key chain
Here I take the seven bull link as an example

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0xb279c05ae6a3d0ffd45da748cf305dd9
#EXTINF:10.927589,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000000.ts
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0x4aec782ea1419fb7865ba5452cbd9413
#EXTINF:9.342667,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000001.ts
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0xeac1912aaab5469783cbb742b9f6f534
#EXTINF:10.719044,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000002.ts
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0x500f42c7a7c969f47bd8886444c2d198
#EXTINF:9.342667,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000003.ts
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0x9b1b6e27eb70eb32c164bb12cde89a86
#EXTINF:10.468789,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000004.ts
#EXT-X-KEY:METHOD=AES-128,URI="http://ogtoywd4d.bkt.clouddn.com/hls128.key",IV=0x947eba85bbefb54f364ce99b0121fbde
#EXTINF:9.300967,
/58IzAY_GglrObBBbbD98wrHIbLk=/llhpmYRGVWfZL8dyCPXwCwKovI9R/000005.ts
#EXT-X-ENDLIST

It can be seen that compared with the unencrypted HLS, there is an extra link called ා EXT-X-KEY:METHOD=AES-128,URI = "http://ogtoywd4d.bkt.clouddn.com/hls128.key" downloading the link will find the links of multiple keys.

curl -I http://ogtoywd4d.bkt.clouddn.com/hls128.key 
HTTP/1.1 200 OK 
Server: Tengine
Content-Type: application/pgp-keys
Content-Length: 17 

But the length of the key chain is not right. The normal key chain is 16. The length of the seven bull key chain is 17. If we find that there is another "\ n" behind the key chain, we need to remove the "\ n" and turn it into a live link. If we use the AVPlayer directly, it will also report an error. It will return the wrong length of the key chain. Next, we will remove it by resetting the way.

(1) redirect playback link AVAssetResourceLoader

Through a resourceLoader in the AVURLAsset attribute, set the agent of the attribute. When the network request is wrong, the server will call back the redirection method of the agent. We can manually make the network request report the error directly, so that we can intercept before the AVURLAsset request and do some custom operations, such as decrypting the link in the URI, or further operating the Key returned by the URI.

AVURLAsset *urlAsset = [[AVURLAsset alloc] initWithURL: [[NSURL alloc] initWithString: @"m3u8Scheme://error.m3u8"] options: nil];
[[urlAsset resourceLoader] setDelegate: self queue: dispatch_get_main_queue()];
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset: urlAsset];
[item setCanUseNetworkResourcesForLiveStreamingWhilePaused: YES]; 

You can see that the system will call back the error because the url we filled in is the wrong url m3u8Scheme://error.m3u8
You can receive the proxy callback at this time

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

(2) modify the Key link in the M3u8 file returned by HLS, so that the link is also called back to the above method.

In the callback, use NSString *url = [[[loadingRequest request] URL] absoluteString];
Get the return string and judge if @ "m3u8Scheme://error.m3u8" is replaced by the content we have recorded to request and the key link in the link is replaced by a wrong link, so that the link can also be called back to the above method.

    if ([url isEqualToString: @"m3u8Scheme://error.m3u8"]) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSData *data = [self M3u8Request: self->m3u8_url];
            if (data) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[loadingRequest dataRequest] respondWithData: data];
                    [loadingRequest finishLoading];
                });
            } else {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self finishLoadingError: loadingRequest];
                });
            }
        });
        return true;
    }
    
    - (NSData *)M3u8Request: (NSString *)url {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    static NSData *result = NULL;
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    NSURLRequest *requset = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    //Return a download task object
    NSURLSessionDownloadTask *loadTask = [manager downloadTaskWithRequest:requset progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"%lld----%lld",downloadProgress.completedUnitCount,downloadProgress.totalUnitCount);
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        
        NSString *fullPath =[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingString:response.suggestedFilename];
        NSLog(@"targetPath-: %@---fullPath:-%@",targetPath,fullPath);
        return  [NSURL fileURLWithPath:fullPath];
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        NSString * string =  [NSString stringWithContentsOfURL:filePath encoding:(NSUTF8StringEncoding) error:nil];
        NSString * newString  = [string stringByReplacingOccurrencesOfString:@"http" withString:@"ckey"];
        result = [newString dataUsingEncoding:NSUTF8StringEncoding];
                dispatch_semaphore_signal(semaphore);
        NSLog(@"Download complete address:%@",filePath);
        
    }];
    [loadTask resume];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return result;
}

(3) repair the redundant "\ n" in the Key

    if (![url hasSuffix: @".ts"] && ![url isEqualToString: @"m3u8Scheme://error.m3u8"]) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                   NSData *data = [self KeyRequest:url];
                   if (data) {
                       dispatch_async(dispatch_get_main_queue(), ^{
                           [[loadingRequest dataRequest] respondWithData: data];
                           [loadingRequest finishLoading];
                       });
                   } else {
                       dispatch_async(dispatch_get_main_queue(), ^{
                           [self finishLoadingError: loadingRequest];
                       });
                   }
               });
        return true;
    } 
    
    
    
    
- (NSData *)KeyRequest: (NSString *)url {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    static NSData *result = NULL;
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
     NSString *newUrl = [url stringByReplacingOccurrencesOfString: @"ckey" withString: @"http"];
    NSURLRequest *requset = [NSURLRequest requestWithURL:[NSURL URLWithString:newUrl]];
    //Return a download task object
    NSURLSessionDownloadTask *loadTask = [manager downloadTaskWithRequest:requset progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"%lld----%lld",downloadProgress.completedUnitCount,downloadProgress.totalUnitCount);
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        
        NSString *fullPath =[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingString:response.suggestedFilename];
        NSLog(@"targetPath-: %@---fullPath:-%@",targetPath,fullPath);
        if ([[NSFileManager defaultManager] fileExistsAtPath: fullPath]) {
            [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil];
        }
    
        return  [NSURL fileURLWithPath:fullPath];
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        NSString * string =  [NSString stringWithContentsOfURL:filePath encoding:(NSUTF8StringEncoding) error:nil];
        NSString * newString  = [string stringByReplacingOccurrencesOfString:@"\n" withString:@""];
        result = [newString dataUsingEncoding:NSUTF8StringEncoding];
        dispatch_semaphore_signal(semaphore);
        NSLog(@"Download complete address:%@",filePath);
        
    }];
    [loadTask resume];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return result;
}

(3) replace the remaining. ts request headers.

In the beginning, we replaced the request link of M3u8, so the system will use this header as the request address when all subsequent ts get files, so we need to replace it here.

if ([url hasSuffix: @".ts"]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSURL * requestUrl =  [NSURL URLWithString:@"Initial link"];
            NSString *newUrl = [NSString stringWithFormat:@"http://%@",[url stringByReplacingOccurrencesOfString: @"m3u8Scheme://error.m3u8" withString: requestUrl.host]] ;
            NSURL *url = [[NSURL alloc] initWithString: newUrl];
            if (url) {
                NSURLRequest *redirect = [[NSURLRequest alloc] initWithURL: url];
                [loadingRequest setRedirect: redirect];
                [loadingRequest setResponse: [[NSHTTPURLResponse alloc] initWithURL: [redirect URL] statusCode: 301 HTTPVersion: nil headerFields: nil]];
                [loadingRequest finishLoading];
            } else {
                [self finishLoadingError: loadingRequest];
            }
        });
        return true;
    }

All the redirects we've completed by this point.
Of course no demo no truth can click the link below to download demo if you can help point a star will be grateful!!!!

Topics: Attribute network encoding Android