※この記事はDRMが施されたHLSの復号についてではなく、AES等で暗号化されたHLSを復号する方法について記載しています。
HLSの暗号化の仕様
HLSストリームの暗号化に関する情報はm3u8ファイルの#EXT-X-KEY
に記載されています。
例えば、以下のファイルでは暗号化の方法・暗号化キーのurl・初期化ベクトルの値がわかります。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:1653
#EXT-X-DISCONTINUITY-SEQUENCE:36
#EXT-X-KEY:METHOD=AES-128,URI="https://hogehoge",IV=0xdad51a9c61cc4344c03a566980ac21e7
#EXTINF:8.333333,
/ts/CD8xZLiy68pVp3/h264/480/a1qLvavaVwX.ts
#EXTINF:8.333334,
/ts/CD8xZLiy68pVp3/h264/480/LAAK1hXHVLa.ts
#EXTINF:8.333333,
/ts/CD8xZLiy68pVp3/h264/480/KXzsNPf8KYx.ts
#EXTINF:8.333333,
/ts/CD8xZLiy68pVp3/h264/480/JmHJgZGPZQA.ts
このキーファイルのURIはData URI schemeでもExoPlayerで再生することができます(参考)。
つまり、暗号化キーのUrlではなくキーそのものをm3u8ファイルにベタ書きしてHLSを配信・再生することが可能だということです。
パーサをカスタマイズする
HLSに限らず、ExoPlayerではDataSourceを自由にinjectすることが可能です。
ここではデフォルトで使用されているHlsPlaylistParserの代わりに自前でカスタマイズしたHLSMediaParserを使用してExoPlayerを初期化します。
class CustomHlsPlaylistParserFactory() : HlsPlaylistParserFactory {
override fun createPlaylistParser(): ParsingLoadable.Parser<HlsPlaylist> =
CustomHlsPlaylistParser()
override fun createPlaylistParser(masterPlaylist: HlsMasterPlaylist): ParsingLoadable.Parser<HlsPlaylist> =
CustomHlsPlaylistParser(masterPlaylist)
}
class CustomHlsPlaylistParser() : ParsingLoadable.Parser<HlsPlaylist> {
@Throws(IOException::class)
override parse(uri: Uri, inputStream: InputStream): HlsPlaylist {
// パーサを実装
// HlsPlaylistParser#parseMediaPlaylist`のfullSegmentEncryptionKeyUri`を任意のData Uri schemeに書き換えます
}
private fun toDataUriScheme(file: File): String {
val base64str = Base64.encodeToString(file.readBytes(), Base64.NO_WRAP)//NO_PADDINGは使用できません
return "data:text/plain;base64,$base64str"
}
}
fun initPlayer(remoteUrl: String): HlsMediaSource {
val userAgent = WebView(getApplication()).settings.userAgentString
val hppf = CustomHlsPlaylistParserWrapper(getApplication(), this)
val dsf = DefaultDataSourceFactory(getApplication(), userAgent)//DefaultDataSourceFactory
val mediaSource = HlsMediaSource.Factory(dsf)
.setPlaylistParserFactory(hppf)
.createMediaSource(remoteUrl.toUri())
player.prepare(mediaSource)
player.playWhenReady = true
}
なお、HLSの仕様書によればAES-128ではキーのパディングを省略できないようです。
An encryption method of AES-128 signals that Media Segments are completely encrypted using the Advanced Encryption Standard (AES) [AES_128] with a 128-bit key, Cipher Block Chaining (CBC), and Public-Key Cryptography Standards #7 (PKCS7) padding [RFC5652].
また、DataSourceFactoryにはDefaultHttpDataSourceFactoryではなくDefaultDataSourceFactoryを用います(DRMを使用する場合はDataSchemeDataSource.Factory)。もしDefaultHttpDataSourceFactoryを使用すると、キーのURIがUrlとして扱われてしまい、MalformedURLExceptionがスローされます。
これで任意のキーに書き換えることができました。