Files
revkit/docs/auth_flow_android.md
T

11 KiB

Netflix Android 起動時認証フロー

対象: Netflix Android v9.57.0 (build 63928) デバイス: Pixel 4a (5G) / bramble / Android 14 取得方法: Frida フック (hook_netflix_android.js) + run.py による JSONL キャプチャ キャプチャ日: 2026-03-14


1. 概要

Netflix Android アプリの起動時に発生する認証・プロビジョニングフローを、キャプチャしたトラフィックから再構成した。

各 API のリクエスト・レスポンス詳細は auth_flow_android_api.md を参照。

1.1 通信先エンドポイント

エンドポイント 略称 プロトコル 用途
android14.appboot.netflix.com appboot HTTPS (MSL レスポンス) デバイス登録・nfvdid Cookie 発行
android14.prod.ftl.netflix.com prod.ftl HTTPS + MSL MSL API (/nq/androidui/samurai/) + GraphQL
android14.prod.cloud.netflix.com prod.cloud HTTPS + MSL GraphQL via MSL

1.2 通信の分類

分類 Content-Encoding ボディ形式 認証方式
MSL API msl_v1 CBOR → GZIP → JSON MSL トークン (Master Token + User Auth Data)
Non-MSL GraphQL なし JSON Cookie (NetflixId / SecureNetflixId)
Appboot なし (レスポンスは application/x-msl+json) Query String nfvdid Cookie

2. 認証フロー全体像

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant App as Netflix App
    participant AB as appboot
    participant FTL as prod.ftl<br/>(MSL API)
    participant Cloud as prod.cloud<br/>(GraphQL)

    Note over App: 起動
    App->>App: ProxyESN 期限チェック (※)

    rect rgba(180, 100, 50, 0.2)
        Note over App,Cloud: Phase 1: リクエスト送信
        App->>AB: POST /appboot/{BaseESN}
        App->>FTL: [MSL] /getProxyEsn
        App->>FTL: [MSL] /aleProvision #1 (RSA鍵交換)
        App->>Cloud: [MSL] RenewSSOToken
        App->>FTL: CurrentCountryQuery
        App->>Cloud: [MSL] InterstitialForProfileGate
        App->>Cloud: [MSL] InterstitialForLolomo
    end

    rect rgba(50, 180, 80, 0.2)
        Note over App,Cloud: Phase 2: レスポンス受信
        FTL-->>App: CurrentCountryQuery → +NetflixId, SecureNetflixId [Cookie]
        FTL-->>App: getProxyEsn → MSL レスポンス
        FTL-->>App: aleProvision #1 → MSL レスポンス
        Cloud-->>App: RenewSSOToken → 200 OK
        AB-->>App: appboot → nfvdid [Cookie] + DeviceIdToken
        Cloud-->>App: InterstitialForLolomo → 200 OK
        Cloud-->>App: InterstitialForProfileGate → 200 OK
    end

    rect rgba(60, 120, 200, 0.2)
        Note over App,Cloud: Phase 3: 追加リクエスト
        App->>Cloud: [MSL] AccountQuery
        Cloud-->>App: AccountQuery → 200 OK
        App->>FTL: [MSL] /aleProvision #2 (RSA鍵交換)
        FTL-->>App: aleProvision #2 → 新 PXA ESN 発行
        App->>App: proxyEsn.onKnown (新ESN保存)
        App->>FTL: PromoProfileGateVideoDataQuery
        FTL-->>App: 200 OK + NetflixId, SecureNetflixId [Cookie]
    end

    rect rgba(150, 150, 150, 0.15)
        Note over App,Cloud: Phase 4: 遅延リクエスト
        App->>FTL: [MSL] FetchConfigData
        App->>Cloud: [MSL] AccountQuery #2
        FTL-->>App: 200 OK
        Cloud-->>App: 200 OK
    end

3. フェーズ概要

3.1 Phase 0: ProxyESN 期限チェック

アプリ起動時、ProxyEsn.$init でキャッシュ済み PXA ESN の期限がチェックされる。期限切れの場合はサーバーから新しい PXA ESN を取得する。

※ キャプチャ時の注意: 今回のキャプチャでは Frida フック (hook_msl.js) が ProxyEsn.$init で expired フラグを強制的に true に書き換えているため、TTL が残っていても必ず再取得が発生する。通常の動作では TTL が有効ならキャッシュがそのまま使用される。

3.2 Phase 1: リクエスト送信

起動後、以下のリクエストが並列に送信される:

  1. Appboot — デバイス登録・Cookie 発行
  2. getProxyEsn (MSL) — PXA ESN 取得
  3. aleProvision #1 (MSL) — RSA-OAEP-256 鍵交換
  4. RenewSSOToken (MSL → GraphQL) — SSO トークン更新
  5. CurrentCountryQuery — 国情報取得 + 認証 Cookie 発行
  6. InterstitialForProfileGate / InterstitialForLolomo (MSL → GraphQL) — UI 中間画面チェック

3.3 Phase 2: レスポンス受信

Phase 1 のレスポンスが順次到着する。主要なデータ:

  • CurrentCountryQuery のレスポンスで NetflixId / SecureNetflixId Cookie が発行される
  • appboot のレスポンスで nfvdid Cookie が更新され、x-netflix-deviceidtoken が発行される

3.4 Phase 3: 追加リクエスト

Phase 2 のレスポンスを受けて追加リクエストが送信される:

  1. AccountQuery (MSL → GraphQL) — アカウント情報取得
  2. aleProvision #2 (MSL) — 再鍵交換 → PXA ESN 確定
  3. PromoProfileGateVideoDataQuery — プロフィール画面データ取得

