mirror of
https://github.com/qtmleap/revkit.git
synced 2026-09-28 05:51:50 +02:00
376 lines
13 KiB
Markdown
376 lines
13 KiB
Markdown
# Netflix Widevine DRM License Exchange - Reference Data
|
|||
|
|
|
||
|
|
> Android アプリ (v9.57.0 / build 63928) から MSL 経由で取得した実データ。
|
||
|
|
> 2026-03-13 にキャプチャ。デバイス: Pixel 4a (5G) / Android 14 / Widevine CDM v17.0.0
|
||
|
|
> **環境**: L3 (ソフトウェア) 強制。レスポンスは Widevine decrypt → CBOR → GZIP → JSON で復元。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 概要
|
||
|
|
|
||
|
|
Netflix Android アプリは **Widevine CDM** DRM を使用してコンテンツを保護している。
|
||
|
|
iOS 版と異なり、マニフェスト取得と初回ライセンス取得が `/licensedManifest` として統合されている:
|
||
|
|
|
||
|
|
1. **licensedManifest (PRE_FETCH)** → ストリーム一覧 + CDN URL + 初回 Widevine ライセンス (limited) を一括取得
|
||
|
|
2. **License Request (standard)** → 再生開始時に standard ライセンスを取得 (Widevine CDM protobuf チャレンジ)
|
||
|
|
3. **License Response** → Widevine ライセンスバイナリを受信、コンテンツ鍵を取得
|
||
|
|
4. **Release License** → 再生終了時にライセンスを解放
|
||
|
|
|
||
|
|
ライセンスリクエスト/レスポンスはすべて **Widevine CryptoContext** で暗号化された MSL ペイロードとして送信される。以下のデータはアプリ内部で MSL 暗号化される前/復号後の平文。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## エンドポイント
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /nq/androidui/samurai/~9.0.0/api
|
||
|
|
```
|
||
|
|
|
||
|
|
MSL URL としては:
|
||
|
|
```
|
||
|
|
/licensedManifest (マニフェスト + 初回ライセンス一括)
|
||
|
|
/license?licenseType={standard|limited}&playbackContextId={id}&esn={esn}&drmContextId={id}
|
||
|
|
```
|
||
|
|
|
||
|
|
### URL パラメータ
|
||
|
|
|
||
|
|
| パラメータ | 説明 |
|
||
|
|
|---|---|
|
||
|
|
| `licenseType` | `standard` (再生開始時) or `limited` (PRE_FETCH / LDL) |
|
||
|
|
| `playbackContextId` | licensedManifest レスポンスで取得した再生セッション ID (Base64url-like) |
|
||
|
|
| `esn` | Netflix デバイス ESN。`NFANDROID1-PRV-P-{L3}-{MODEL}-{userId}-{fingerprint}` 形式 |
|
||
|
|
| `drmContextId` | DRM コンテキスト ID (数値)。licensedManifest レスポンスの `packageId` と対応 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. licensedManifest Request (PRE_FETCH)
|
||
|
|
|
||
|
|
コンテンツ選択時に送信される。iOS の `/manifest` + `/license` を統合した Android 固有の API。
|
||
|
|
バッチリクエストにより複数 viewableId を同時に指定可能。
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"version": 2,
|
||
|
|
"url": "/licensedManifest",
|
||
|
|
"languages": ["en-JP"],
|
||
|
|
"common": {
|
||
|
|
"challenge": "<共通 Widevine CDM protobuf>"
|
||
|
|
},
|
||
|
|
"params": [
|
||
|
|
{
|
||
|
|
"viewableId": "81756595",
|
||
|
|
"method": "licensedManifest",
|
||
|
|
"flavor": "PRE_FETCH",
|
||
|
|
"drmType": "widevine",
|
||
|
|
"manifestVersion": "v2",
|
||
|
|
"licenseType": "limited",
|
||
|
|
"challenges": {
|
||
|
|
"primary": [{
|
||
|
|
"challengeBase64": "<Widevine CDM protobuf>",
|
||
|
|
"drmSessionId": 1,
|
||
|
|
"clientTime": 1773373148
|
||
|
|
}]
|
||
|
|
},
|
||
|
|
"profiles": ["playready-h264mpl30-dash", "playready-h264hpl22-dash", "..."],
|
||
|
|
"profileGroups": [{"name": "primary", "profiles": ["..."]}]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### iOS との違い
|
||
|
|
|
||
|
|
| 項目 | iOS (`/manifest` + `/license`) | Android (`/licensedManifest`) |
|
||
|
|
|---|---|---|
|
||
|
|
| API パス | 分離 (2 リクエスト) | 統合 (1 リクエスト) |
|
||
|
|
| バッチ | 1 viewableId / リクエスト | 複数 viewableId / リクエスト |
|
||
|
|
| DRM チャレンジ | FairPlay SPC (個別) | `common.challenge` + 個別 `challenges.primary` |
|
||
|
|
| DRM タイプ | `fairplay` | `widevine` |
|
||
|
|
| チャレンジ形式 | FairPlay SPC (JSON: CHALLENGES[].PAYLOAD) | Widevine CDM protobuf (バイナリ) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. licensedManifest Response
|
||
|
|
|
||
|
|
L3 Widevine decrypt → CBOR (key 62) → GZIP 展開で復元。8 チャンク、合計 456KB。
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": 1,
|
||
|
|
"version": 2,
|
||
|
|
"serverTime": 1773373149628,
|
||
|
|
"result": [
|
||
|
|
{
|
||
|
|
"movieId": "81756595",
|
||
|
|
"packageId": "2596051",
|
||
|
|
"duration": 8523000,
|
||
|
|
"drmContextId": "2596051",
|
||
|
|
"playbackContextId": "E3-Bgj5tevc...",
|
||
|
|
"video_tracks": [{"..."}],
|
||
|
|
"audio_tracks": [{"..."}],
|
||
|
|
"timedtexttracks": [{"..."}],
|
||
|
|
"servers": [{"..."}],
|
||
|
|
"links": {
|
||
|
|
"events": {"href": "/events?playbackContextId=...&esn=..."},
|
||
|
|
"ldl": {"href": "/license?licenseType=limited&..."},
|
||
|
|
"license": {"href": "/license?licenseType=standard&..."}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### result フィールド解説
|
||
|
|
|
||
|
|
| フィールド | 型 | 説明 |
|
||
|
|
|---|---|---|
|
||
|
|
| `movieId` | string | コンテンツ ID |
|
||
|
|
| `packageId` | string | DRM パッケージ ID (`drmContextId` と対応) |
|
||
|
|
| `duration` | number | 再生時間 (ミリ秒)。`8523000` = 2時間22分3秒 |
|
||
|
|
| `drmContextId` | string | DRM コンテキスト ID |
|
||
|
|
| `playbackContextId` | string | 再生セッション ID (後続の `/license`, `/events` URL に埋め込み) |
|
||
|
|
| `video_tracks` | array | 映像トラック一覧 (複数ビットレート/解像度) |
|
||
|
|
| `audio_tracks` | array | 音声トラック一覧 (多言語 × 複数ビットレート) |
|
||
|
|
| `timedtexttracks` | array | 字幕トラック一覧 (IMSC 1.1) |
|
||
|
|
| `servers` | array | CDN サーバー一覧 (Open Connect Appliance) |
|
||
|
|
| `links` | object | 後続 API エンドポイント (`events`, `ldl`, `license`) |
|
||
|
|
|
||
|
|
### video_tracks の構造
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"trackType": "PRIMARY",
|
||
|
|
"new_track_id": "V:2:1;2;;primary;-1;none;-1;",
|
||
|
|
"dimensionsLabel": "2D",
|
||
|
|
"streams": [
|
||
|
|
{
|
||
|
|
"content_profile": "playready-h264hpl30-dash",
|
||
|
|
"bitrate": 1050,
|
||
|
|
"peakBitrate": 2250,
|
||
|
|
"res_w": 960,
|
||
|
|
"res_h": 540,
|
||
|
|
"framerate_value": 24000,
|
||
|
|
"framerate_scale": 1001,
|
||
|
|
"size": 1181780966,
|
||
|
|
"downloadable_id": "1496730611",
|
||
|
|
"vmaf": 87,
|
||
|
|
"isDrm": true
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
L3 環境での最大解像度は **960x540** (SD)。7 ストリーム (80〜1050 kbps)。
|
||
|
|
|
||
|
|
### servers の構造
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": 140368,
|
||
|
|
"name": "c062.osa001.ix.nflxvideo.net",
|
||
|
|
"rank": 1,
|
||
|
|
"type": "OPEN_CONNECT_APPLIANCE",
|
||
|
|
"dns": {
|
||
|
|
"host": "ipv4-c062-osa001-ix.1.oca.nflxvideo.net",
|
||
|
|
"ipv4": "45.57.82.139",
|
||
|
|
"ipv6": null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
3 台の Open Connect Appliance (大阪: osa001, osa003)。`rank` でフェイルオーバー優先度を指定。
|
||
|
|
|
||
|
|
### iOS manifest レスポンスとの比較
|
||
|
|
|
||
|
|
| 項目 | iOS (`/manifest`) | Android (`/licensedManifest`) |
|
||
|
|
|---|---|---|
|
||
|
|
| レスポンス取得 | **未取得** | **復元済み** (L3 decrypt 経由) |
|
||
|
|
| result 構造 | 単一オブジェクト (推定) | 配列 (バッチ対応) |
|
||
|
|
| links | events, license (推定) | events, ldl, license |
|
||
|
|
| servers | CDN URL 一覧 (推定) | Open Connect Appliance (id, name, rank, dns) |
|
||
|
|
| video_tracks | 不明 | streams[] (bitrate, res, vmaf, downloadable_id) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Standard License Request
|
||
|
|
|
||
|
|
再生開始時に送信される。licensedManifest レスポンスの `links.license.href` を使用。
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"version": 2,
|
||
|
|
"url": "/license?licenseType=standard&playbackContextId=E3-Bgj5tevc...&esn=NFANDROID1-PRV-P-L3-GOOGLPIXEL%3D4A%3D%3D5G%3D-22594-...&drmContextId=2596051",
|
||
|
|
"params": {
|
||
|
|
"clientTime": 1773373168,
|
||
|
|
"challengeBase64": "<Widevine CDM protobuf, ~3.4KB>",
|
||
|
|
"xid": "7616579701369171345"
|
||
|
|
},
|
||
|
|
"languages": ["en-JP"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### params フィールド解説
|
||
|
|
|
||
|
|
| フィールド | 型 | 説明 |
|
||
|
|
|---|---|---|
|
||
|
|
| `clientTime` | number | クライアント時刻 (Unix epoch, seconds) |
|
||
|
|
| `challengeBase64` | string | Base64 エンコードされた Widevine CDM protobuf ライセンスチャレンジ |
|
||
|
|
| `xid` | string | リクエスト ID (トランザクション ID) |
|
||
|
|
|
||
|
|
### challengeBase64 の内容
|
||
|
|
|
||
|
|
iOS の FairPlay SPC が JSON (`CHALLENGES[].PAYLOAD`) であるのに対し、Widevine は **protobuf バイナリ**。
|
||
|
|
内部に以下の情報が含まれる:
|
||
|
|
|
||
|
|
| フィールド | 値 | 説明 |
|
||
|
|
|---|---|---|
|
||
|
|
| `esn` | `NFANDROID1-PRV-P-L3-GOOGLPIXEL=4A==5G=-22594-...` | デバイス ESN (L3 マーカー含む) |
|
||
|
|
| `movieid` | `"81756595"` | コンテンツ ID |
|
||
|
|
| `issuetime` | `1773373148` | 発行時刻 |
|
||
|
|
| `salt` | `"35982634558192635..."` | ランダムソルト |
|
||
|
|
| `oem_crypto_build_information` | `"OEMCrypto Level3 Code"` | OEMCrypto セキュリティレベル |
|
||
|
|
| `widevine_cdm_version` | `"17.0.0"` | CDM バージョン |
|
||
|
|
| `device_name` | `"bramble"` | デバイスコードネーム |
|
||
|
|
| `architecture_name` | `"arm64-v8a"` | CPU アーキテクチャ |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Standard License Response
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": 1,
|
||
|
|
"version": 2,
|
||
|
|
"serverTime": 1773373169221,
|
||
|
|
"result": {
|
||
|
|
"licenseResponseBase64": "<Base64-encoded Widevine license protobuf>",
|
||
|
|
"secureStopExpected": false,
|
||
|
|
"links": {
|
||
|
|
"releaseLicense": {
|
||
|
|
"rel": "releaseLicense",
|
||
|
|
"href": "/releaseLicense?drmLicenseContextId=E3-Bgj5tevc...;EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED;STANDARD;1773373169202"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"drmGroupId": "132",
|
||
|
|
"licenseType": "standard",
|
||
|
|
"expiration": 1773373168000
|
||
|
|
},
|
||
|
|
"common": {},
|
||
|
|
"from": "playapi"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### result フィールド解説
|
||
|
|
|
||
|
|
| フィールド | 型 | 説明 |
|
||
|
|
|---|---|---|
|
||
|
|
| `licenseResponseBase64` | string | Base64 エンコードされた Widevine ライセンスバイナリ (protobuf)。`provideKeyResponse()` に渡す |
|
||
|
|
| `secureStopExpected` | boolean | Secure Stop (再生終了通知) が必要か |
|
||
|
|
| `links.releaseLicense.href` | string | ライセンス解放用 URL。再生終了時にこの URL にリクエストを送る |
|
||
|
|
| `drmGroupId` | string | DRM グループ ID。`"132"` (iOS は `"191-192"` のようにハイフン区切り) |
|
||
|
|
| `licenseType` | string | `"standard"` |
|
||
|
|
| `expiration` | number | ライセンス有効期限 (Unix epoch, milliseconds) |
|
||
|
|
|
||
|
|
### licenseResponseBase64 の内容
|
||
|
|
|
||
|
|
iOS の FairPlay CKC が JSON (`VERSION`, `MEDIASESSIONID`, `RESPONSES[].PAYLOAD`) であるのに対し、
|
||
|
|
Widevine のライセンスレスポンスは **protobuf バイナリ**。内部には:
|
||
|
|
|
||
|
|
- **暗号化されたコンテンツ鍵**: Widevine CDM のセッション鍵で暗号化
|
||
|
|
- **埋め込み JSON**: `version`, `esn`, `issuetime`, `movieid`, `salt`
|
||
|
|
- **鍵 ID**: `CE66E4B447F964AC19000000000000000`
|
||
|
|
- **ライセンスメタデータ**: security_level, expiry_duration
|
||
|
|
|
||
|
|
`android.media.MediaDrm.provideKeyResponse(sessionId, licenseResponseBase64)` に渡すことで
|
||
|
|
コンテンツ復号鍵が Widevine CDM にインストールされる。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Release License (再生終了)
|
||
|
|
|
||
|
|
再生終了時、サーバーに通知する。`links.releaseLicense.href` を使用。
|
||
|
|
|
||
|
|
iOS と同じく、レスポンスは空の `actions` オブジェクト (推定):
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"result": [
|
||
|
|
{
|
||
|
|
"actions": {}
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"id": 1,
|
||
|
|
"common": {},
|
||
|
|
"from": "playapi",
|
||
|
|
"serverTime": 1773373xxx000,
|
||
|
|
"version": 2
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. drmSessionId / videoTrackName フォーマット
|
||
|
|
|
||
|
|
```
|
||
|
|
V:2:1;2;;primary;-1;none;-1;
|
||
|
|
```
|
||
|
|
|
||
|
|
セミコロン区切り。推定される構造:
|
||
|
|
- `V:2:1` — ビデオトラック識別 (V=Video, 以降はインデックス)
|
||
|
|
- `2` — DRM グループ数
|
||
|
|
- (空) — 予約
|
||
|
|
- `primary` — プロファイルグループ (iOS は `ce4` = HEVC)
|
||
|
|
- `-1` — 品質レベル (-1 = auto)
|
||
|
|
- `none` — HDR タイプ (none = SDR)
|
||
|
|
- `-1` — 予約
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. ESN (Entertainment Service Name) フォーマット
|
||
|
|
|
||
|
|
```
|
||
|
|
NFANDROID1-PRV-P-L3-GOOGLPIXEL=4A==5G=-22594-3E369F1C9B189ED664E13DEEEE9BACB67684B4FE6283537D1A67810BFE73E371
|
||
|
|
```
|
||
|
|
|
||
|
|
- `NFANDROID1` — Netflix Android プラットフォーム (iOS は `NFAPPL`)
|
||
|
|
- `PRV` — プロビジョニングタイプ
|
||
|
|
- `P` — 製品タイプ
|
||
|
|
- `L3` — Widevine セキュリティレベル (L3=ソフトウェア、L1=TEE)。iOS には存在しない
|
||
|
|
- `GOOGLPIXEL=4A==5G=` — デバイスモデル (Pixel 4a (5G)、`=` は `,` のエスケープ)
|
||
|
|
- `22594` — ユーザー ID
|
||
|
|
- 末尾 64 文字 — デバイス固有ハッシュ (SHA-256)
|
||
|
|
|
||
|
|
### iOS ESN との比較
|
||
|
|
|
||
|
|
| 項目 | iOS | Android |
|
||
|
|
|---|---|---|
|
||
|
|
| プレフィックス | `NFAPPL` | `NFANDROID1` |
|
||
|
|
| DRM バージョン | `02` (FairPlay v2) | なし |
|
||
|
|
| セキュリティレベル | なし | `L3` or `L1` |
|
||
|
|
| モデル | `IPHONE9=1` | `GOOGLPIXEL=4A==5G=` |
|
||
|
|
| ユーザー ID | なし | あり (`22594`) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. データの関連性まとめ
|
||
|
|
|
||
|
|
```
|
||
|
|
licensedManifest Response
|
||
|
|
├─ playbackContextId ──→ /license Request URL
|
||
|
|
├─ drmContextId / packageId ──→ /license Request URL
|
||
|
|
├─ video_tracks / audio_tracks ──→ CDN ストリーム URL
|
||
|
|
├─ servers[] ──→ CDN サーバー (Open Connect Appliance)
|
||
|
|
└─ links
|
||
|
|
├─ events ──→ /events URL (再生イベント報告)
|
||
|
|
├─ ldl ──→ /license?licenseType=limited (LDL)
|
||
|
|
└─ license ──→ /license?licenseType=standard
|
||
|
|
|
||
|
|
License Request
|
||
|
|
└─ challengeBase64 (Widevine protobuf) ──→ License Response.licenseResponseBase64
|
||
|
|
|
||
|
|
License Response
|
||
|
|
├─ licenseResponseBase64 ──→ MediaDrm.provideKeyResponse() → コンテンツ鍵
|
||
|
|
├─ links.releaseLicense.href ──→ Release License Request URL
|
||
|
|
└─ expiration ──→ standard: ライセンス有効期限
|
||
|
|
```
|