# Netflix MSL UserAuthData — Android 動的解析レポート Netflix Android アプリ (`com.netflix.mediaclient` v9.57.0) の MSL 通信におけるユーザー認証データ (`userauthdata`) の生成・送信メカニズムを、Frida フックおよびキャプチャ済み暗号平文の CBOR 解析により明らかにした結果をまとめる。 --- ## 1. 結論サマリー | 項目 | 内容 | |---|---| | **userauthdata の送信タイミング** | **初回認証時のみ 1 回** (鍵交換 + エンティティ認証) | | **使用される認証スキーム** | `NETFLIXID` (netflixId + secureNetflixId) | | **通常リクエストの認証方式** | `UserIdToken` (key 18) — サーバー発行のトークン | | **CBOR キー** | userauthdata = **key 47**, useridtoken = **key 18** | | **apiRequest 第5引数** | 常に `null` — userauthdata は MslControl 層で MSL MessageHeader に直接埋め込まれ、apiRequest パラメータとしては渡されない | --- ## 2. ユーザー認証フロー ```mermaid %%{init:{'theme':'dark'}}%% sequenceDiagram participant App as Netflix App participant MSL as MslControl participant WV as WidevineCryptoContext participant Server as Netflix Server rect rgba(200,100,100,0.15) Note over App,Server: 初回認証 (1回のみ) App->>MSL: apiRequest(url, body, headers, userId, null) Note over MSL: MslControl が MessageHeader を構築 MSL->>MSL: userauthdata を MessageHeader に埋め込み
(CBOR key 47) Note over MSL: scheme: NETFLIXID
authdata: {netflixId, secureNetflixId} MSL->>WV: encrypt(headerdata) MSL->>Server: MSL Message (暗号化済み) Server->>MSL: keyresponsedata + mastertoken
+ UserIdToken end rect rgba(100,200,100,0.15) Note over App,Server: 以降のリクエスト (348回観測) App->>MSL: apiRequest(url, body, headers, userId, null) Note over MSL: MslControl が MessageHeader を構築 MSL->>MSL: UserIdToken を MessageHeader に埋め込み
(CBOR key 18) MSL->>WV: encrypt(headerdata) MSL->>Server: MSL Message (暗号化済み) end ``` --- ## 3. キャプチャデータによる証明 ### 3.1 MSL MessageHeader の認証キー分布 353 件の暗号化前 MSL MessageHeader (Widevine encrypt 平文) を CBOR 解析した結果: | 認証方式 | CBOR キー | 件数 | 割合 | |---|---|---|---| | **UserIdToken のみ** | key 18 | 348 | 98.6% | | **userauthdata のみ** (NETFLIXID) | key 47 | 1 | 0.3% | | **どちらもなし** (鍵交換 handshake) | — | 5 | 1.1% | | **両方** | key 18 + 47 | 0 | 0% | ### 3.2 userauthdata が送信されたヘッダー (1件) ファイル: `logs/android_20260313/android14.prod.ftl.netflix.com/0052_msl.widevine.encrypt.json` ``` MSL MessageHeader CBOR キー: [17, 19, 20, 21, 22, 24, 36, 40, 41, 42, 47] 17: mastertoken (list) 19: renewable = false 20: sender = "" (ESN) 21: handshake = true ← 鍵交換ハンドシェイク 22: messageid = 2491352839960294 24: timestamp = 1773392548 40: peer = true 41: nonreplayable_id = 10 42: keyrequestdata (WIDEVINE) 47: userauthdata ← ★ ここにユーザー認証データ ``` ### 3.3 userauthdata の構造 (CBOR key 47) ```json { "30": "NETFLIXID", "35": { "56": "", "60": "" } } ``` | CBOR キー | フィールド名 | 値 | |---|---|---| | `30` | `scheme` | `NETFLIXID` | | `35` | `authdata` | (dict) | | `35.56` | `netflixId` | `v=3&mac=AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.&dt=1773392547767` | | `35.60` | `secureNetflixId` | `v=3&ct=<暗号化トークン 672文字>&pg=ZEULH5S2GNGCRAABCSG6J2EGGA&ch=` | ### 3.4 netflixId の構造 ``` v=3&mac=AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.&dt=1773392547767 ``` | パラメータ | 値 | 意味 | |---|---|---| | `v` | `3` | バージョン | | `mac` | `AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.` | HMAC 署名 (改ざん検知) | | `dt` | `1773392547767` | デバイスタイムスタンプ (Unix ms) | ### 3.5 secureNetflixId の構造 ``` v=3&ct=<暗号化トークン>&pg=ZEULH5S2GNGCRAABCSG6J2EGGA&ch= ``` | パラメータ | 値 | 意味 | |---|---|---| | `v` | `3` | バージョン | | `ct` | `BgjHlOvcAxLcA5Nne...` (672 文字) | 暗号化された認証トークン (protobuf base64url) | | `pg` | `ZEULH5S2GNGCRAABCSG6J2EGGA` | プロファイル GUID | | `ch` | `AQEAEAABABTp79nN9l_2MuRhqTXl0-SjAqcm83QU8vw.` | チャネル HMAC | --- ## 4. UserIdToken の構造 (通常リクエスト) 初回認証後、サーバーから発行された `UserIdToken` が以降の全リクエストで使用される。 ### 4.1 CBOR 構造 (key 18) ```json { "15": "", "16": "" } ``` ### 4.2 tokendata の CBOR 構造 | CBOR キー | フィールド名 | 値 (キャプチャ例) | |---|---|---| | `29` | `userdata` | `` (暗号化されたユーザーデータ) | | `25` | `serialnumber` | `5970945423176003` | | `11` | `issuedate` | `1773420422` (2026-03-13 16:47:02 UTC) | | `12` | `mtserialnum` | `8079134327147185` (紐づく MasterToken のシリアル) | | `profileid` | `profileid` | `ZEULH5S2GNGCRAABCSG6J2EGGA` | | `13` | `expiration` | `1774601222` (2026-03-27 08:47:02 UTC, **14日後**) | ### 4.3 トークンライフサイクル ``` 発行: 2026-03-13 16:47:02 UTC 有効期限: 2026-03-27 08:47:02 UTC 有効期間: 14日間 ``` 同一セッション内で全 348 リクエストが同じ UserIdToken (serialnumber: `5970945423176003`) を使用。 --- ## 5. 認証スキーム一覧 Netflix MSL は 5 つのユーザー認証スキームをサポート (iOS ドキュメントから): | スキーム | クラス (Android) | 送信データ | 用途 | |---|---|---|---| | **`NETFLIXID`** | `com.netflix.msl.userauth.NetflixIdAuthenticationData` (ProGuard 難読化) | netflixId, secureNetflixId | **本キャプチャで観測** — Cookie ベース認証 | | `EMAIL_PASSWORD` | ProGuard 難読化 (ClassNotFound) | email, password | ログインフォーム認証 | | `USER_ID_TOKEN` | `com.netflix.msl.userauth.UserIdTokenAuthenticationData` | 既存 UserIdToken | トークン再認証 | | `SSO_TOKEN` | ProGuard 難読化 (ClassNotFound) | ssoToken | SSO 認証 | | `SWITCH_PROFILE` | ProGuard 難読化 (ClassNotFound) | switchGUID, originalGUID | プロファイル切替 | ### 5.1 ProGuard 難読化の影響 | クラス | 状態 | |---|---| | `UserAuthenticationData` (基底クラス) | **存在確認済み** — メソッド `a(2)`, `b(2)`, `c(2)` に難読化 | | `UserIdTokenAuthenticationData` | **存在確認済み** — コンストラクタフック成功 (ただし呼び出しなし) | | `EmailPasswordAuthenticationData` | ClassNotFoundException | | `NetflixIdAuthenticationData` | ClassNotFoundException | | `SsoTokenAuthenticationData` | ClassNotFoundException | | `SwitchProfileAuthenticationData` | ClassNotFoundException | → 4 つのサブクラスが ProGuard で難読化/インライン化されている。`UserIdTokenAuthenticationData` のみ元のクラス名が残存。 --- ## 6. MSL CBOR 整数キーマッピング (固定値) MSL プロトコルは JSON と CBOR の 2 つのエンコーディングをサポートする。CBOR エンコード時は、JSON の文字列キーが**プロトコル仕様で定義された固定の整数キー**に置換される。これはメッセージサイズ削減のための最適化であり、Netflix の MSL ライブラリ (`MslEncoderFactory`) にハードコードされている。 以下のマッピングは、353 件の暗号化前 MSL MessageHeader と 403 件の PayloadChunk の CBOR 解析、および iOS 版 MSL ドキュメント (`msl_ios.md` §3) の JSON フィールド名との照合により確定したものである。 ### 6.1 MSL メッセージ外殻 (Header / MasterToken / EntityAuthData) JSON 構造: ```json { "headerdata": "", "signature": "", "mastertoken": { "tokendata": "...", "signature": "..." } } ``` | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値例 | |---|---|---|---|---| | `15` | `tokendata` | bytes | トークンデータ (CBOR エンコード、暗号化済み) | `` | | `16` | `signature` | bytes | HMAC 署名 | `` (HMAC-SHA256) | > **注**: key 15/16 は MasterToken, UserIdToken, keyrequestdata 等の**トークン系構造体で共通使用**される汎用キー。 ### 6.2 MessageHeader (復号後の headerdata) JSON 構造 (iOS 版 `msl_ios.md` §3.2): ```json { "messageid": 12345, "sender": "NFANDROID1-PRV-P-...", "renewable": false, "handshake": false, "timestamp": 1773392401, "capabilities": { ... }, "useridtoken": { "tokendata": "...", "signature": "..." }, "userauthdata": { "scheme": "NETFLIXID", "authdata": { ... } }, "keyrequestdata": [ ... ], "nonreplayableid": 10 } ``` | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測数 | |---|---|---|---|---| | `17` | `mastertoken` | list[dict{15,16}] | MasterToken (tokendata + signature) | 18 件 | | `18` | `useridtoken` | dict{15,16} | UserIdToken (tokendata + signature) | 348 件 | | `19` | `renewable` | bool | トークン更新可能フラグ | 353 件 (全ヘッダー) | | `20` | `sender` | string | 送信者 ESN (空文字列 = デフォルト) | 353 件 | | `21` | `handshake` | bool | ハンドシェイクメッセージフラグ | 353 件 | | `22` | `messageid` | int | メッセージ一意 ID | 353 件 | | `24` | `timestamp` | int | Unix タイムスタンプ (秒) | 353 件 | | `36` | `capabilities` | dict | クライアント能力情報 | 353 件 | | `40` | `peer` | bool | ピアツーピアフラグ | 353 件 | | `41` | `nonreplayableid` | int | リプレイ防止 ID | 2 件 | | `42` | `keyrequestdata` | list[dict{30,31}] | 鍵交換リクエストデータ | 3 件 | | `47` | `userauthdata` | dict{30,35} | **ユーザー認証データ** | 1 件 | ### 6.3 capabilities (key 36 の内部) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値 | |---|---|---|---|---| | `37` | `compressionalgos` | list[string] | サポート圧縮アルゴリズム | `[]` (空) | | `38` | `languages` | list[string] | サポート言語 | `["GZIP", "LZW"]` | | `39` | `encoders` | list[string] | サポートエンコーダー | `["CBOR"]` | | `maxpayloadchunksize` | `maxpayloadchunksize` | int | 最大ペイロードチャンクサイズ | `-1` (無制限) | > **注**: `maxpayloadchunksize` は文字列キーのまま。Netflix 独自拡張のため整数キーが未割当と推定。 ### 6.4 keyrequestdata / keyresponsedata (key 42 の内部要素) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値 | |---|---|---|---|---| | `30` | `scheme` | string | 鍵交換スキーム | `"WIDEVINE"` | | `31` | `keydata` | dict | 鍵データ | Widevine デバイス証明書 | | `31.duid` | `keydata.duid` | bytes | デバイス一意 ID (32B) | Widevine deviceUniqueId | | `31.50` | `keydata.cdmsg` | bytes | CDM メッセージ (protobuf) | Widevine ライセンスチャレンジ (~2577B) | ### 6.5 userauthdata (key 47 の内部) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値 | |---|---|---|---|---| | `30` | `scheme` | string | 認証スキーム | `"NETFLIXID"` | | `35` | `authdata` | dict | スキーム固有の認証データ | dict{56, 60} | #### NETFLIXID スキームの authdata (key 35 の内部) | CBOR キー | JSON フィールド名 | 型 | 説明 | |---|---|---|---| | `56` | `netflixid` | string | NetflixId Cookie (URL エンコード) | | `60` | `securenetflixid` | string | SecureNetflixId Cookie (URL エンコード) | ### 6.6 MasterToken tokendata (key 17[].15 を CBOR デコード) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値例 | |---|---|---|---|---| | `25` | `serialnumber` | int | MasterToken シリアル番号 | `5970945423176003` | | `26` | `renewable` | bool | 更新可能フラグ | `true` | | `27` | `issuer` | string | 発行者 | `"sf"`, `"cad"` | | `28` | `identity` | bytes | エンティティ ID (暗号化済み) | `` | | `43` | `sequencenumber` | int | シーケンス番号 | `8079134327147185` | ### 6.7 UserIdToken tokendata (key 18.15 を CBOR デコード) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値例 | |---|---|---|---|---| | `11` | `issuedate` | int | 発行日 (Unix タイムスタンプ秒) | `1773420422` | | `12` | `mtserialnum` | int | 紐づく MasterToken のシリアル番号 | `8079134327147185` | | `13` | `expiration` | int | 有効期限 (Unix タイムスタンプ秒) | `1774601222` | | `25` | `serialnumber` | int | UserIdToken シリアル番号 | `5970945423176003` | | `29` | `userdata` | bytes | 暗号化されたユーザーデータ | `` | | `profileid` | `profileid` | string | プロファイル GUID | `"ZEULH5S2GNGCRAABCSG6J2EGGA"` | > **注**: `profileid` は文字列キーのまま。Netflix 独自拡張フィールド。 ### 6.8 PayloadChunk (暗号化前) | CBOR キー | JSON フィールド名 | 型 | 説明 | 観測値例 | |---|---|---|---|---| | `14` | `sequencenumber` | int | チャンクシーケンス番号 | `1`, `2`, ... `8` | | `22` | `messageid` | int | メッセージ ID (MessageHeader と一致) | `1170337128274032` | | `44` | `compressionalgo` | string | 圧縮アルゴリズム | `"GZIP"` | | `62` | `data` | bytes | ペイロードデータ (GZIP 圧縮) | `` | | `63` | `endofmsg` | bool | 最終チャンクフラグ | `true` / `false` | ### 6.9 全整数キー一覧 (ソート順) キャプチャデータから観測された全 28 整数キーの完全一覧: | CBOR キー | JSON フィールド名 | 使用コンテキスト | |---|---|---| | `11` | `issuedate` | UserIdToken.tokendata | | `12` | `mtserialnum` | UserIdToken.tokendata | | `13` | `expiration` | UserIdToken.tokendata | | `14` | `sequencenumber` | PayloadChunk | | `15` | `tokendata` | MasterToken, UserIdToken, keyrequestdata (共通) | | `16` | `signature` | MasterToken, UserIdToken, keyrequestdata (共通) | | `17` | `mastertoken` | MessageHeader | | `18` | `useridtoken` | MessageHeader | | `19` | `renewable` | MessageHeader | | `20` | `sender` | MessageHeader | | `21` | `handshake` | MessageHeader | | `22` | `messageid` | MessageHeader, PayloadChunk | | `24` | `timestamp` | MessageHeader | | `25` | `serialnumber` | MasterToken.tokendata, UserIdToken.tokendata | | `26` | `renewable` | MasterToken.tokendata | | `27` | `issuer` | MasterToken.tokendata | | `28` | `identity` | MasterToken.tokendata | | `29` | `userdata` | UserIdToken.tokendata | | `30` | `scheme` | keyrequestdata, userauthdata | | `31` | `keydata` | keyrequestdata | | `35` | `authdata` | userauthdata | | `36` | `capabilities` | MessageHeader | | `37` | `compressionalgos` | capabilities | | `38` | `languages` | capabilities | | `39` | `encoders` | capabilities | | `40` | `peer` | MessageHeader | | `41` | `nonreplayableid` | MessageHeader | | `42` | `keyrequestdata` | MessageHeader | | `43` | `sequencenumber` | MasterToken.tokendata | | `44` | `compressionalgo` | PayloadChunk | | `47` | `userauthdata` | MessageHeader | | `50` | `cdmsg` | keyrequestdata.keydata (Widevine) | | `56` | `netflixid` | userauthdata.authdata (NETFLIXID) | | `60` | `securenetflixid` | userauthdata.authdata (NETFLIXID) | | `62` | `data` | PayloadChunk | | `63` | `endofmsg` | PayloadChunk | ### 6.10 文字列キー (整数マッピングなし) | キー | 使用コンテキスト | 説明 | |---|---|---| | `maxpayloadchunksize` | capabilities | 最大ペイロードチャンクサイズ | | `profileid` | UserIdToken.tokendata | プロファイル GUID | | `duid` | keyrequestdata.keydata | デバイス一意 ID | > これらは Netflix 独自の拡張フィールドであり、MSL 標準の整数キーが割り当てられていないと推定される。 --- ## 7. userauthdata の生成メカニズム ### 7.1 生成レイヤー `userauthdata` は `apiRequest` のパラメータとしてではなく、**MslControl 層で MSL MessageHeader に直接埋め込まれる**: ``` Netflix App ↓ apiRequest(url, body, headers, userId=GUID, auth=null) ApiHandlerImpl ↓ (auth は null のまま渡される) MslControl ↓ MessageHeader を構築 ↓ ← ここで userauthdata を設定 ↓ (初回: NETFLIXID スキーム) ↓ (以降: UserIdToken を使用) WidevineCryptoContext.encrypt(headerdata) ↓ 暗号化された MSL メッセージ → サーバー送信 ``` ### 7.2 netflixId / secureNetflixId の生成元 これらの値は **Android の Cookie ストレージ** から取得される: | Cookie 名 | 生成元 | 保存場所 | |---|---|---| | `netflixId` (=`NetflixId`) | サーバーが Set-Cookie で発行 | Android CookieManager / SharedPreferences | | `secureNetflixId` (=`SecureNetflixId`) | サーバーが Set-Cookie で発行 (Secure 属性) | Android CookieManager / SharedPreferences | ログイン成功時にサーバーからこれらの Cookie が発行され、MSL ユーザー認証に使用される。 ### 7.3 RenewSSOToken との関係 キャプチャで `RenewSSOToken` GraphQL オペレーションが観測されている: ```json { "operationName": "RenewSSOToken", "variables": { "ssoToken": "BgiHtuvcAxL4AWLUqSE7W14gsbHZBDGMle4StHvRnr70..." } } ``` これは MSL レベルの `userauthdata` とは別の、**GraphQL レベルの SSO トークン更新**。MSL 層では既に UserIdToken で認証されており、SSO トークン更新は MSL ペイロード内で行われる。 --- ## 8. 認証フロー全体図 ```mermaid %%{init:{'theme':'dark'}}%% sequenceDiagram participant User as ユーザー participant App as Netflix App participant MSL as MslControl participant Server as Netflix Server rect rgba(255,200,200,0.15) Note over User,Server: Phase 1: ログイン (本キャプチャ前) User->>App: メールアドレス + パスワード入力 App->>Server: HTTPS POST /login Server->>App: Set-Cookie: NetflixId, SecureNetflixId Note over App: Cookie をローカルに保存 end rect rgba(200,200,255,0.15) Note over User,Server: Phase 2: MSL 初回認証 (キャプチャ済み, 1回) App->>MSL: apiRequest(..., userId=GUID, auth=null) MSL->>MSL: MessageHeader 構築 Note over MSL: key 42: keyrequestdata (WIDEVINE)
key 47: userauthdata (NETFLIXID)
→ netflixId + secureNetflixId MSL->>Server: 暗号化 MSL メッセージ Server->>MSL: keyresponsedata + MasterToken
+ UserIdToken (有効期間 14日) Note over MSL: UserIdToken をメモリに保持 end rect rgba(200,255,200,0.15) Note over User,Server: Phase 3: 通常リクエスト (348回観測) App->>MSL: apiRequest(..., userId=GUID, auth=null) MSL->>MSL: MessageHeader 構築 Note over MSL: key 18: useridtoken
→ serialnumber + profileid + expiration MSL->>Server: 暗号化 MSL メッセージ Server->>MSL: MSL レスポンス end rect rgba(255,255,200,0.15) Note over User,Server: Phase 4: トークン更新 (15回観測) Note over MSL: MasterToken + keyrequestdata で
トークンを更新 (key 17 + key 18) MSL->>Server: 更新リクエスト Server->>MSL: 新しい MasterToken end ``` --- ## 9. iOS 版との比較 | 観点 | Android | iOS | |---|---|---| | **userauthdata スキーム** | `NETFLIXID` (netflixId + secureNetflixId) | `NETFLIXID` (同一) | | **CBOR キー** | 整数キー (47, 18, etc.) | 整数キー (同一仕様) | | **apiRequest パラメータ** | 第5引数 `UserAuthenticationData auth` = null | `makeUserAuthData()` メソッドで生成 | | **userauthdata 設定レイヤー** | MslControl (Java) | IosMslClient (C++) | | **暗号化** | WidevineCryptoContext | AES-CBC (C++) | | **UserIdToken 有効期間** | 14 日 | 不明 | | **ProGuard 影響** | サブクラス 4/5 が難読化で ClassNotFound | N/A (C++ バイナリ) | --- ## 10. 解析に使用したコード ### 10.1 CBOR デコードスクリプト ```python import base64, json, cbor2 from urllib.parse import unquote # Widevine encrypt 平文から MSL MessageHeader を解析 with open("logs/android_20260313/android14.prod.ftl.netflix.com/0052_msl.widevine.encrypt.json") as f: data = json.load(f) raw = base64.b64decode(data["plaintext_b64"]) header = cbor2.loads(raw) # userauthdata (key 47) を取得 if 47 in header: uad = header[47] scheme = uad[30] # "NETFLIXID" authdata = uad[35] netflix_id = unquote(authdata[56]) secure_netflix_id = unquote(authdata[60]) print(f"scheme: {scheme}") print(f"netflixId: {netflix_id}") print(f"secureNetflixId: {secure_netflix_id}") # UserIdToken (key 18) を取得 if 18 in header: uit = header[18] tokendata = cbor2.loads(uit[15]) print(f"profileid: {tokendata.get('profileid')}") print(f"serialnumber: {tokendata.get(25)}") print(f"expiration: {tokendata.get(13)}") ``` ### 10.2 Frida フックによるキャプチャ `hook_netflix_android.js` に以下のフックを実装: | フック対象 | 目的 | 結果 | |---|---|---| | `ApiHandlerImpl.apiRequest` 第5引数 | UserAuthenticationData オブジェクト取得 | **常に null** (MslControl 層で設定されるため) | | `UserIdTokenAuthenticationData.$init` | コンストラクタ監視 | **フック成功、呼び出しなし** (トークンはサーバーから受信) | | `EmailPasswordAuthenticationData.$init` | コンストラクタ監視 | ClassNotFoundException (ProGuard) | | `NetflixIdAuthenticationData.$init` | コンストラクタ監視 | ClassNotFoundException (ProGuard) | | `SsoTokenAuthenticationData.$init` | コンストラクタ監視 | ClassNotFoundException (ProGuard) | | `SwitchProfileAuthenticationData.$init` | コンストラクタ監視 | ClassNotFoundException (ProGuard) | | `UserAuthenticationData.getScheme` | スキーム名取得 | メソッド名が難読化 (ProGuard) | | `WidevineCryptoContext.encrypt` | 暗号化前平文取得 | **成功** — CBOR 解析で userauthdata 発見 | --- ## 11. まとめ 1. **`userauthdata` は MSL MessageHeader の CBOR key 47 として暗号化前に埋め込まれる**。`apiRequest` のパラメータ(第5引数)としては渡されず、`MslControl` 層が Cookie ストレージから `netflixId` / `secureNetflixId` を読み取って設定する。 2. **NETFLIXID スキーム**が使用され、`netflixId` (HMAC 付きデバイストークン) と `secureNetflixId` (暗号化認証トークン + プロファイル GUID) の 2 つの値で構成される。 3. **初回の MSL ハンドシェイク時に 1 回だけ送信**される。サーバーは応答として `UserIdToken` (有効期間 14 日) を発行し、以降の全リクエストはこの `UserIdToken` (key 18) で認証される。 4. **353 件の MSL ヘッダーのうち userauthdata を含むのは 1 件のみ** (0.3%)。残りの 98.6% は `UserIdToken` による認証。 5. ProGuard 難読化により `NetflixIdAuthenticationData` 等のサブクラスは直接フックできないが、**Widevine encrypt の平文データを CBOR 解析することで userauthdata の完全な内容を取得できた**。