技術解説

DocuChan の実際の仕組み — そして、その確かめ方

「信じてください」はセキュリティモデルではありません。このページは DocuChan の脅威モデルを、私たち自身がほかのエンジニアに書いてほしいと思う書き方でまとめたものです。何がどこで暗号化されるのか、サーバーが保存しているものの全容、私たちには守れないことの具体的な範囲、そしてブラウザの開発者ツールを使って、ここに書いた主張をご自身で確かめる方法までを扱います。

「安全な」ファイル共有が抱える問題

ほとんどのファイル共有サービスは「安全」をうたっていますが、その意味はたいてい同じ二つです。通信経路での TLS と、事業者のディスク上での保存時の暗号化です。どちらも有用ですが、どちらも事業者自身がファイルを読むことまでは防げません。鍵を持っているのは事業者だからです。あなたの契約書も、パスポートのスキャン画像も、公開前のビルドも、他人のサーバー上に、そのサーバーが開ける形で置かれています。しかもダウンロードページは、その横に広告を表示して収益を上げていることも少なくありません。

エンドツーエンド暗号化は、それよりも強い保証です。ファイルはあなたの端末を離れる前に、サービス側が一度も受け取らない鍵で暗号化されます。そうなれば、サーバー側で何が起きても大きな問題にはなりません。好奇心に駆られた管理者がいても、法的な開示要求が届いても、ストレージバケットから漏えいが起きても、そこにあるのは暗号文だけです。DocuChan は、これをポリシー上の約束ではなく、設計そのものによって保証するように作られています。

この話では二つのものが移動しますが、両者が同じ道を通ることは決してありません。色に注目してください。鍵は青、暗号文は緑で示しています。

鍵が私たちに届くことはありません — 昔からあるブラウザの決まりのおかげです

ファイルをアップロードすると、ブラウザは Web Crypto API を使って、ランダムな 256 ビットの AES-GCM 鍵を生成します。この鍵は共有リンクに含まれますが、置かれる場所が特別です。それが URL のフラグメント、つまり # より後ろの部分です。

docuchan.com/d/x7Kq0aH2サーバーに送信される#Zk3vRw…kQ9M.mR8wSalt送信されない — ブラウザ内にとどまる
DocuChan のリンクです。パスの部分が暗号文を特定し、フラグメントが復号鍵(パスワードを設定した場合はそのソルトも)を運びます。

フラグメントは JavaScript よりも古い仕組みです。ページ内の見出しへジャンプするために設計されたもので、ブラウザはフラグメントを決して HTTP リクエストに含めないよう仕様で定められており、すべてのブラウザがこれに従っています。受信者がリンクを開いたとき、私たちのサーバーに届くのは /d/x7Kq0aH2 へのリクエストだけです。受信者の端末で動く JavaScript がフラグメントをその場で読み取り、端末内で復号します。これは Firefox Send が採用していたのと同じ構成です。つまり「バックドアなし」という主張は、私たちの善意によるものではなく、鍵がどこを通るかという構造そのものから導かれる性質です。

ファイルはどのように暗号化されるか

ファイルは 8 MiB ずつのチャンクに分割され、ストリーミング方式で暗号化されます。チャンクを一つ読み込み、暗号化し、アップロードしたら次へ進む、という流れです。ブラウザがファイル全体をメモリに抱えることはないため、メモリの少ないノート PC やスマートフォンでも、数 GB のファイルをアップロードできます。各チャンクには固有のランダムな 12 バイトの IV が割り当てられ、AES-256-GCM で封印されます。送信時のデータ形式は単純で、[IV | ciphertext + auth tag] の繰り返しです。

ファイルが「一度にまとめて」暗号化されることはありません。チャンクごとに固有の IV を持つ独立した封印が波のように続き、できたものから順に送り出されていきます。

GCM は認証付きの暗号化モードです。各チャンクには認証タグが付いており、復号時に検証されます。保存された暗号文が 1 ビットでも書き換えられれば、原因が悪意のあるストレージ事業者でも、通信経路上の中間者でも、ディスクの破損でも、復号ははっきりと失敗し、ダウンロードはファイルを出力しません。改ざんされた文書を、気づかないまま受け取ってしまうことはありません。

ファイル名も同じように扱います。ファイル名はサーバーが読む必要のないメタデータなので、保存する前に、同じファイル固有の鍵を使ってブラウザ側で暗号化します(先頭に e2e: が付いたデータとして保存されます)。私たちの側から見えるのは、ランダムな ID、中身の読めないラベル、そしてバイト数だけです。保存する MIME タイプは application/octet-stream なので、それが PDF なのか写真なのかさえ分かりません。

バイト列は実際にどこを通るのか