3.5 Phase 4: 遅延リクエスト

起動から約 30 秒後、バックグラウンドで送信される:

  1. FetchConfigData (MSL → Samurai) — デバイス・ストリーミング設定取得
  2. AccountQuery #2 (MSL → GraphQL) — アカウント情報再取得

4. 鍵交換と暗号化フロー

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as Netflix App
    participant Server as prod.ftl (MSL)

    Note over Client: RSA-2048 鍵ペア生成

    Client->>Server: aleProvision #1<br/>pubkey + scheme=A128GCM<br/>type=SOCKETROUTER
    Server-->>Client: A128GCM セッション鍵<br/>(RSA-OAEP-256 で暗号化)

    Note over Client: セッション鍵復号<br/>→ 以降の MSL 通信で使用

    Client->>Server: aleProvision #2<br/>同一 pubkey
    Server-->>Client: 新セッション鍵 + PXA ESN

    Note over Client: PXA ESN 保存<br/>→ MSL ヘッダに使用

4.1 ALE Provision パラメータ

パラメータ 値 説明
keyx.scheme RSA-OAEP-256 鍵交換暗号化方式 (RSA with OAEP padding, SHA-256)
keyx.data.pubkey Base64 RSA 2048-bit クライアントが生成する使い捨て公開鍵
scheme A128GCM セッション暗号化方式 (AES-128-GCM)
type SOCKETROUTER プロビジョニング種別
ver 1 プロトコルバージョン

4.2 aleProvision が 2 回呼ばれる理由

  1. #1: 起動直後に即座に鍵交換を開始。他のリクエストと並列。
  2. #2: getProxyEsn のレスポンスを受けた後に再度鍵交換。PXA ESN の発行と紐づく。

同一の RSA 公開鍵を使うが、サーバー側で新しいセッション鍵が発行される。


%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant App as Netflix App
    participant AB as appboot
    participant FTL as prod.ftl
    participant Cloud as prod.cloud

    Note over App: 起動時: nfvdid (既存) のみ保持

    App->>AB: POST /appboot (nfvdid Cookie なし)
    App->>FTL: CurrentCountryQuery
    App->>Cloud: [MSL] RenewSSOToken

    FTL-->>App: set-cookie: NetflixId=..., SecureNetflixId=...
    Note over App: ← 認証 Cookie 取得

    AB-->>App: set-cookie: nfvdid=... (更新)<br/>x-netflix-deviceidtoken: ...
    Note over App: ← デバイス Cookie 更新 + Device ID Token 取得

    App->>FTL: PromoProfileGateVideoDataQuery
    Note over App: NetflixId / SecureNetflixId Cookie を送信
    FTL-->>App: set-cookie: NetflixId=..., SecureNetflixId=...
    Note over App: ← 認証 Cookie リフレッシュ
Cookie 名 発行元 用途 発行タイミング
nfvdid appboot デバイス識別子 appboot レスポンス (set-cookie)
NetflixId prod.ftl ユーザー認証 CurrentCountryQuery レスポンス
SecureNetflixId prod.ftl セキュア認証 (HTTPS only) CurrentCountryQuery レスポンス

5.2 重要な観察

  • MSL リクエストは Cookie に依存しない。 MSL は独自の Master Token + User Auth Data で認証を行う。
  • Non-MSL GraphQL は Cookie で認証する。 NetflixId / SecureNetflixId が必要。
  • nfvdid は全リクエストに付与されるが、認証には直接使われない (デバイストラッキング用)。

6. ProxyESN ライフサイクル

%%{init: {'theme':'dark'}}%%
stateDiagram-v2
    [*] --> Cached: アプリ前回終了時に保存
    Cached --> CheckTTL: 起動時に TTL チェック
    CheckTTL --> Active: TTL 有効 → キャッシュ使用
    CheckTTL --> Requesting: TTL 期限切れ → /getProxyEsn 送信
    Requesting --> Provisioning: aleProvision #1 + #2
    Provisioning --> Received: proxyEsn.response
    Received --> Active: proxyEsn.onKnown<br/>(新 ESN キャッシュ保存)
    Active --> [*]: MSL 通信で使用

6.1 ESN の変化

タイミング ESN
起動時 (キャッシュ) NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-02025HJ6UHN1D...
更新後 NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202DQTKQKE1UP...
  • 22594 = Widevine System ID (固定)
  • 末尾の HMAC 部分のみが変化 (サーバー側で新規生成)

7. セキュリティ上の観察

  1. 二重認証メカニズム: MSL (Master Token ベース) と Cookie (NetflixId ベース) の 2 系統が並行して使われる。
  2. PXA ESN ローテーション: PXA ESN は TTL ベースで管理され、期限切れ時にサーバーから新しい ESN を取得する。今回のキャプチャでは Frida が強制失効させているため、毎回再取得が発生している。
  3. 鍵の使い捨て: aleProvision の RSA 鍵ペアはセッション単位で生成される (2048-bit)。
  4. Persisted Query: GraphQL クエリ本文をネットワーク上で送信しないため、API スキーマの隠蔽に寄与。
  5. Cookie の分離: SecureNetflixId は Secure 属性付きで HTTPS でのみ送信される。
  6. 並列リクエスト: 起動時間を最小化するため、認証に必要な全リクエストが並列に送信される。
  7. トークンの用途限定: ssoToken は RenewSSOToken のみ、x-netflix-deviceidtoken は appboot レスポンスでのみ観測された。これらのトークンが HTTP レベルで他の API に転送される様子はなく、MSL レイヤー内部での使用またはローカル保持が示唆される。