残念ながら今のところは動きません…下に技術的な詳細があります:
Markdown below ## 保護の仕組み — 多層防御
**3つの独立したレイヤー**があって、どれも単体で十分なレベルです。合わせると、CDMハードウェアの協力なしにフル画質で保存するのは事実上不可能になります。
---
### レイヤー1 — 通信の暗号化(アプリケーションレベルのAES-GCM)
プロキシ(`/api/secure-proxy/…`)からのHTTPレスポンスはすべて、通常のHTTPS通信の上に**アプリケーション層で**AES-256-GCMで暗号化されています。プレイヤーのJavaScriptがセッション鍵を持っていて、届いた暗号文を復号してから、初めて生のバイト列をブラウザに渡します。ネットワークを傍受(Wireshark、HAR、プロキシ)しても、m3u8プレイリスト、initセグメント、メディアセグメントなど全部、役に立たない暗号文しか取れません。
**防げるもの**:あらゆる受動的なネットワーク傍受。
---
### レイヤー2 — ウォーターマーク(ユーザー固有のストリームURL)
復号が意味を持つ前の段階で、サーバーは各ユーザーを固有のURLパスに振り分けて、目に見えるウォーターマークを焼き込みます:
```
/api/secure-proxy/wm-stream/{64-char token}/{userId}/{resolution}/seg.m4s
```
`{userId}`の部分が、エンコード時にそのユーザーの識別子を映像フレームに合成するようサーバーに指示します。つまり、仮に動画をキャプチャできても、あなたのアカウント情報が埋め込まれていて、あなたまで辿られます。
同じストリームのクリーン版もこちらにあります:
```
/api/secure-proxy/stream/{64-char token}/{resolution}/seg.m4s
```
どちらのURLも有効で、ウォーターマークのオーバーレイが無い以外は同じ内容を返します。
**防げるもの**:キャプチャした動画の共有(元アカウントが特定される)。
---
### レイヤー3 — Widevine CENC DRM(破れない部分)
これが中核の保護です。プレイヤーがAES-GCMから復号するfMP4セグメントは**生の動画ではなく**、サンプル単位で[Common Encryption (CENC)](https://www.w3.org/TR/eme-stream-mp4/)によってさらに暗号化されています。各映像フレームは、Widevine CDM(Content Decryption Module)が持つ鍵で個別に暗号化されます。
CDMは、ハードウェアで隔離されたエンクレーブ内で動く**ネイティブのバイナリプラグイン**です。暗号化されたサンプルを受け取り、ライセンスサーバーから取得した鍵で復号して、デコードしたフレームを**GPUコンポジタに直接**書き込みます。JavaScriptの手が届かない完全に外側の領域です。復号されたピクセルは、Webページや拡張機能が触れられるアドレス可能なメモリ上には一切存在しません。
生のfMP4構造を見れば確認できます:
- `stsd`ボックス:サンプルエントリのタイプは`avc1`ではなく`encv`(暗号化ビデオ)
- `moov`ボックス:Widevineのシステム ID(`edef8ba9…`)を持つ`pssh`ボックスを含む
- 各`moof`(メディアフラグメント)に`senc`、`saiz`、`saio`ボックスがある — サンプルごとの初期化ベクタとキーID
- `mdat`ペイロード:H.264パラメータセット(SPS/PPS)は平文だが、スライスのNALユニットはすべて暗号化されている — エントロピーは≈5.7ビット/バイト、完全に圧縮されたデータだと≈7.9
**防げるもの**:ブラウザのJavaScriptエンジン内でデータを傍受するあらゆる手法。
---
## ほぼ不可能な理由
```
Network wire
│ HTTPS (TLS) ← 標準的、特別なものではない
▼
Proxy server returns
│ AES-GCM encrypted blob ← レイヤー1
▼
Player JS decrypts with session key
│ fMP4 segments ← ここまではアクセス可能、だが…
▼
appendBuffer() ← ここにフックした
│ CENC-encrypted samples ← レイヤー3はまだ有効
▼
Widevine CDM (native, hardware-isolated)
│ Decrypted frames → GPU compositor ← レイヤー3の復号はここ
▼
Screen pixels
```
**完全に復号された表示可能な動画**を傍受できるのは、GPUコンポジタの後、つまり画面そのものだけです。`chrome.tabCapture`がまさにそれをやっていて、レンダリングされたコンポジタの出力をキャプチャします。ただ、その時点では既に画面に出ているものを再エンコードすることになるので、画質が1世代分落ちます(しかもリアルタイムで録画する必要あり)。
---
## 実際にやった手順(順番どおり)
| ステップ | 試したこと | 結果 |
|--- | --- | ---|
| **1. HAR解析** | ネットワークトラフィックを見てURL構造を把握 | `wm-stream` / `stream`のURLパターンと、全レスポンスにAES-GCM暗号化があることを発見 |
| **2. fetchフック(最初の試み)** | `window.fetch`を横取りして`wm-stream`のURLを`stream`のURLに書き換え | 書き換えが黙って破棄された — strictモードの`arguments`エイリアスのバグ:`input`引数を再代入しても`arguments[0]`が更新されず、`.apply(this, arguments)`が元のURLを送っていた |
| **3. argumentsバグの修正** | `.apply(this, arguments)`をやめて`_origFetch.call(this, input, init)`に変更 | ウォーターマーク除去がちゃんと動くようになった |
| **4. CSPエラー** | 拡張機能が次を注入していた