道は二つあります。暗号文は上の道を通ってストレージを経由し、鍵はリンクの中だけに乗って、人から人へと渡ります。両者が再び出会うのは受信者のブラウザの中です。ファイルが平文の状態で存在するのは、その場所だけです。

一つ、はっきり書いておきたい点があります。暗号化されたチャンクは、署名付き URL を使ってオブジェクトストレージへ直接アップロードされます。私たちのアプリケーションサーバーはアップロードを許可して URL を発行しますが、ファイルのバイト列がそこを通ることはありません。そのため、たとえ偶然であっても平文が存在しうる中間地点がありません。ストレージ事業者の側も、鍵を持たない暗号文を保管するだけです。ダウンロードはこの経路を逆にたどり、チャンクが届くたびに順次復号されます(File System Access API に対応していないブラウザではサービスワーカーを経由するため、大きなファイルでもメモリ使用量が膨らみません)。

サーバーが保存するもの — すべての項目

暗号化をうたう標語よりも、メタデータについて正直であることのほうが大切です。そこで、転送ごとに保存している記録の全項目を、データベース定義のとおりに挙げます。

  • 推測できないランダムなリンク ID(ストレージ上のオブジェクト名も兼ねます)
  • 暗号化されたファイル名のラベル(私たちには読めません)
  • 暗号文のサイズ(バイト数)
  • 有効期限、ダウンロード後に削除するかどうかの設定、ダウンロード回数
  • パスワードを設定した場合は、PBKDF2 による検証用ハッシュ(パスワードそのものは保存しません。詳しくは次の節で説明します)
  • 作成日時と、ログインしていた場合はあなたのアカウント ID

運用面では、レート制限と不正利用対策のために、IP アドレスにソルトを加えてハッシュ化した値を短期間だけ保持しています(アドレスそのものではありません)。ほかに、標準的な Web サーバーのログもあります。アクセス解析は自前でホストしており、Cookie も使いません。ダウンロード、ファイルリクエスト、ギフト、アカウント、Rooms の各ページではそもそも読み込まれません。PDF ツールのページでは、私たち自身のコードが1件のページビュー(パスのみ)を送るだけで、文書の横でトラッカーのスクリプトが動くことはなく、Content-Security-Policy もそれ以外の送信先を許可しません。それ以外のページでも、クエリ文字列と # 以降のフラグメントを取り除いてから記録するため、鍵が記録されることはありえません。記録されるイベントに含まれるのは大まかな件数とサイズの区分だけで、ファイル名が含まれることはありません。仮に明日、法的な開示要求を受けたとしても、あなたのファイルについて私たちが提出できるのは、上の一覧と暗号文がすべてです。何かが共有されたことは確認できても、何が共有されたのかを示すことはできません。

パスワードは鍵ではなく、関門です

リンクにはパスワードを設定できます。その働きについては、正確に説明しておきます。暗号鍵はパスワードから作られるわけではありません。鍵はパスワードの有無に関係なく、フラグメントの中にあります。パスワードはアクセスの関門です。正しいパスワードがなければ、サーバーは暗号文を渡しません。これは、リンクだけが漏れてパスワードは漏れていない場合(転送されたメール、チャットの履歴、スクリーンショットなど)にあなたを守ります。

パスワードそのものも、私たちが知ることはありません。ブラウザはパスワードから PBKDF2(SHA-256、反復回数 210,000 回、ランダムなソルト。ソルトは鍵と並んでフラグメントに入ります)で検証用の値を導出し、保存されるのはその値だけです。ダウンロード時の照合も、この値を使って定数時間で行われます。万一データベースが流出しても、手に入るのは総当たりに時間のかかるハッシュであって、あなたのパスワードではありません。

自動削除の仕組みを正確に

保存が一度完了すれば、あとには漏れるものが何も残りません。私たちにとっても、後からリンクを見つけた誰かにとっても。

「ダウンロード後に削除」を有効にすると、最初のダウンロードが完了した時点でファイルが削除されます。削除が実行されるのは、受信者のブラウザがファイル全体の保存を確認してからです。そのため、90% の時点で接続が切れても、読まれないままファイルが消えてしまうことはありません。ダウンロードをキャンセルした場合も削除されません。有効期限付きのリンクは、期限が来るとバックグラウンドの削除処理によって消去されます。削除とは、暗号文のオブジェクトがストレージから取り除かれ、リンクが機能しなくなることを意味します。私たちの側には最初から暗号文しか存在しないため、追いかけて消すべき平文のコピーはどこにもありません。

Beam:ファイルをサーバーに触れさせたくないとき

