DocuChan の実際の仕組み — そして確認する方法
「信じてください」はセキュリティモデルではありません。このページは DocuChan の threat model です。別のエンジニアが自分たちのために 書いてくれるならこうあってほしい、という粒度で書いています。何が どこで暗号化されるのか、サーバーが保存するものの全て、私たちが守れないもの、そしてブラウザの developer tools で主張を 自分で検証する方法まで扱います。
「安全な」ファイル共有の問題
ほぼ全てのファイル共有サービスは「安全」と言います。そして多くの 場合、それは同じ二つを意味します。通信中の TLS と、ディスク上での 保存時暗号化です。どちらも大切です。ただし、どちらも提供者自身が ファイルを読むことは止めません。鍵を持っているのは提供者だからです。 契約書、パスポートのスキャン、未公開のビルドが、他人のサーバー上に そのサーバーで開ける形で置かれます。ダウンロードページは、その横に 広告を出すことで成り立っていることもあります。
エンドツーエンド暗号化は、より強い主張です。ファイルは あなたの端末を出る前に、サービスが受け取らない鍵で暗号化 されます。そうするとサーバー側で何が起きても、好奇心の強い管理者、 令状、storage bucket の漏えいがあっても、そこにあるのは 暗号文です。DocuChan は、この状態を ポリシー上の約束ではなく、設計そのもので強制するように作られています。
この話では二つのものが移動します。そして同じ道を通りません。色を見て ください。鍵は青、 暗号文は緑です。
鍵は私たちに届きません — 古いブラウザ仕様のおかげです
ファイルをアップロードすると、ブラウザは Web Crypto API を使って ランダムな 256-bit AES-GCM 鍵を生成 します。その鍵は共有リンクに入ります。ただし場所が重要です。 URL fragment、つまり # の後ろです。
Fragment は JavaScript より古い仕組みです。ページ内の見出しへ ジャンプするために作られたため、ブラウザはそれを HTTP リクエストに含めない仕様になっています。全ての ブラウザがこれに従います。受信者がリンクを開くと、私たちのサーバーが 見るのは /d/x7Kq0aH2 へのリクエストだけです。受信者の 端末で動く JavaScript が fragment をローカルで読み、その場で復号 します。Firefox Send も同じ構成を使っていました。つまり「backdoor なし」という主張は、私たちの善意ではなく、鍵がどこを通るかという 性質に基づいています。
ファイルはどう暗号化されるか
ファイルは 8 MiB chunk ごとに stream で暗号化 されます。chunk を読む、暗号化する、アップロードする、次へ進む。 ブラウザがファイル全体をメモリに持つことはありません。だから低 RAM のノート PC やスマートフォンでも、数 GB のアップロードが動きます。 各 chunk にはランダムな 12-byte IV が付き、 AES-256-GCM で封印されます。wire format は単純で、[IV | ciphertext + auth tag] が繰り返されます。
GCM は認証付きモードです。各 chunk には復号時に検証される tag が あります。もし誰かが保存された 暗号文の 1 bit でも変えた場合、敵対的な storage provider、中間者、ディスク 破損のいずれであっても、復号は明確に失敗し、ダウンロードはファイルを 出力しません。改ざんされた文書を気づかず受け取ることはありません。
ファイル名も同じ扱いです。これは私たちのサーバーが読む必要のない metadata なので、保存前に同じ per-file key で client-side に暗号化 されます(e2e: prefix の blob として)。私たち側に あるのは、ランダムな id、不透明な label、バイト数だけです。保存 する MIME type は application/octet-stream です。 PDF なのか写真なのか、私たちには分かりません。
バイト列は実際にどこへ行くのか
明記しておきたい点があります。暗号化された chunk は presigned URL でobject storage に直接アップロードされます。私たちの application server はアップロードを認可して URL を渡しますが、ファイル のバイト列はそこを通りません。偶然でも plaintext が存在しうる中間地点が ありません。storage provider は、鍵を持たない暗号文を保存するだけです。 ダウンロードは逆方向で、chunk が届くたびに stream で復号されます (File System Access API がないブラウザでは service worker 経由なので、 大きなファイルでもメモリが膨らみません)。
サーバーが保存する全て — 完全なリスト
暗号化のスローガンより、metadata について正直であることの方が重要です。 だから per-transfer record 全体を、schema からそのまま挙げます。
- ランダムで推測不能な link id(storage object の名前でもあります)
- 暗号化されたファイル名 label(私たちには不透明)
- 暗号文の byte size
- expiry time、burn-after-download flag、download counter
- パスワードを設定した場合: PBKDF2 verifier hash。パスワードそのものは 決して保存しません(次の章)
- 作成時刻、ログインしていた場合は account id
運用上は、rate limiting と abuse control のために IP address の salted hashes を短期間保持します。アドレスそのものではありません。加えて 標準的な web-server logs があります。analytics は self-hosted かつ cookieless で、ファイル名や link id を見ることはありません。もし明日 法的開示請求を受け取ったとしても、ファイルについて出せる情報は上のリストと 暗号文が全てです。私たちは何かが共有されたことは確認できます。 しかし何が共有されたかは出せません。
パスワードは鍵ではなく、ゲートです
リンクにはパスワードを付けられます。ここで何が起きるのかは正確に 言う必要があります。パスワードは暗号鍵の由来ではありません。 鍵はパスワードの有無に関わらず fragment 内にあります。パスワードは access gateです。サーバーはそれなしでは暗号文を渡しません。 これは、リンクだけが流出し、パスワードは流出していない場合 (転送メール、chat log、screenshot など)に効きます。
パスワード自体も私たちは知りません。ブラウザは PBKDF2 (SHA-256、210,000 iterations、ランダム salt — salt は鍵の隣で fragment に入ります)で verifier を導出し、保存・照合されるのはその verifier だけです。照合は download 時に constant time で行われます。 database leak が起きても、得られるのは総当たりに時間のかかる hash であり、 あなたのパスワードではありません。
自動削除を正確に言うと
"Burn after download" は、最初の完了したダウンロード後に ファイルを削除します。burn が発火する前に、受信者のブラウザが完全保存を 確認します。そのため接続が 90% で切れても、読めないままファイルが消える ことはありません。キャンセルされたダウンロードでも burn しません。時間 指定のリンクは、background janitor が expiry 時刻に削除します。削除とは、 暗号文 object が storage から取り除かれ、リンクが解決されなくなること です。私たち側には最初から暗号文しか存在しないので、追いかけるべき plaintext copy はありません。
Beam: ファイルをサーバーに触れさせたくないとき
device-to-device 送信(docuchan.com/beam)では、 ファイルは WebRTC data channel を通って二つのブラウザ間を直接移動します。 私たちのサーバーは接続の handshake だけを relay します(SDP/ICE、つまり ネットワーク座標、数 KB)。ただし WebRTC 内蔵の DTLS 暗号化には既知の 弱点があります。handshake を relay する側が、原理上は中間者になれるのです。 そこで Beam は上に独自のエンドツーエンド層を載せています。設計上、 私たち自身も threat model に入っています。
- 各端末は一時的な ECDH P-256 key pair を生成し、最初に commitment を送ります。これは ephemeral public key と 永続的な device identity key の組み合わせに対する SHA-256 hash です。
- peer の commitment を受け取ってから、各側が実際の key を reveal します。両方の key は commitment と照合され、 AES-256-GCM session key と短い code の両方に結び付けられます。
- 両方の画面に 6-character code(Short Authentication String)が表示されます。人間が一度だけ一致を確認し、端末を pin すれば 次回から自動にできます。
commit-before-reveal の段階が、短いコードを安全にします。悪意ある relay は、あなたの key を見る前に差し替える key を選ばなければ なりません。そのため、両画面に同じコードを出す key pair を後から探す ことはできません。およそ 10 億分の 1 の盲目的な一発勝負です。これは 音声通話を守るために ZRTP が使った構成と同じです。直接接続できず relay (TURN)が traffic を運ぶ場合でも、relay が見るのは既に暗号化された stream だけです。
私たちが守れないもの
制限を一つも書かないセキュリティページは、マーケティングページです。 DocuChan の制限は次の通りです。
- 私たちが配信する JavaScript を信頼する必要があります。暗号化は、ページを読み込むたびに私たちのサーバーから届くコード内で 実行されます。私たちが悪意を持った場合、強制された場合、あるいは侵害 された場合、そのコードは変更され得ます。これは全ての browser-based E2E tool に当てはまります。暗号化 messenger の web 版も同じです。 コメント欄で見つけてもらうより、ここで明言します。client source を 検査できるよう公開することは積極的に検討中であり、正直な mitigation です。
- third-party audit はまだありません。 設計はよく研究 された構成(Firefox Send の fragment scheme、ZRTP-style SAS)に従って いますが、この実装を独立した第三者が監査したわけではありません。変化が あれば、レポートをここにリンクします。
- リンクそのものが秘密です。 完全なリンク(パスワードを 設定している場合はパスワードも)を持つ人は、ファイルを復号できます。 信頼できる経路で共有し、漏れたリンクが最初の使用後に無効になるよう burn-after-download を使ってください。
- あなたの端末は scope 外です。 malware や肩越しのカメラは、 私たちのものを含め、どんな暗号化も破ります。
- 失われた鍵は、失われたファイルです。 リンクの鍵を復旧、 reset、再生成することはできません。私たちはその鍵を持っていませんでした。 これは設計通りですが、"forgot password" に慣れた人には驚かれます。
穴を見つけましたか?知らせてください。
脆弱性を見つけた場合、本当に教えてほしいです。これは建前ではありません。 検証されることこそ、このページの存在理由です。件名に "Security" を入れて [email protected] へメールしてください(security.txtにも記載しています)。全ての report を読み、返信し、希望する発見者には credit を載せます。ほかの人のファイルでテストしないでください。
— Beki, DocuChan の builder · よりやさしい説明 · 実際のファイルで試す