mirror of
https://github.com/qtmleap/revkit.git
synced 2026-09-28 14:01:50 +02:00
chore: prepare for commit message generation without code changes
This commit is contained in:
@@ -0,0 +1,734 @@
|
||||
# MSL CBOR 整数キーマッピング — リファレンス
|
||||
|
||||
MSL プロトコルの CBOR エンコード時に使用される固定整数キー。
|
||||
JSON の文字列キーをサイズ削減のため整数に置換する仕組みで、`MslEncoderFactory` にハードコードされている。
|
||||
|
||||
353 件の暗号化前 MessageHeader + 403 件の PayloadChunk の CBOR 解析により確定。
|
||||
|
||||
---
|
||||
|
||||
## 全整数キー一覧
|
||||
|
||||
| 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 |
|
||||
|
||||
## 文字列キー (整数マッピングなし)
|
||||
|
||||
| キー | 使用コンテキスト |
|
||||
|---|---|
|
||||
| `maxpayloadchunksize` | capabilities |
|
||||
| `profileid` | UserIdToken.tokendata |
|
||||
| `duid` | keyrequestdata.keydata |
|
||||
|
||||
---
|
||||
|
||||
## MessageHeader 構造
|
||||
|
||||
```
|
||||
MessageHeader (暗号化前 headerdata):
|
||||
├─ 17: mastertoken → list[ {15: tokendata, 16: signature} ]
|
||||
├─ 18: useridtoken → {15: tokendata, 16: signature}
|
||||
├─ 19: renewable → bool
|
||||
├─ 20: sender → string (ESN)
|
||||
├─ 21: handshake → bool
|
||||
├─ 22: messageid → int
|
||||
├─ 24: timestamp → int (Unix秒)
|
||||
├─ 36: capabilities
|
||||
│ ├─ 37: compressionalgos → list
|
||||
│ ├─ 38: languages → list
|
||||
│ ├─ 39: encoders → list
|
||||
│ └─ maxpayloadchunksize → int
|
||||
├─ 40: peer → bool
|
||||
├─ 41: nonreplayableid → int
|
||||
├─ 42: keyrequestdata → list[ {30: scheme, 31: keydata} ]
|
||||
└─ 47: userauthdata → {30: scheme, 35: authdata}
|
||||
```
|
||||
|
||||
## PayloadChunk 構造
|
||||
|
||||
```
|
||||
PayloadChunk (暗号化前):
|
||||
├─ 14: sequencenumber → int
|
||||
├─ 22: messageid → int
|
||||
├─ 44: compressionalgo → string ("GZIP")
|
||||
├─ 62: data → bytes (GZIP圧縮ペイロード)
|
||||
└─ 63: endofmsg → bool
|
||||
```
|
||||
|
||||
## MasterToken tokendata 構造
|
||||
|
||||
```
|
||||
MasterToken.tokendata (key 17[].15 を CBORデコード):
|
||||
├─ 25: serialnumber → int
|
||||
├─ 26: renewable → bool
|
||||
├─ 27: issuer → string ("sf", "cad")
|
||||
├─ 28: identity → bytes (暗号化済み)
|
||||
└─ 43: sequencenumber → int
|
||||
```
|
||||
|
||||
## UserIdToken tokendata 構造
|
||||
|
||||
```
|
||||
UserIdToken.tokendata (key 18.15 を CBORデコード):
|
||||
├─ 11: issuedate → int (Unix秒)
|
||||
├─ 12: mtserialnum → int (紐づくMasterTokenシリアル)
|
||||
├─ 13: expiration → int (Unix秒, 発行から14日後)
|
||||
├─ 25: serialnumber → int
|
||||
├─ 29: userdata → bytes (暗号化済み)
|
||||
└─ profileid → string (プロファイルGUID)
|
||||
```
|
||||
|
||||
## userauthdata 構造 (NETFLIXID スキーム)
|
||||
|
||||
```
|
||||
userauthdata (key 47):
|
||||
├─ 30: scheme → "NETFLIXID"
|
||||
└─ 35: authdata
|
||||
├─ 56: netflixid → string (URLエンコード)
|
||||
│ 例: v=3&mac=AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.&dt=1773392547767
|
||||
└─ 60: securenetflixid → string (URLエンコード)
|
||||
例: v=3&ct=<暗号化トークン>&pg=<プロファイルGUID>&ch=<HMAC>
|
||||
```
|
||||
|
||||
### netflixid のパラメータ
|
||||
|
||||
| パラメータ | 説明 |
|
||||
|---|---|
|
||||
| `v` | バージョン (`3`) |
|
||||
| `mac` | HMAC 署名 |
|
||||
| `dt` | デバイスタイムスタンプ (Unix ms) |
|
||||
|
||||
### securenetflixid のパラメータ
|
||||
|
||||
| パラメータ | 説明 |
|
||||
|---|---|
|
||||
| `v` | バージョン (`3`) |
|
||||
| `ct` | 暗号化認証トークン (protobuf base64url) |
|
||||
| `pg` | プロファイル GUID |
|
||||
| `ch` | チャネル HMAC |
|
||||
|
||||
---
|
||||
|
||||
## ユーザー認証フロー
|
||||
|
||||
1. **初回認証 (1回)**: key 47 (`userauthdata`) に NETFLIXID スキームで netflixId + secureNetflixId を送信
|
||||
2. **サーバー応答**: UserIdToken を発行 (有効期間 14日)
|
||||
3. **以降のリクエスト (348回観測)**: key 18 (`useridtoken`) で認証。userauthdata は送信しない
|
||||
|
||||
353件の MSL ヘッダーのうち userauthdata を含むのは **1件のみ** (0.3%)。
|
||||
apiRequest の第5引数 (`UserAuthenticationData`) は常に null — MslControl 層が内部で設定する。
|
||||
|
||||
---
|
||||
|
||||
## キャプチャ実データ
|
||||
|
||||
### Phase 1: 初回ハンドシェイク (Widevine 鍵交換)
|
||||
|
||||
ソース: `crypto/0002_msl.widevine.encrypt.json` (2026-03-13T09:00:02.108Z)
|
||||
|
||||
userauthdata なし、useridtoken なし。Widevine 鍵交換のみ。
|
||||
|
||||
```
|
||||
MessageHeader:
|
||||
key 17 (mastertoken): list (len=1)
|
||||
[0].15 (tokendata): <bytes 153>
|
||||
[0].16 (signature): <bytes 44>
|
||||
key 19 (renewable): false
|
||||
key 20 (sender): ""
|
||||
key 21 (handshake): true
|
||||
key 22 (messageid): 5608206180487888
|
||||
key 24 (timestamp): 1773392401
|
||||
key 36 (capabilities):
|
||||
37 (compressionalgos): []
|
||||
38 (languages): ["GZIP", "LZW"]
|
||||
39 (encoders): ["CBOR"]
|
||||
maxpayloadchunksize: -1
|
||||
key 40 (peer): false
|
||||
key 42 (keyrequestdata): list (len=1)
|
||||
[0].30 (scheme): "WIDEVINE"
|
||||
[0].31 (keydata): {duid: <bytes 32>, 50 (cdmsg): <bytes 2577>}
|
||||
```
|
||||
|
||||
### Phase 2: userauthdata 送信 (NETFLIXID 認証, 1回のみ)
|
||||
|
||||
ソース: `android14.prod.ftl.netflix.com/0052_msl.widevine.encrypt.json` (2026-03-13T09:02:28.168Z)
|
||||
|
||||
```
|
||||
MessageHeader:
|
||||
key 17 (mastertoken): list (len=1)
|
||||
key 19 (renewable): false
|
||||
key 20 (sender): ""
|
||||
key 21 (handshake): true
|
||||
key 22 (messageid): 2491352839960294
|
||||
key 24 (timestamp): 1773392548
|
||||
key 36 (capabilities): {37: [], 38: ["GZIP","LZW"], 39: ["CBOR"], maxpayloadchunksize: -1}
|
||||
key 40 (peer): true
|
||||
key 41 (nonreplayableid): 10
|
||||
key 42 (keyrequestdata): list (len=1)
|
||||
[0].30 (scheme): "WIDEVINE"
|
||||
key 47 (userauthdata):
|
||||
30 (scheme): "NETFLIXID"
|
||||
35 (authdata):
|
||||
56 (netflixid): "v=3&mac=AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.&dt=1773392547767"
|
||||
60 (securenetflixid): "v=3&ct=BgjHlOvcAxLcA5Nned7dv-Iq5m2f7a5MNrKy8VAkRdBzSXaHjTsiFmEI
|
||||
-etnBrrNnCGzam9tRfUqnfVpjLaK3eFk79_6Jcsspf05U_HMC9CnJ3vy0tUhQatCl9ma2mrGqIWMm6fBWX11
|
||||
CofiJwnM-OpAzHO3fO5AJwHFSMwa7pCucoXbyjee_5tLcNdykJrEGPRnjJRc6DPfaYAJN4sDgCJptsO1Ssfw
|
||||
A0CTujgVhkf4r7S-HloF7BOktKT_dwzw2ULtuZXg5uC0XySU6Phk9y-jRjqQ9LvpVD-E9mXrKMAH23yxAI5
|
||||
Ku-YRnmTOEMhk92gJBVTwnHJAe13NihHhF-aHxXDfBcMkWPHDT6-raPJ3HClitacWIoWQ2AH0q5WRrm1XBMg
|
||||
A4KuNIepWOfJV9AGRVAfGoKpxbLjU6ajSx1KiD9o5KkaZ1yvEvlxtAxbISivsKhVocRN7Lt12W_9jsuUZbL-f
|
||||
urw4jGezz5lHUNLSkh4suuiVYdAMMkhyPnbC1pTKE-fRxd2stJ0uuOAord3hrUbKyDK5g_nbvRSXZFW1sLb22
|
||||
691pj5FnCxhkJquZtoW67Iwa7hVo9Jg5cUVSNvbuVLC0hmRotilPJujiaLz-XQty0SUaWv-WArtj-aEXjYKa3
|
||||
DuGAYiDgoMGowgnSHIWWhHgSZz&pg=ZEULH5S2GNGCRAABCSG6J2EGGA&ch=AQEAEAABABTp79nN9l_2MuRh
|
||||
qTXl0-SjAqcm83QU8vw."
|
||||
```
|
||||
|
||||
#### netflixid デコード
|
||||
|
||||
```
|
||||
v=3
|
||||
mac=AQEAEQABABQxZyPp7r91RNj4pmPTIatYcL3jjIUVbvQ.
|
||||
dt=1773392547767
|
||||
```
|
||||
|
||||
#### securenetflixid デコード
|
||||
|
||||
```
|
||||
v=3
|
||||
ct=BgjHlOvcAxLcA5Nned7dv-Iq5m2f7a5MNrKy8VAk... (672文字, 暗号化認証トークン)
|
||||
pg=ZEULH5S2GNGCRAABCSG6J2EGGA (プロファイル GUID)
|
||||
ch=AQEAEAABABTp79nN9l_2MuRhqTXl0-SjAqcm83QU8vw. (チャネル HMAC)
|
||||
```
|
||||
|
||||
### Phase 3: 通常リクエスト (UserIdToken 認証, 348回)
|
||||
|
||||
ソース: `android14.prod.cloud.netflix.com/0002_msl.widevine.encrypt.json` (2026-03-13T09:00:02.415Z)
|
||||
|
||||
```
|
||||
MessageHeader:
|
||||
key 17 (mastertoken): list (len=2)
|
||||
key 18 (useridtoken):
|
||||
15 (tokendata): <bytes 296>
|
||||
16 (signature): 01010081000101204005d2ae93a88d111b34aecb30b911f634e542d41c97f8f8a305af3850d3e9a22de3595d
|
||||
key 19 (renewable): false
|
||||
key 20 (sender): ""
|
||||
key 21 (handshake): false
|
||||
key 22 (messageid): 491822966207012
|
||||
key 24 (timestamp): 1773392402
|
||||
key 36 (capabilities): {37: [], 38: ["GZIP","LZW"], 39: ["CBOR"], maxpayloadchunksize: -1}
|
||||
key 40 (peer): false
|
||||
```
|
||||
|
||||
#### UserIdToken tokendata (CBOR decoded)
|
||||
|
||||
```
|
||||
11 (issuedate): 1773420422 (2026-03-13T16:47:02+00:00)
|
||||
12 (mtserialnum): 8079134327147185
|
||||
13 (expiration): 1774601222 (2026-03-27T08:47:02+00:00) ← 発行から14日後
|
||||
25 (serialnumber): 5970945423176003
|
||||
29 (userdata): <bytes 217> (暗号化済み)
|
||||
profileid: "ZEULH5S2GNGCRAABCSG6J2EGGA"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ログインフロー キャプチャ実データ
|
||||
|
||||
セッション `android_20260313` で実際に観測されたログイン〜認証完了までの全リクエスト。
|
||||
ドメインが `android.prod.cloud.netflix.com` (MSL v14 ではない旧ドメイン) の場合は `userId: null` で未認証状態。
|
||||
|
||||
### Step 1: RenewSSOToken (既存セッション更新の試行)
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:00:02.245Z
|
||||
domain: android14.prod.cloud.netflix.com
|
||||
userId: ZEULH5S2GNGCRAABCSG6J2EGGA
|
||||
userauthdata: null
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"operationName": "RenewSSOToken",
|
||||
"variables": {
|
||||
"ssoToken": "BgiHtuvcAxL4AWLUqSE7W14gsbHZBDGMle4StHvRnr70_-VHofvb_wqAIEYDS3SmOscABWcD-0O-ibsEZPZYT3R7kUeCsJ14vV6crTlZY5DozcjvtZbtjykI8sqaQzMNtrOq1Af9lZemVLhi5FqnmNp7uy2RpwwiCqojwvVWzmKRzeOKStqUHDSIdzAofrErd6kAsfoJZleWTV87eSx2gyAa9mIauY_A51-rE2lOaTj19CeVaTeqSrWZMqWzuZ0OaYfqVZkuUf7CZ1bW1NTtlxS3mo17dmxIhpIfX8CFf26dp1Ggui93k_2dNbOF4_8e_whkxNBpTNh1RMmalk11gJocGAYiDgoMYoDsMZdYw3vL-Wdq"
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 102,
|
||||
"id": "a4d00303-b02d-47c9-a53f-776b6a63b001"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: InterstitialHook (ログイン画面初期化)
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:01:29.542Z
|
||||
domain: android.prod.cloud.netflix.com
|
||||
userId: null ← 未認証
|
||||
userauthdata: null
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"operationName": "InterstitialHook",
|
||||
"variables": {
|
||||
"flowName": "loginMobile",
|
||||
"format": "HTML",
|
||||
"resolutionMode": "ANDROID_XHDPI",
|
||||
"imageFormat": "PNG",
|
||||
"parameters": null,
|
||||
"commonParameters": {
|
||||
"isConsumptionOnly": true,
|
||||
"isNetflixPreloaded": false,
|
||||
"channelId": "",
|
||||
"androidInstallType": "regular"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 102,
|
||||
"id": "bb592c79-c026-44e3-a989-ddba298d5eaf"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: InterstitialScreenUpdate — メールアドレス送信 + reCAPTCHA
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:02:12.214Z
|
||||
domain: android.prod.cloud.netflix.com
|
||||
userId: null ← 未認証
|
||||
userauthdata: null
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"operationName": "InterstitialScreenUpdate",
|
||||
"variables": {
|
||||
"serverState": {
|
||||
"realm": "growth",
|
||||
"name": "IDENTIFICATION",
|
||||
"clcsSessionId": "74a682e9-d991-4d03-a2e4-aa130cdd1623",
|
||||
"sessionContext": {
|
||||
"session-breadcrumbs": { "funnel_name": "loginMobile" },
|
||||
"MobileLoginSessionContext": {}
|
||||
},
|
||||
"hellfireSessionId": "d1141445-134a-37ae-b91f-efe6902155e1"
|
||||
},
|
||||
"serverScreenUpdate": {
|
||||
"realm": "custom",
|
||||
"name": "growthProcessLogin",
|
||||
"metadata": {
|
||||
"recaptchaSiteKey": "6LeWeOoUAAAAAJB9vW-OBEYmBwbF9R7PILe6U_ML"
|
||||
},
|
||||
"loggingAction": "Submitted",
|
||||
"loggingCommand": "SubmitCommand"
|
||||
},
|
||||
"inputFields": [
|
||||
{ "name": "userLoginId", "value": { "stringValue": "lemonandchan+5@gmail.com" } },
|
||||
{ "name": "countryCode", "value": { "stringValue": "81" } },
|
||||
{ "name": "countryIsoCode", "value": { "stringValue": "JP" } },
|
||||
{ "name": "password", "value": { "stringValue": "" } },
|
||||
{ "name": "recaptchaResponseTime", "value": { "intValue": 1231 } },
|
||||
{ "name": "recaptchaResponseToken","value": { "stringValue": "<reCAPTCHA token ~4KB>" } }
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 102,
|
||||
"id": "d1a0c5d5-2c35-4b98-8b9c-11d8e7706148"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注**: `password` は空文字列。パスワードレスログイン (MFA OTP) が使用されている。
|
||||
> `recaptchaSiteKey`: `6LeWeOoUAAAAAJB9vW-OBEYmBwbF9R7PILe6U_ML` (Google reCAPTCHA v3)
|
||||
|
||||
### Step 4: InterstitialScreenUpdate — MFA OTP 送信
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:02:27.251Z
|
||||
domain: android.prod.cloud.netflix.com
|
||||
userId: null ← まだ未認証
|
||||
userauthdata: null
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"operationName": "InterstitialScreenUpdate",
|
||||
"variables": {
|
||||
"serverState": {
|
||||
"realm": "growth",
|
||||
"name": "MFA_COLLECT_OTP_EMAIL_INPUT",
|
||||
"clcsSessionId": "74a682e9-d991-4d03-a2e4-aa130cdd1623",
|
||||
"sessionContext": {
|
||||
"session-breadcrumbs": { "funnel_name": "loginMobile" },
|
||||
"MobileLoginSessionContext": {}
|
||||
},
|
||||
"hellfireSessionId": "d1141445-134a-37ae-b91f-efe6902155e1"
|
||||
},
|
||||
"serverScreenUpdate": {
|
||||
"realm": "custom",
|
||||
"name": "growthVerifyMfaChallenge",
|
||||
"metadata": { "validateLength": 4 },
|
||||
"loggingAction": "Submitted",
|
||||
"loggingCommand": "SubmitCommand"
|
||||
},
|
||||
"inputFields": [
|
||||
{ "name": "challengeOtp", "value": { "stringValue": "8104" } }
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 102,
|
||||
"id": "d1a0c5d5-2c35-4b98-8b9c-11d8e7706148"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注**: MFA OTP は 4 桁 (`validateLength: 4`)。メールに送信された OTP `8104` を入力。
|
||||
|
||||
### Step 5: InterstitialSendFeedback — ログイン完了、プロフィール画面へ遷移
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:02:30.453Z
|
||||
domain: android.prod.cloud.netflix.com
|
||||
userId: ZEULH5S2GNGCRAABCSG6J2EGGA ← 認証完了
|
||||
userauthdata: null
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"operationName": "InterstitialSendFeedback",
|
||||
"variables": {
|
||||
"serverState": {
|
||||
"realm": "growth",
|
||||
"name": "MFA_COLLECT_OTP_EMAIL_INPUT",
|
||||
"clcsSessionId": "74a682e9-d991-4d03-a2e4-aa130cdd1623"
|
||||
},
|
||||
"serverFeedback": {
|
||||
"name": "system.inAppNavigation",
|
||||
"metadata": { "loggingCommand": "Navigating to /profiles" }
|
||||
},
|
||||
"inputFields": []
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 102,
|
||||
"id": "4718d209-37d8-4b43-ae43-858cd07c6c0b"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: AccountQuery (認証後, userId 確定)
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:02:29.644Z
|
||||
domain: android14.prod.cloud.netflix.com
|
||||
userId: TEMP_PROFILE_ID ← 一時プロファイル
|
||||
|
||||
ts: 2026-03-13T09:02:31.123Z
|
||||
domain: android14.prod.cloud.netflix.com
|
||||
userId: ZEULH5S2GNGCRAABCSG6J2EGGA ← 確定プロファイル
|
||||
```
|
||||
|
||||
### Step 7: userauthdata (NETFLIXID) 送信 — MSL 層で初回ユーザー認証
|
||||
|
||||
```
|
||||
ts: 2026-03-13T09:02:28.168Z ← Step 4〜5 の間
|
||||
domain: android14.prod.ftl.netflix.com
|
||||
```
|
||||
|
||||
→ Phase 2 のデータ参照 (前述)。ログイン完了直後に MslControl が netflixId + secureNetflixId を MSL MessageHeader に埋め込み。
|
||||
|
||||
### ログインフロー時系列まとめ
|
||||
|
||||
| 時刻 | ステップ | userId | ドメイン | 内容 |
|
||||
|---|---|---|---|---|
|
||||
| 09:00:02 | RenewSSOToken | `ZEULH5S..` | android14.prod.cloud | 既存セッションの SSO トークン更新 |
|
||||
| 09:01:29 | InterstitialHook | null | android.prod.cloud | ログイン画面初期化 (`loginMobile`) |
|
||||
| 09:02:12 | InterstitialScreenUpdate | null | android.prod.cloud | メールアドレス送信 + reCAPTCHA (`IDENTIFICATION`) |
|
||||
| 09:02:27 | InterstitialScreenUpdate | null | android.prod.cloud | MFA OTP `8104` 送信 (`MFA_COLLECT_OTP_EMAIL_INPUT`) |
|
||||
| 09:02:28 | **MSL userauthdata** | — | android14.prod.ftl | **NETFLIXID 認証** (netflixId + secureNetflixId) |
|
||||
| 09:02:29 | AccountQuery | `TEMP_PROFILE_ID` | android14.prod.cloud | 一時プロファイルでアカウント照会 |
|
||||
| 09:02:30 | InterstitialSendFeedback | `ZEULH5S..` | android.prod.cloud | ログイン完了 → `/profiles` 遷移 |
|
||||
| 09:02:31 | AccountQuery | `ZEULH5S..` | android14.prod.cloud | 確定プロファイルでアカウント照会 |
|
||||
|
||||
---
|
||||
|
||||
## 実際の Cookie とリクエストヘッダー
|
||||
|
||||
2026-03-13 キャプチャから取得した実データ。
|
||||
|
||||
### Cookie 一覧
|
||||
|
||||
3 つの Cookie が送信される:
|
||||
|
||||
| Cookie 名 | 説明 | サンプル値 |
|
||||
|---|---|---|
|
||||
| `nfvdid` | Netflix デバイス ID | `BQFmAAEBEEPj84LzHGpQ_ldxaVuQv8tg...` (Base64, 約130文字) |
|
||||
| `NetflixId` | メイン認証トークン | `v=3&ct=BgjHlOvc...&pg=ZEULH5S2GNG...&ch=AQEAEAABABTp79nN...` (約600文字) |
|
||||
| `SecureNetflixId` | セキュア認証トークン (HMAC) | `v=3&mac=AQEAEQABABSdkuDd...&dt=1773391620952` |
|
||||
|
||||
### NetflixId の構造
|
||||
|
||||
```
|
||||
v=3
|
||||
ct=BgjHlOvcAxLcA6sY6f_QqKsJWi9efmzU7gW0d6XdGtEbMELNpOs-Ws2jKWjrRgmY8LTraOV1L5gpMfa5YJ63VaUYZrzppkA0wm-r_7A_XkrygJ4mEYMVax3K5POwTnVKZK-k2h5ITFTGYp3sHXwBQy3BbuBTJ8rD4nQOXGgRbm3RilTMKcOCHYyahc4YSmE-E3CPdoASBsLzpHAszy7XN8Mwc4iGr198Hac49DdcXpLhUaP6LGm8j9_3nI0R2gg8B-ntAZIKumUrbQXSsb_zMv71GxZYcKnjNVfwFAKppf3gydKKhcvzuFKw7jZvebCSYp83l56MRB88yd_vUjmXsDUdZS1AXjYMk9zVfHoARiF-2-c0FSfS3zb2SQWQkUcgTR_SabzN5u6_w6zXgffoYlVJDPsmy8Z9pDGYFzOGL3Xqc_UNIz4KdEPCDkSGmQC9nhVOJXwXYb-LBhVC7qE1Cz1vpZ98mYEwE7LomPN3-34eGIWBeeMR8E4VXfQJnL4iBnO6hBfRmy8xNRgDA7VlAjJiYYolnK_x4_gTu2-x7A5AK1WYqobiCkcOClMQIioAC8ZlH17Wf_eN2lFfOEP6G50KJpYPhUezIpPzfV5osQT9Q0AsfUrzRF3MWXDM9ukNDmHiYfN0GAYiDgoMSvU3vVSCd907UQKa
|
||||
pg=ZEULH5S2GNGCRAABCSG6J2EGGA
|
||||
ch=AQEAEAABABTp79nN9l_2MuRhqTXl0-SjAqcm83QU8vw.
|
||||
```
|
||||
|
||||
- `v`: バージョン (3)
|
||||
- `ct`: 暗号化トークン本体 (Base64url)
|
||||
- `pg`: プロファイル GUID
|
||||
- `ch`: チャンネル/チェックサム
|
||||
|
||||
### SecureNetflixId の構造
|
||||
|
||||
```
|
||||
v=3
|
||||
mac=AQEAEQABABSdkuDdxX0gGJU2IJOTCYiVHmFkqRtHQj0.
|
||||
dt=1773391620952
|
||||
```
|
||||
|
||||
- `v`: バージョン (3)
|
||||
- `mac`: HMAC 署名 (Base64url)
|
||||
- `dt`: タイムスタンプ (epoch ms)
|
||||
|
||||
---
|
||||
|
||||
## cURL 形式リクエスト例
|
||||
|
||||
### 共通ヘッダー
|
||||
|
||||
```
|
||||
User-Agent: com.netflix.mediaclient/63928 (Linux; U; Android 14; en; Pixel 4a (5G); Build/UP1A.231005.007)
|
||||
X-Netflix-ProxyEsn: NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202Q7INAHS2TKI5GTQESPDEHTFK7MG1BKUU7QAPUQP2QMI641A8HN08CE40C5H2K4J15NCLBC5DGJI0M03TMV0VGS1ER8VACIG0257E
|
||||
```
|
||||
|
||||
### 1. Push 通知 WebSocket 接続
|
||||
|
||||
```bash
|
||||
curl -v \
|
||||
-H 'Origin: http://www.netflix.com' \
|
||||
-H 'User-Agent: com.netflix.mediaclient/63928 (Linux; U; Android 14; en; Pixel 4a (5G); Build/UP1A.231005.007)' \
|
||||
-H 'X-Netflix-ProxyEsn: NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202Q7INAHS2TKI5GTQESPDEHTFK7MG1BKUU7QAPUQP2QMI641A8HN08CE40C5H2K4J15NCLBC5DGJI0M03TMV0VGS1ER8VACIG0257E' \
|
||||
-H 'Upgrade: websocket' \
|
||||
-H 'Connection: Upgrade' \
|
||||
-H 'Sec-WebSocket-Version: 13' \
|
||||
-H 'Sec-WebSocket-Extensions: permessage-deflate' \
|
||||
-b 'nfvdid=BQFmAAEBEEPj84LzHGpQ_ldxaVuQv8tgIBE9VAe3w-WeF5En4w5goMB6eLYVXqxblfzh23QC62wkeecrOuKtsfIRNC5GkWZ80HdbCeAoFLJ8LL5stGc-h87ykqdvoTT1Vg5jhdcHity8mbE5rciYfRNlegiejB_j; NetflixId=v%3D3%26ct%3DBgjHlOvcAxLcA6sY6f_QqKsJWi9efmzU7gW0d6XdGtEbMELNpOs-Ws2jKWjrRgmY8LTraOV1L5gpMfa5YJ63VaUYZrzppkA0wm-r_7A_XkrygJ4mEYMVax3K5POwTnVKZK-k2h5ITFTGYp3sHXwBQy3BbuBTJ8rD4nQOXGgRbm3RilTMKcOCHYyahc4YSmE-E3CPdoASBsLzpHAszy7XN8Mwc4iGr198Hac49DdcXpLhUaP6LGm8j9_3nI0R2gg8B-ntAZIKumUrbQXSsb_zMv71GxZYcKnjNVfwFAKppf3gydKKhcvzuFKw7jZvebCSYp83l56MRB88yd_vUjmXsDUdZS1AXjYMk9zVfHoARiF-2-c0FSfS3zb2SQWQkUcgTR_SabzN5u6_w6zXgffoYlVJDPsmy8Z9pDGYFzOGL3Xqc_UNIz4KdEPCDkSGmQC9nhVOJXwXYb-LBhVC7qE1Cz1vpZ98mYEwE7LomPN3-34eGIWBeeMR8E4VXfQJnL4iBnO6hBfRmy8xNRgDA7VlAjJiYYolnK_x4_gTu2-x7A5AK1WYqobiCkcOClMQIioAC8ZlH17Wf_eN2lFfOEP6G50KJpYPhUezIpPzfV5osQT9Q0AsfUrzRF3MWXDM9ukNDmHiYfN0GAYiDgoMSvU3vVSCd907UQKa%26pg%3DZEULH5S2GNGCRAABCSG6J2EGGA%26ch%3DAQEAEAABABTp79nN9l_2MuRhqTXl0-SjAqcm83QU8vw.; SecureNetflixId=v%3D3%26mac%3DAQEAEQABABSdkuDdxX0gGJU2IJOTCYiVHmFkqRtHQj0.%26dt%3D1773391620952' \
|
||||
'https://android14.push.prod.netflix.com/ws'
|
||||
```
|
||||
|
||||
### 2. PlayExchange WebSocket 接続
|
||||
|
||||
```bash
|
||||
curl -v \
|
||||
-H 'Origin: http://www.netflix.com' \
|
||||
-H 'User-Agent: com.netflix.mediaclient/63928 (Linux; U; Android 14; en; Pixel 4a (5G); Build/UP1A.231005.007)' \
|
||||
-H 'X-Netflix-ProxyEsn: NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202Q7INAHS2TKI5GTQESPDEHTFK7MG1BKUU7QAPUQP2QMI641A8HN08CE40C5H2K4J15NCLBC5DGJI0M03TMV0VGS1ER8VACIG0257E' \
|
||||
-H 'x-netflix.socketrouter.schema.version: 2' \
|
||||
-H 'X-Netflix.Request.Client.Context: {"appstate":"foreground"}' \
|
||||
-H 'x-netflix.socketrouter.group.name: Test80913.Cell2|Test80897.Cell2|Test80905.Cell2' \
|
||||
-H 'Upgrade: websocket' \
|
||||
-H 'Connection: Upgrade' \
|
||||
-H 'Sec-WebSocket-Version: 13' \
|
||||
-H 'Sec-WebSocket-Extensions: permessage-deflate' \
|
||||
-b 'nfvdid=BQFmAAEBEEPj84LzHGpQ_ldxaVuQv8tgIBE9VAe3w-WeF5En4w5goMB6eLYVXqxblfzh23QC62wkeecrOuKtsfIRNC5GkWZ80HdbCeAoFLJ8LL5stGc-h87ykqdvoTT1Vg5jhdcHity8mbE5rciYfRNlegiejB_j; NetflixId=v%3D3%26ct%3DBgjHlOvc...%26pg%3DZEULH5S2GNGCRAABCSG6J2EGGA%26ch%3DAQEAEAABABTp79nN9l_2MuRhqTXl0-SjAqcm83QU8vw.; SecureNetflixId=v%3D3%26mac%3DAQEAEQABABSdkuDdxX0gGJU2IJOTCYiVHmFkqRtHQj0.%26dt%3D1773391620952' \
|
||||
'https://android14.ws.prod.cloud.netflix.com/playexchange'
|
||||
```
|
||||
|
||||
### 3. MSL API リクエスト (Samurai API)
|
||||
|
||||
MSL API は WebSocket 上の MSL プロトコルで送信される。HTTP レベルでは上記 WebSocket 接続のみ。
|
||||
MSL メッセージ内部で以下のエンドポイントにルーティングされる:
|
||||
|
||||
```
|
||||
https://android14.prod.ftl.netflix.com/nq/androidui/samurai/~9.0.0/api
|
||||
https://android14.prod.ftl.netflix.com/nq/androidui/samurai/v1/config
|
||||
https://android14.prod.ftl.netflix.com/playapi/android/event/1
|
||||
https://android14.prod.cloud.netflix.com/graphql
|
||||
https://android.prod.cloud.netflix.com/graphql
|
||||
https://android14.logs.netflix.com/log/android/logblob/1
|
||||
```
|
||||
|
||||
> **注意**: MSL メッセージボディは CBOR エンコード → Widevine CryptoContext で暗号化されるため、
|
||||
> cURL で直接再現するには暗号化済みバイナリを POST する必要がある。
|
||||
> Cookie は HTTP トランスポート層で送信され、MSL レイヤーの `userauthdata` (CBOR key 47) にも
|
||||
> 同じ `netflixId`/`secureNetflixId` が埋め込まれる(二重送信)。
|
||||
|
||||
### ESN (デバイス識別子) の構造
|
||||
|
||||
```
|
||||
NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202Q7INAHS2TKI5GTQESPDEHTFK7MG1BKUU7QAPUQP2QMI641A8HN08CE40C5H2K4J15NCLBC5DGJI0M03TMV0VGS1ER8VACIG0257E
|
||||
```
|
||||
|
||||
| セグメント | 値 | 意味 |
|
||||
|---|---|---|
|
||||
| プラットフォーム | `NFANDROID1` | Netflix Android |
|
||||
| セキュリティ | `PXA-P` | ProGuard eXtended Authentication - Production |
|
||||
| DRM レベル | `L3` | Widevine Level 3 |
|
||||
| デバイスモデル | `GOOGLPIXEL=4A==5G=` | Google Pixel 4a (5G) |
|
||||
| Widevine System ID | `22594` | CDM System ID |
|
||||
| デバイス固有 ID | `0202Q7INA...0257E` | ハードウェア固有識別子 |
|
||||
|
||||
---
|
||||
|
||||
## ESN 送信箇所の全体マップ
|
||||
|
||||
2026-03-13 キャプチャデータの分析結果。ESN は複数の場所・形式で送信される。
|
||||
|
||||
### 2 種類の ESN
|
||||
|
||||
同一デバイスに対して **2 種類の ESN** が使い分けられている:
|
||||
|
||||
| 種別 | プレフィックス | 用途 | デバイス固有ID |
|
||||
|---|---|---|---|
|
||||
| **PXA (ProxyEsn)** | `NFANDROID1-PXA-P-L3-` | HTTP ヘッダー (`X-Netflix-ProxyEsn`) | `0202Q7INA...0257E` |
|
||||
| **PRV (Private)** | `NFANDROID1-PRV-P-L3-` | ライセンス要求、DRM、再生イベント | `3E369F1C...E371` |
|
||||
|
||||
- `PXA` = ProGuard eXtended Authentication。WebSocket 接続時のデバイス識別に使用。
|
||||
- `PRV` = Private。DRM ライセンスおよびコンテンツ再生関連で使用。
|
||||
- デバイスモデル部分 (`GOOGLPIXEL=4A==5G=`) と Widevine System ID (`22594`) は共通。
|
||||
- デバイス固有 ID 部分は種別ごとに異なる。
|
||||
|
||||
### ESN が送信される全箇所
|
||||
|
||||
| # | 送信箇所 | ESN 種別 | レイヤー | フック可能性 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `X-Netflix-ProxyEsn` HTTP ヘッダー | PXA | HTTP (OkHttp) | OkHttp `Request.newBuilder()` で差し替え |
|
||||
| 2 | `/license` URL クエリパラメータ `esn=` | PRV (完全) | MSL API パラメータ | `apiRequest` body 書き換え |
|
||||
| 3 | `/events` URL クエリパラメータ `esn=` | PRV (完全) | MSL API パラメータ | `apiRequest` body 書き換え |
|
||||
| 4 | `challengeBase64` (Widevine CDM protobuf) | PRV (完全) | DRM (MediaDrm) | `MediaDrm.getProvisionRequest` / `setPropertyString` |
|
||||
| 5 | `logblob` ボディ内 | 短縮 (`NFANDROID1-GOOGLPIXEL=...`) | MSL API パラメータ | `apiRequest` body 書き換え |
|
||||
| 6 | MSL MessageHeader `sender` (CBOR key 20) | **空文字列** | MSL CBOR | 使われていない(書き換え不要) |
|
||||
|
||||
### 各箇所の詳細
|
||||
|
||||
#### 1. X-Netflix-ProxyEsn (HTTP ヘッダー)
|
||||
|
||||
WebSocket 接続 (`/ws`, `/playexchange`) 時に送信される。
|
||||
|
||||
```
|
||||
X-Netflix-ProxyEsn: NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202Q7INAHS2TKI5GTQESPDEHTFK7MG1BKUU7QAPUQP2QMI641A8HN08CE40C5H2K4J15NCLBC5DGJI0M03TMV0VGS1ER8VACIG0257E
|
||||
```
|
||||
|
||||
**フック方法**: OkHttp `RealCall.getResponseWithInterceptorChain` 内で `request.newBuilder().removeHeader().addHeader()` で差し替え。
|
||||
|
||||
#### 2-3. /license, /events URL クエリパラメータ
|
||||
|
||||
MSL API ペイロード内の URL パスに `esn=` として埋め込まれる。
|
||||
|
||||
```
|
||||
/license?licenseType=standard&playbackContextId=E3-...&esn=NFANDROID1-PRV-P-L3-GOOGLPIXEL%3D4A%3D%3D5G%3D-22594-3E369F1C9B189ED664E13DEEEE9BACB67684B4FE6283537D1A67810BFE73E371&drmContextId=2596051
|
||||
```
|
||||
|
||||
**フック方法**: `ApiHandlerImpl.apiRequest` の body (arguments[1]) を書き換え。または ESN 提供元をフックすればアプリが自動的に新 ESN で URL を組み立てる。
|
||||
|
||||
#### 4. challengeBase64 (Widevine CDM protobuf)
|
||||
|
||||
DRM ライセンス要求の `challengeBase64` フィールドに含まれる Widevine CDM protobuf 内に ESN が埋め込まれている。
|
||||
|
||||
```json
|
||||
{
|
||||
"challengeBase64_contents": {
|
||||
"device_certificate": {
|
||||
"esn": "NFANDROID1-PRV-P-L3-GOOGLPIXEL=4A==5G=-22594-3E369F1C9B189ED664E13DEEEE9BACB67684B4FE6283537D1A67810BFE73E371",
|
||||
"movieid": "81756595",
|
||||
"issuetime": 1773373148,
|
||||
"salt": "3598263455819263519576642966605"
|
||||
},
|
||||
"oem_crypto_build_information": "OEMCrypto Level3 Code May 20 2022 21:36:54",
|
||||
"widevine_cdm_version": "17.0.0",
|
||||
"device_name": "bramble",
|
||||
"architecture_name": "arm64-v8a"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この protobuf は `MediaDrm` CDM が生成するため、**直接書き換えが最も難しい箇所**。
|
||||
|
||||
**フック方法 (候補)**:
|
||||
- `MediaDrm.setPropertyString("esn", ...)` をフックして差し替え — CDM が protobuf 生成前に ESN を取得する段階で介入
|
||||
- `MediaDrm.getProvisionRequest()` の戻り値 (byte[]) 内を文字列置換 — protobuf 構造が壊れるリスクあり
|
||||
- CDM より上流の ESN 提供クラス (SharedPreferences / DeviceInfo) をフック — CDM に渡る前に差し替えるため protobuf 構造は保持される
|
||||
|
||||
> **注意**: `challengeBase64` 内の ESN は Widevine サーバー側で検証される可能性がある。
|
||||
> ESN を差し替えても CDM の `device_certificate` 署名と不整合が生じると、
|
||||
> ライセンス取得がエラーになる場合がある。
|
||||
|
||||
#### 5. logblob ボディ (テレメトリ)
|
||||
|
||||
テレメトリログ送信時に短縮 ESN が含まれる:
|
||||
|
||||
```
|
||||
NFANDROID1-GOOGLPIXEL=4A==5G=S-3269A5B59F399B066558C80DCB36AE97F9A8B477F8854DFED2C14C8453D6F556
|
||||
```
|
||||
|
||||
セキュリティ種別 (`PXA`/`PRV`) を含まない短縮形式。デバイス固有 ID も PXA/PRV とは異なる第3の値。
|
||||
|
||||
#### 6. MSL MessageHeader sender (CBOR key 20)
|
||||
|
||||
キャプチャした全 353 ヘッダーで **sender は空文字列 `""`** だった。
|
||||
Android 版では MSL MessageHeader の sender フィールドは使われておらず、ESN は上記 1-5 の経路で送信される。
|
||||
|
||||
### ESN 書き換え戦略まとめ
|
||||
|
||||
```
|
||||
最上流 (推奨)
|
||||
├── SharedPreferences / DeviceInfo → ESN 文字列の保持元
|
||||
│ └── ここを書き換えれば #1, #2, #3, #5 は自動的に反映
|
||||
│
|
||||
├── MediaDrm.setPropertyString("esn") → CDM への ESN 設定
|
||||
│ └── ここを書き換えれば #4 (challengeBase64) にも反映
|
||||
│
|
||||
最下流 (個別対応)
|
||||
├── OkHttp Request.newBuilder() → #1 のみ
|
||||
├── apiRequest body replace → #2, #3, #5
|
||||
└── protobuf byte[] replace → #4 (構造破壊リスク)
|
||||
```
|
||||
|
||||
**理想的なアプローチ**: ESN の提供元 (SharedPreferences / EsnManager 相当クラス) と `MediaDrm.setPropertyString` の 2 箇所をフックすれば、全箇所に波及する。
|
||||
Reference in New Issue
Block a user