端末間で直接送る場合(Beam)、ファイルは WebRTC のデータチャネルを通って、二つのブラウザの間を直接移動します。私たちのサーバーが中継するのは、接続のハンドシェイク(SDP/ICE。ネットワーク上の接続先の情報で、数 KB 程度)だけです。ただし、WebRTC に組み込まれた DTLS 暗号化には、よく知られた弱点があります。ハンドシェイクを中継する側が、原理的には通信の間に割り込めてしまうのです。そこで Beam は、その上に独自のエンドツーエンド暗号化の層を重ねています。この層は、私たち自身を脅威モデルに含めて設計されています。仕組みは次のとおりです。

  1. 各端末は一時的な ECDH P-256 鍵ペアを生成し、まずコミットメントを送ります。これは、一時公開鍵を、その端末に固有の永続的な識別鍵と結び付ける SHA-256 ハッシュです。
  2. 相手のコミットメントを受け取ってはじめて、各端末はそれらの鍵を開示し、開示された鍵はコミットメントと照合されます。そのうえで、両方の鍵が AES-256-GCM のセッション鍵にも短いコードにも組み込まれます。
  3. 両方の画面に 6 文字のコード(SAS:短い認証文字列)が表示されます。利用者が一致を一度確認したら、その端末をピン留めしておくことで、次回からは自動で接続できます。
ハンドシェイクの順序です。どちらの鍵ペアも開示される前に、各端末が自分の一時鍵と端末の識別情報を確定させます。そのあとで、利用者が短いコードを一つ見比べます。

短いコードを安全なものにしているのは、開示の前にコミットさせるこの手順です。悪意のある中継者は、あなたの鍵を見る前に、すり替える鍵を決めなければなりません。そのため、両方の画面に同じコードが表示されるような鍵の組み合わせを、総当たりで探すことはできません。許されるのは当てずっぽうの一回きりで、成功する確率はおよそ 10 億分の 1 です。これは ZRTP が音声通話を保護するために用いたのと同じ構成です。直接接続できず、中継サーバー(TURN)が通信を運ぶ場合でも、中継サーバーに見えるのは、すでに暗号化されたこのデータの流れだけです。

Rooms:サーバーが引き合わせるのは参加者であり、文書ではありません

DocuChan Rooms は共同編集ができる文書エディタですが、文書をサーバーに預かるサービスではありません。ルームには、互いに独立した二つの権限があります。参加者同士が落ち合うために使う、公開のランダムなルーム ID と、招待リンクの #key= の後ろに入っている 256 ビットのルーム鍵です。ページはアプリケーションが起動する前にこのフラグメントを取り出してアドレスバーから消去し、Rooms の API に送ることは一切ありません。アクセス解析も、Rooms 配下のすべてのページで無効にしています。

サーバーが保持するのは、ブラウザ同士が互いを見つけるための、短時間で消えるプレゼンス情報と、件数に上限のある WebRTC のシグナリング記録だけです。文書の内容、Markdown、コメント、選択範囲、埋め込み画像、Mermaid のソースは、暗号化された WebRTC のデータチャネルを通じて参加者のブラウザ間でやり取りされます。アプリケーションのプロトコルはルーム鍵を認証し、HKDF で導出した AES-256-GCM 鍵で独自にメッセージを暗号化します。そのため、シグナリングサービスは文書の更新内容を読むことができず、気づかれずに改変することもできません。

ルームの作成者には、ルームのロックと入室の可否の判断だけに使う、もう一つのランダムな権限が与えられます。この権限は作成者のブラウザのサイトストレージにとどまり、招待用や再開用の URL に含まれることはありません。サーバーが保存するのは、その SHA-256 ハッシュだけです。ロックされていないルームでは、有効な参加リクエストはすぐに受け入れられます。ロックされたルームでは、オンライン中のホストが承認または拒否するまで、リクエストは保留されます。ロックの状態、ハッシュ化された参加者の権限、入室の状態、自己申告の名前、タイムスタンプは、短期間だけ保持されるサーバー上のメタデータです。それでも、これらに文書の鍵や文書の内容が含まれることはありません。

ただし、利用者自身の端末の上では、復元機能のためにこの保存の境界が変わります。Rooms は、そのブラウザの IndexedDB に平文の Yjs 文書を保持し、誤ってタブを閉じても復元できるよう、ルーム鍵をローカルストレージに保存することがあります。Rooms のロビーでは、個々のローカルコピーを削除することも、このブラウザが把握しているすべての Rooms のデータベースと復元用の鍵をまとめて消去することもできます。サーバー側のバックアップはなく、利用者が失った鍵を DocuChan が復元することはできません。

Clipboard:直接つながらなければ送りません

