mirror of
https://github.com/qtmleap/revkit.git
synced 2026-09-16 08:11:51 +02:00
Enhances iOS MSL CBOR decoder for multi-item parsing, iOS-specific payload handling, and IV extraction from ciphertext. Updates NetflixCrypto to support Tweak-format key JSON. Extends the Tweak to capture DH key exchange material and session keys more reliably, saving full key histories and implementing better appboot phase tracking. Adds scripts for DH-derived HKDF parameter analysis and appboot key response investigation. Improves documentation on iOS CBOR MSL protocol differences, decryption pipeline, and key extraction workflow. Relates to iOS MSL traffic analysis and decryption research.
8.0 KiB
8.0 KiB
Netflix MSL クライアント共通仕様
プラットフォーム共通の MSL プロトコル、マニフェスト構造、セグメントダウンロード、復号の仕様。 プラットフォーム固有の認証・エンドポイント・DRM は各プラットフォーム別仕様を参照。
1. MSL メッセージ暗号化
1.1 エンベロープ構造
MSL レスポンスは複数の JSON オブジェクトが連結された形式:
{headerdata, signature, mastertoken}{payload, signature}{payload, signature}...
- 1つ目: ヘッダー (mastertoken 含む)
- 2つ目以降: ペイロードチャンク
1.2 ペイロード暗号化フォーマット
各ペイロードチャンクの payload を base64 デコードすると:
{
"ciphertext": "<base64(AES-CBC encrypted data)>",
"sha256": "<base64(HMAC-SHA256)>",
"keyid": "{ESN}_{sequence}",
"iv": "<base64(16-byte IV)>"
}
1.3 復号手順
ivを base64 デコード (16バイト IV)ciphertextを base64 デコード (暗号文)- MSL セッションの
encryption_key(AES-128) と IV で AES-CBC 復号 - PKCS7 パディング除去
- 結果は JSON:
{data, messageid, compressionalgo, sequencenumber} dataを base64 デコード → 圧縮されたペイロード本体- 圧縮展開 (gzip または LZW) → JSON
1.4 圧縮方式
compressionalgo |
方式 | 使用プラットフォーム |
|---|---|---|
(gzip magic 1f8b) |
gzip | StreamFab, Android |
LZW |
LZW | Chrome |
1.5 iOS CBOR MSL (JSON 形式との差異)
iOS は JSON ではなく CBOR (数値キー) でエンコードする。 詳細: ios_msl_decrypt_pipeline.md
主な差異:
- IV: JSON MSL では
"iv"フィールドに格納。CBOR MSL では ciphertext の先頭 16 bytes に prepend (IV フィールドは空) - 復号後 (リクエスト): CBOR bstr(9) ヘッダー + gzip 圧縮 JSON (Base64 経由なし)
- 復号後 (レスポンス):
00 00ヘッダー + raw deflate 圧縮 JSON - 鍵交換: JSON MSL は
ASYMMETRIC_WRAPPED (JWK_RSA)、CBOR MSL は Scheme 3 (DH ベース)
2. マニフェストレスポンス構造
2.1 トップレベル
{
"id": 1775356074,
"version": 2,
"serverTime": 1775356074742,
"result": {
"movieId": 81756595,
"duration": 8523000,
"drmType": "widevine",
"drmVersion": 25,
"drmContextId": "2596051",
"playbackContextId": "E3-Bgilt-vcAxKKB...",
"expiration": 1775399301816,
"video_tracks": [...],
"audio_tracks": [...],
"timedtexttracks": [...],
"servers": [...],
"links": {
"license": { "href": "/license?licenseType=standard&playbackContextId=...&esn=...&drmContextId=..." },
"ldl": { "href": "/license?licenseType=limited&playbackContextId=...&esn=...&drmContextId=..." },
"events": { "href": "/events?playbackContextId=...&esn=..." }
}
},
"common": { "cadToken": "C1-Bgjtt-vcAxKMAQX..." },
"from": "playapi"
}
links.license.href はライセンスチャレンジ時にそのまま使用する相対パス。
2.2 ビデオトラック
{
"trackType": "PRIMARY",
"maxWidth": 1920,
"maxHeight": 1080,
"drmHeader": {
"bytes": "AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQAAAAAAloudsAAAAAAAAAAA==",
"keyId": "AAAAAAloudsAAAAAAAAAAA==",
"drmHeaderId": "000000000968b9db0000000000000000",
"resolution": { "height": 540, "width": 960 }
},
"streams": [...]
}
drmHeader.bytes: PSSH ボックス (base64)。Widevine CDM のgenerateRequestに渡すinitDatadrmHeaderは track レベルに 1 つだが、KID 境界は stream レベル (SD 鍵 / HD 鍵)
2.3 ビデオストリーム
{
"content_profile": "av1-main-L40-dash-cbcs-prk",
"bitrate": 1412,
"peakBitrate": 9675,
"res_w": 1920,
"res_h": 1080,
"framerate_value": 24000,
"framerate_scale": 1001,
"size": 1511911636,
"startByteOffset": 67372,
"vmaf": 97,
"drmHeaderId": "000000000968b9de0000000000000000",
"downloadable_id": "1500103607",
"isDrm": true,
"moov": { "offset": 116, "size": 992 },
"sidx": { "offset": 1108, "size": 33140 },
"ssix": { "offset": 34248, "size": 33124 },
"urls": [
{ "url": "https://{cdn_host}/?o=1&v=21&e={expiration}&t={token}", "cdn_id": 140752 }
]
}
| フィールド | 説明 |
|---|---|
content_profile |
コーデック + 暗号化スキーム識別子 |
drmHeaderId |
この stream の DRM Key ID (hex 32文字) |
downloadable_id |
Netflix 内部ストリーム ID |
urls[].url |
CDN ダウンロード URL (署名付き、有効期限あり) |
moov/sidx/ssix |
MP4 ボックスオフセット (セグメントインデックスに必要) |
startByteOffset |
メディアデータ開始位置 |
size |
全セグメント合計サイズ (bytes) |
2.4 DRM スキーム対応表
| content_profile パターン | 暗号化スキーム | 暗号化方式 | 使用コーデック |
|---|---|---|---|
*-playready-* |
cenc |
AES-128-CTR (全サンプル) | H.264 |
*-cenc-* |
cenc |
AES-128-CTR (全サンプル) | HEVC |
*-cbcs-* |
cbcs |
AES-128-CBC (サブサンプル 1/10) | AV1 |
コーデックごとに異なる KID ペアが割り当てられる。 コーデック切り替え時はライセンス再取得が必要。
2.5 KID 境界 (実測)
同一コーデック内で 2 つの KID が使い分けられる:
- SD (540p 以下): SD 用 Key ID
- HD (720p 以上): HD 用 Key ID
2.6 オーディオトラック
| ソース | プロファイル | チャンネル | ビットレート |
|---|---|---|---|
| licensedmanifest | ddplus-5.1-dash, ddplus-5.1hq-dash |
5.1 | 192, 256, 384, 448, 640 kbps |
| manifest API | xheaac-dash |
2.0 | 32, 64, 96, 192 kbps |
licensedmanifest は 19 言語で 5.1ch を提供。manifest API は 2.0ch のみ。
2.7 CDN サーバー
{
"dns": { "host": "...", "ipv6": "...", "ipv4": "..." },
"id": 140752,
"rank": 1,
"type": "OPEN_CONNECT_APPLIANCE",
"name": "c014.tyo013.ix.nflxvideo.net"
}
rank が低いほど優先。urls[].cdn_id と servers[].id で対応付ける。
3. セグメントダウンロード
3.1 CDN URL 構造
https://{cdn_host}/?o=1&v={version}&e={expiration_unix}&t={signature_token}
| パラメータ | 説明 |
|---|---|
o |
常に 1 |
v |
CDN バージョン (21, 22 等) |
e |
URL 有効期限 (Unix timestamp) |
t |
署名トークン (base64url) |
3.2 DASH セグメントダウンロード手順
Range: bytes=0-{moov.offset + moov.size - 1}で初期化セグメントを取得sidxをパースしてセグメント一覧 (バイトレンジ + 時間) を構築- 各セグメントを
Rangeリクエストで取得
4. 復号
4.1 復号フロー
encrypted_segment + content_key(KID) → decrypted_segment
- セグメントの
moofボックスからtenc(Track Encryption Box) を読み取り senc(Sample Encryption Box) から各サンプルの IV を取得- Key ID に対応するコンテンツ鍵で AES-CTR (cenc) または AES-CBC (cbcs) 復号
4.2 Widevine ライセンスフロー (共通)
- マニフェストの
drmHeader.bytes(PSSH, base64) をデコード - Widevine CDM
generateRequest("cenc", psshBytes)→license-request(protobuf, ~4000 bytes) - MSL 経由で
/pbo_licenses/routerに POST - レスポンスから
license-response(protobuf, ~1000 bytes) 取得 - CDM
update(licenseResponse)でコンテンツ鍵インストール
5. 有効期限
| 項目 | フィールド | 典型値 |
|---|---|---|
| マニフェスト | expiration |
~8 時間 (manifestExpirationDuration) |
| CDN URL | e パラメータ |
~12 時間 (urlExpirationDuration) |
| Cookie | セッション依存 | 数日〜数週間 |