背景
在iOS開發(fā)的一些業(yè)務(wù)場景中,可能有一些敏感信息(如付款的二維碼等),我們不希望被隨意傳播。應(yīng)用內(nèi)禁止截屏,可以一定程度上,提高敏感信息被傳播的門檻(可能需要另外一臺手機(jī)拍照,并手機(jī)間傳輸)。
然而,由于iOS系統(tǒng)的特殊性,常規(guī)方法無法完全禁止用戶截屏落地成系統(tǒng)圖片。目前,網(wǎng)上的解決方案,主要有兩種:
- 監(jiān)聽截屏后通知(
UIApplicationUserDidTakeScreenshotNotification),并進(jìn)行提示。
- 監(jiān)聽截屏后通知(
- 讓用戶安裝禁用屏幕快照和屏幕錄制的配置文件。
前者無法禁止截屏內(nèi)容落地,后者閹割了手機(jī)功能,導(dǎo)致其他應(yīng)用也無法截屏。二者均不能滿足需求。
其他刪除本地相冊的方案在新系統(tǒng)上不能滿足需求。
本文在研究了參考資料給出的一些建議,使用DRM最終實現(xiàn)了對控件的防止截屏功能。
該方案有以下特點:
- 可以做到真正意義防止敏感內(nèi)容落地。
- 沒有用到私有api,是蘋果原生支持的。
- 支持主流系統(tǒng)版本。
注: 本文方案只能對敏感控件進(jìn)行防截屏處理,無法做到全局任何位置防截屏(全部控件加drm,代價太大)。
使用DRM實現(xiàn)防止截屏
蘋果系統(tǒng)是支持DRM(Digital Rights Management,數(shù)字版權(quán)管理)的。它表現(xiàn)在,當(dāng)你播放一個加密了的hls流時,你進(jìn)行截屏(用手機(jī)截屏或用Xcode截屏),該視頻控件會顯示空白。
所以,我們的思路就很清晰了。我們可以把一個敏感信息的控件,轉(zhuǎn)化為帶DRM加密的視頻,然后播放。此后,系統(tǒng)進(jìn)行截屏?xí)r,該控件就會消失,達(dá)到防止敏感內(nèi)容落地的目的。
你可以在敏感信息控件后放個背景,用來在截屏?xí)r敏感控件消失后,做更友好的提示。如demo所示。
演示工程的效果如下:

如demo所示,截屏截不到真實的內(nèi)容,敏感信息會被過濾。
我們的整個流程差不多如下:
1) 根據(jù)控件內(nèi)容生成mp4文件
2) 啟動webserver
3) 本地播放帶DRM加密的hls流
其中1) 不是重點,我們將放后面講。我們先驗證DRM是否真的可以做到防止截屏。假設(shè),我們已經(jīng)把一個文本內(nèi)容轉(zhuǎn)換為mp4了(demo工程中的text.mp4)。那么,我們首先讓這個視頻可以播放起來。
啟動webserver
我們需要把該mp4拷貝到一個目錄后,啟動webServer,如demo中,我們把它拷到tmp目錄下(演示用,實際可以做到mp4數(shù)據(jù)也不落地)。
[_webServer addGETHandlerForBasePath:@"/" directoryPath:dir indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:8989 bonjourName:nil];
然后,就可以播放該mp4流了。
[Self.player playURL:@"http://localhost:8989/text.mp4" inView:self.labelContainer];
播放帶hls加密的流
如果你按上面播放,發(fā)現(xiàn)只是單純的展示內(nèi)容,并沒有防截屏的效果。我們需要加密播放流。
你可能需要一些AVPlayer播放視頻的基礎(chǔ),可以參考[iOS]仿微博視頻邊下邊播之封裝播放器和Playing Offline HLS with AES-128 encryption iOS。
當(dāng)你理解了AVAssetResourceLoaderDelegate了后,就可以開始了。我們讓播放器去播放一個私有協(xié)議的m3u8文件。
[self.player playURL:@"jedi://text.m3u8" inView:self.labelContainer];
而當(dāng)它無法解析時,就需要走AVAssetResourceLoaderDelegate,我們在其中,返回寫死的text.m3u8數(shù)據(jù)。
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-VERSION:5
#EXT-X-TARGETDURATION:1
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="jedi://text.key"
#EXTINF:0.067,
http://localhost:8989/text.mp4
#EXT-X-ENDLIST
m3u8中寫入了mp4的url地址,同時,也通過EXT-X-KEY來描述當(dāng)前的加密key,說明當(dāng)前的流是經(jīng)過加密的。
同樣,這里的key的獲取也是用的私有協(xié)議,同樣需要走AVAssetResourceLoaderDelegate。所以,最終AVAssetResourceLoaderDelegate中的方法看起來如下:
-(BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest{
NSString *url = loadingRequest.request.URL.absoluteString;
if ([url isEqualToString:@"jedi://text.m3u8"]) {
NSData *data = [[self gen_m3u8] dataUsingEncoding:NSUTF8StringEncoding];
[loadingRequest.dataRequest respondWithData:data];
[loadingRequest finishLoading];
}
else if([url isEqualToString:@"jedi://text.key"]) {
NSMutableData *data = [NSMutableData dataWithLength:16];
[data resetBytesInRange:NSMakeRange(0, [data length])];
[loadingRequest.dataRequest respondWithData:[data copy]];
[loadingRequest finishLoading];
}
return YES;
}
這里,我們返回的key是16字節(jié)純0的數(shù)據(jù),其實mp4文件并沒有加密,只是讓系統(tǒng)以為加密了,好在截屏?xí)r進(jìn)行保護(hù)。
如果你看到此,應(yīng)該已經(jīng)基本掌握了防止截屏的大致流程了。如果效果無法達(dá)到的話,可以隨時參照demo中的示例。
編碼生成mp4
這里用AVAssetWriter和CVPixelBuffer即可生成mp4。這里參考了https://github.com/caferrara/img-to-video.git的代碼。
VTMP4Encoder中包含根據(jù)view生成mp4的邏輯,這里不展開,感興趣的可以查看代碼。
注:要注意,AVAssetWriter要設(shè)置shouldOptimizeForNetworkUse = YES,讓它支持faststart,否則在m3u8中播放不了。
生成的mp4可以在demo中試用,或在mac端啟nginx配置m3u8測試。
封裝成SDK
TODO。 目前只大概封裝了encoder,完整的封裝,敬請期待。
TODO
優(yōu)化生成mp4算法
獲取mp4也用
AVAssetResourceLoaderDelegate實現(xiàn)
目前mp4獲取主要借助GCDWebServer,下個版本考慮去掉對GCDWebServer的依賴。添加對錄屏、投屏?xí)r的監(jiān)聽和處理
目前可以做到防止手機(jī)截屏和通過Xcode的截屏,但沒有監(jiān)控錄屏和投屏(這種監(jiān)聽很容易實現(xiàn),待補(bǔ)充)。
參考資料
1. Prevent screen capture in an iOS app