Clipboard は、テキストや PNG 画像を自分の端末同士で受け渡す機能です。Beam よりもさらに厳格で、ブラウザが公開 STUN サーバーを使うのは WebRTC の直接経路を見つけるためだけです。Clipboard が TURN による中継に切り替えることはなく、直接接続が確立できなければ、ほかの誰かを経由する代わりに転送そのものが失敗します。サーバーに見えるのは、短時間で自動的に失効するプレゼンス情報とシグナリングの記録だけです。各項目は、二つのブラウザが合意した鍵を使ってアプリケーション層でも重ねて暗号化され、Beam と同じように、両方の画面で短いコードを照らし合わせます。受け取った項目はそのタブのメモリ上にだけ(最大で直近の 20 件まで)保持され、タブを閉じると消えます。

PDFツール:ファイルはタブの中にとどまります

PDFツールは、文書を開く・変更する・保存する処理をすべてブラウザーのタブの中で行います。動くのは、私たち自身のコードと自前で配信するPDF.jsのワーカーだけです。ツールのページには、connect-src が 'none'(アナリティクスを使う環境では、ページビュー1件を受け取るエンドポイントのみ)のContent-Security-Policyを付けて配信しているため、ページそのものが文書をどこかへ送ることはできません。

文字認識(OCR)には、ページに含まれていないものが2つ必要です。認識エンジン(約1.5 MB)と言語モデル(0.8〜3.1 MB)で、合計約2.5〜4.5 MBです。どちらもCDNではなく私たち自身のオリジンから配信します。これらを取得するのは専用のワーカーで、そのワーカー自身のポリシーには /ocr/ 以下の6つの言語ファイルとエンジンのスクリプトだけが記されており、それ以外には接続できません。ワーカーが受け取るのはページの画像で、認識した文字はお使いのデバイス上でPDFに書き込まれます。

PDFをWordに変換するツールも同じ仕組みです。ページの読み取りと.docxの作成はタブの中で行い、Word用のライブラリはこのツールでだけ読み込みます。

注釈と「隠して置き換え」エディターも、すべてタブの中で動作します。エディターは何をするかを正直に示します。隠した文字はファイル内に残り、文字を完全に消せるのは墨消しだけです。

私たちには守れないもの

限界を一つも書いていないセキュリティページは、ただの宣伝ページです。私たちの限界は次のとおりです。

  • 私たちが配信する JavaScript を信頼していただく必要があります。暗号化の処理は、ページを読み込むたびに私たちのサーバーから届くコードの中で動いています。私たちが悪意を持った場合や、強制された場合、あるいは侵害された場合には、そのコードが書き換えられるおそれがあります。これはブラウザ上で動くすべてのエンドツーエンド暗号化ツールに共通する話で、暗号化メッセンジャーの Web 版も例外ではありません。どこかのコメント欄で指摘される前に、自分たちからここで明言しておきます。検証のためにクライアントのソースコードを公開することを真剣に検討しており、それが誠実な緩和策だと考えています。
  • 第三者による監査はまだ受けていません。設計は十分に研究された構成(Firefox Send のフラグメント方式や、ZRTP 型の SAS)に沿っていますが、この実装を独立した第三者が監査したことはまだありません。監査を受けたときには、その報告書へのリンクをここに掲載します。
  • リンクそのものが秘密です。完全なリンク(パスワードを設定した場合はパスワードも)を持っている人なら、誰でもファイルを復号できます。リンクは信頼できる経路で共有し、「ダウンロード後に削除」を使って、漏れたリンクが最初の利用後には無効になるようにしてください。
  • あなたの端末は対象外です。マルウェアや、肩越しに画面をとらえるカメラの前では、私たちのものを含め、どんな暗号化も意味をなしません。
  • 鍵を失えば、ファイルも失われます。リンクの鍵を私たちが復旧したり、リセットしたり、作り直したりすることはできません。そもそも私たちは、その鍵を一度も持っていないからです。これは設計どおりの動作ですが、「パスワードをお忘れの方」のような手続きに慣れた方には、意外に感じられるかもしれません。

セキュリティ上の穴を見つけたら、教えてください

脆弱性を見つけたら、ぜひ教えてください。これは社交辞令ではありません。厳しい目で検証してもらうことこそ、このページを公開している理由です。件名に「Security」と入れて [email protected] までメールでお知らせください(security.txt にも記載しています)。いただいた報告にはすべて目を通して返信し、希望される発見者の方のお名前は謝辞として掲載します。なお、他人のファイルを対象にしたテストはご遠慮ください。

— DocuChan の開発者、Beki · もっとやさしい解説 · 実際のファイルで試す

DocuChan のセキュリティ:暗号化の実際の仕組み