2022年12月08日追記
この投稿では、Kotlinを用いてMediaDrmクラスを参照し、DRM、特にGoogle Widevine*1のセキュリティ情報を取得するための話題を取り扱っている。
そこで紹介したコードを応用し、DRM情報に加えて、HDCP情報も取得するAndroidアプリケーションを作成した。次の投稿で取り扱っているので、関心があれば参照してほしい。
impsbl.hatenablog.jp
本文
オンライン動画再生の再生可否、再生解像度に影響を与える要素の一つがDRMだ。セキュリティ上、問題のないことを担保できる再生環境と認められた場合、高解像度での再生が許可される。そうではない場合、低解像度での再生を強いられるか、再生が許可されない場合もある。
この再生可否は、究極的にはDRMライセンス・サーバーを運用するプラットフォーマーの一存によって決まる。例えば、手許のスマートフォンでDRM Info*2のようなアプリケーションを起動し、Widevine L1が出力されたからと言って、それが常に保証されるわけではない。後述するように、プラットフォーマーの一存によってセキュリティ・レベルが格下げされることもあるのだ。
とはいえ、次のような事柄は気になるところだ。
- そのレベルは何を根拠に出力されているのか
- 具体的にどのような環境であればよいのか
この投稿では、この疑問を解消するための方法、プログラムを紹介する。なお投稿中に掲載しているコードは、コード全体からの抜粋だ。投稿の読みやすさを考慮して、折り返し表示している。参照する場合は、適宜展開すること。コードの全文は投稿末尾に掲載している。
コンテンツ保護としてのDRM (Digital Rights Management)、そしてWidevine
DRMで言うところのセキュリティとは、コンテンツ保護の観点からのセキュリティを指している。それは海賊版の流出を防ぎ、コンテンツ・プロバイダの収益を保護するための仕組みであり、具体的には、
- コピーできない
- 録画できない
- キャプチャできない
ことを担保するコンテンツ再生環境、それを実現する暗号化、復号化対応を対象としている。
これはスマートフォンのような特定端末だけではなく、「再生環境」を前提としたエコシステムを前提としている。例えば、スマートフォンにストリーミングした動画を、テレビやプロジェクタで再生することがある。このような構成要素をすべて含めた「再生環境」を指している。
スマートフォンがWidevine L1だったとしても、接続しているテレビやプロジェクトを含めて低レベルと判定されることもある。例えば、Widevine L2だ。そして実際、このような制約があることを明らかにしているプロジェクタも存在する。
Can't mirror Netflix and other similar streaming apps on iOS devices due to copyright limits
ETOE E2 FHD projector (A1191)etoeofficial.com
このエコシステムにおける構成要素の一つがライセンス・サーバーだ。スマートフォンがWidevine L1だったとしても、接続しているテレビやプロジェクトを含めて低レベルと判定されることもあれば、実際に再生する端末の仕様がどうあれ、そのライセンス発行可否によって、究極的には再生可否が決まる。
Widevineの場合、たとえ最高レベルであるL1と判定されたスマートフォンであったとしても、時を経てL3へ格下げされることもあるのだ。そして、そのような運用は既に実施されている。
qiita.com
DRMセキュリティ・レベルの調べ方 - Android端末の場合
Widevine L1のような情報を直接出力するメソッドや定数は存在するのだが、API level 29~30 (Android 10~11)あたりで廃止されている。
How to determine if Android device is security Level 1 or Level 3 - Widevine Help
MediaDrm.SecurityLevel | Android Developers
とはいえ、それらがすぐに機能しなくなるわけではない。特にDRM Infoのようなアプリケーションは、より広いバージョン範囲でAndroid OSをサポートするため、より古いAPI levelを採用しているからだ。しかしいずれ、L1~3といった表示は、そのような機能からの直接出力ではなく、より粒度の細かい出力結果を総合的に判断した結果として出力されるようになるのだろう。具体的には、
HDCP | High bandwidth Digital Content Protection 最終出力環境までの経路 |
暗号化スキーム | DRMキー管理 コンテンツの暗号化、復号化 |
これらを司るのがMediaDrmクラスだ。*3このクラスを活用し、次の各レベルで対応状況を調査することになる。
保護システム | 対応している保護システム 例:Google Widevine, Microsoft PlayReady, Apple FairPlay |
コンテンツ・タイプ | 対応しているメディア 例:MP4, WebM |
暗号化スキーム | 対応可否、どのようなスキームか? 例:ハードウェア支援、ソフトウェア支援 |
ちなみにHDCPは、その対応機器を含めて確認する必要があるため、この投稿では取り上げない。HDCPについては、次の投稿で触れている。
DRM review Ver 1.1 - DRM (Widevine)、HDCPなどの情報を表示するAndroidアプリ - Technically Impossible
保護システム・レベルの確認
保護システムとはWidevineやMicrosoft PlayReady*4、Apple FiarPlay*5といった、各社DRM保護システムを指している。そしてスマートフォンやPC webブラウザには、それらに応じたプラグインが導入されている。そのプラグインに応じた詳細情報を、それぞれのUUIDを用いて取得してみる。コードは次のようになる。
ちなみにWidevine L1といったセキュリティ・レベルは、このレベルでの情報だ。
🔎コード抜粋:対応システムと、そのプロパティの確認処理
val systemInfo = listOf( UUID.fromString(getString(R.string.uuid_widevine)), UUID.fromString(getString(R.string.uuid_playready)), UUID.fromString(getString(R.string.uuid_fairplay)) ) var myOutput = "" systemInfo.map { sys_key -> sys_key to MediaDrm.isCryptoSchemeSupported(sys_key) }.forEach { (sys_key, sys_val) -> myOutput += "----- status - protection system level\n" myOutput += "$sys_key \t $sys_val\n" if (sys_val){ myOutput += outputSystemLevelStatus(sys_key) myOutput += outputContentLevelStatus(sys_key) } } binding.txtHello.text = myOutput
private fun outputSystemLevelStatus(myUuid: UUID):String{ val mediaDrmInfo = listOf( MediaDrm.PROPERTY_DESCRIPTION, MediaDrm.PROPERTY_VERSION, MediaDrm.PROPERTY_VENDOR, MediaDrm.PROPERTY_ALGORITHMS, "securityLevel" ) val myMediaDrm = MediaDrm(myUuid) var myOutput = "" mediaDrmInfo.map { md_key -> md_key to myMediaDrm.getPropertyString(md_key) }.forEach { (md_key, md_val) -> myOutput += "$md_key \t $md_val\n" } return myOutput }
各社DRM保護システムのUUIDは、次のサイトを参照すると良い。
dashif.org
UUIDで指定したプラグインが存在していたら、そのプロパティ情報を出力する。その出力が冒頭画像の前半だ。Android Studioに搭載されているエミュレータは、Google Widevine CDM Version 15を搭載しており、結果としてWidevine L3と判定された。その根拠は、コンテンツ・タイプ以下の情報に由来するものだ。
コンテンツ・タイプレベルの確認
🔎コード抜粋:コンテンツ・タイプごとの暗号化、復号化確認処理
private fun outputContentLevelStatus(myUuid: UUID):String{ val mimeInfo = listOf( getString(R.string.mime_mp4), getString(R.string.mime_webm) ) val securityLevelInfo = listOf( MediaDrm.SECURITY_LEVEL_UNKNOWN, MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL ) var myOutput = "----- status - content type level\n" mimeInfo.map { mime_key -> mime_key to MediaDrm.isCryptoSchemeSupported(myUuid, mime_key) }.forEach { (mime_key, mime_val) -> myOutput += "$mime_key \t $mime_val\n" if (mime_val){ securityLevelInfo.map { sl_key -> sl_key to MediaDrm.isCryptoSchemeSupported(myUuid, mime_key, sl_key) }.forEach { (sl_key, sl_val) -> myOutput += "$sl_key \t $sl_val\n" } } } return myOutput }
MIME typeは、次のサイトを参照すると良い。
www.iana.org
developer.mozilla.org
developer.mozilla.org
目的のコンテンツ・タイプに該当するMIME typeを指定し、対応しているセキュリティ・レベルを出力する。その出力結果が冒頭画像の後半だ。出力結果に含まれる0~5の数字は、それぞれ次のように対応している。
0 | MediaDrm.SECURITY_LEVEL_UNKNOWN |
1 | MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO |
2 | MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE |
3 | MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO |
4 | MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE |
5 | MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL |
つまりエミュレータはソフトウェア・レベルでの暗号化、復号化にしか対応しておらず、その結果としてWidevine L3の判定に繋がっている。
同じ処理を実機で確認すると、異なる結果を返す。Surface Duo、Nokia 8 Siroccoは、どちらもWidevine L1と判定された。
Surface DuoはAndroid 12Lをインストールしているため、搭載されているWidevine CDMのバージョンも新しい。Nokia 8 Siroccoはエミュレータと同じ保護システムが搭載されているが、Widevine L1と判定されている。
「SECURITY_LEVEL_HW_SECURE_ALL」は、求められていること全てに対応していることを示している。つまり、ハードウェア、ソフトウェアでの暗号化、復号化対応可否に関わらず、この判定がTrueであることがWidevine L1判定に通じているのだろう。
Surface Duo | Nokia 8 Sirocco |
---|---|
![]() |
![]() |
コード全文