はじめに
企画書・仕様書・技術検討をQiitaにて一般に公開してみる試みです。
企画書なんかの書き方の参考になれば幸いです。
問題点やフィードバックもいただけると嬉しいです。
ファイル共有サービスを実装する
ファイル共有サービスを作成していきます。
このサービスの基本的な要求は以下の通りです。
- プライバシーを重視して簡単にファイルを共有したい
以上になります
具体的なユースケースとしては、
- スマホで撮影した写真を手軽にパソコンに移す
- 重要なデータを安全に共有する
このようなものを考えます。
では最初にこのサービスの名前とプロジェクト名を考えます。
サービスの名前はGeminiと相談した結果、「Anzdrop」に決定しました。
「アンズドロップ」と言います。センスないと思います。
また、プロジェクト名はそのままAnzdropにします。
「Anzdrop」の要件定義
Anzdropに必要な機能を検討します。
- ファイルを暗号化してアップロードできる
- 特定のURLにアクセスするとファイルをダウンロードできる
- 7日経過すると自動でファイルを削除する
- アップロード時にウイルススキャンを行う
これらがマストで必要な機能です
これらの要件以外にも色々出てきたり、追加したくなったり、
消したくなったりすると思いますが以上は基礎的な要件です。
技術選定
プライバシーの重視と手軽さを両立するため以下の技術スタックを採用します
| カテゴリ | 選定技術 | 理由 |
|---|---|---|
| フロントエンド | Next.js | AppRouterによる効率的なルーティングと、動的OGPを導入したいため採用。 |
| バックエンドAPI | Hono | CloudflareWorkers上で動作する高速かつ軽量なフレームワーク。hono/clientによるRPCにより、開発スピードと品質を両立。 |
| ストレージ | CloudflareR2 | AWSS3互換のオブジェクトストレージ。データ転送量が無料のため、ファイル共有サービスの運用コストを劇的に抑えられるため採用。 |
| データベース | CloudflareD1 | Cloudflareのエッジで動作するSQLiteベースのサーバーレスDB。操作にはPrismaを利用する |
| セキュリティ | WebCryptoAPI | サーバーへ送信する前にクライアント側で暗号化を行うことで、運営側ですら中身を閲覧できない高いプライバシーを実現。 |
以上のような技術スタックを採用します。
Cloudflareのサービスを利用するのは初めてのため、
適宜ぐぐりながら開発を進めていきます。
データの流れと暗号化フロー
「運営すら中身を見ることができない」仕組みを実現するために、
エンドツーエンドでの暗号化(EE2E)を採用します。
アップロード時のフロー
- ユーザーがファイルを選択する
- ブラウザ(WebCryptoAPI)で一時的な対象鍵を生成
- そのカギでファイルを暗号化
- 暗号化されたファイルをサーバーへ送信
- 復号用のカギはURLのフラグメントに含める
以上の仕組みにて実装していきます。
WebCryptoAPIも触るのは初めてなので、
こちらもぐぐりながら進めていきたいと考えています。
API設計
Honoで実装する主なエンドポイントの設計案です。
ネイティブアプリの開発も考えているので再利用可能な仕組みにしていきます。
| メソッド | パス | 概要 |
|---|---|---|
| POST | /api/upload | ファイルのアップロードとメタデータの登録 |
| GET | /api/file/:id | ファイルのメタデータ取得 |
| GET | /api/download/:id | R2から暗号化済みファイル本体をダウンロード |
| POST | /api/cleanup | 期限切れのファイルを削除する(定期実行) |
以上のような構成です。
もちろん今後必要になるAPIも増えてくるとは思いますが一旦はこちらでOKです
ウイルススキャンの実装方針
こちらが今回のアプリケーション最大の難所になります。
クライアントサイドで暗号化を行ってしまうと、サーバー側で従来のウイルススキャンを行ってもただのランダムなデータとして扱われ、検知が不可能になるからです。
私なりに調査した内容に基づきますが、もし認識に誤りがあればコメント等でご指摘いただけると幸いです。
このジレンマを解決するため、以下の3つのアプローチを検討しました。
- ウイルススキャン機能を要件から外す
- サーバーでスキャンを行ってから暗号化する
- 暗号化を行う前にブラウザ側でスキャンする
以上の3つがあると思います。
一つずつ検討していきましょう。
1.スキャン機能を要件から外す
これは、本アプリと設計思想が近いメッセンジャーアプリ「Signal」などが採用している方針です。
Signalは徹底したプライバシー保護のため、運営側が中身に介入できる機能を一切排除しています。
そのため、
Signal使うならさ、送信者当てにしちゃだめじゃない?
自己防衛、ダウンロード後のウイルススキャン、
あとセキュリティソフトの導入、LINE脱出だよね
だから運営なんかあてにしちゃだめよ
あてにするから文句が出るわけでしょ?
といったスタンスのようです。
しかし、Signalはメッセージのやり取りが主体であるのに対し、Anzdropは「ファイルの共有」そのものが主目的です。悪意のあるソフトウェアの配布に利用されるリスクを考えると、この案をそのまま採用するのは躊躇われました。
2.サーバーでスキャンを行ってから暗号化する
一度ファイルを平文(生のデータ)のままサーバーへ送り、スキャンを通してからサーバー側で暗号化・保存する方法です。
しかし、これでは「運営者すらファイルの中身を閲覧できない」というAnzdrop最大のコンセプトが崩壊してしまいます。そのため、この案はボツとなりました。
3.暗号化を行う前にブラウザでスキャンする(ハッシュ照合)
安全性とプライバシーを両立させる折衷案です。
ファイル本体ではなく、ブラウザ側で計算した「ファイルのハッシュ値」のみを外部のウイルススキャンAPI(VirusTotalなど)に送り、過去に報告されたマルウェアと一致するかを確認する手法です。
一見素晴らしい案のように感じますが以下のデメリットもあります。
それはファイルそのものを持っていることの証明にはなってしまうことです。
外部のAPIに送る以上はそのハッシュ値を外部の人が知ることできてしまいますが、
その運営者に悪意があったり、ハッキングされていたりすると以下のようなことが起こります
- 運営者側が政府の機密文書や特定の画像のハッシュ値をあらかじめ持っておく
- ユーザーが送信したハッシュ値と一致するかどうかを調べる
- 「このユーザーはこの機密文書を送信した」という事実がばれてしまう
以上のようなことが起きてしまう可能性があります。
もちろんAnzdropが政府の機密文書を送るようなことに使われることはないと思いますが。
結論
めっちゃ悩みました。3日くらい悩みました。
結果現時点では「ウイルススキャン機能を見送る」という判断になりました。
中途半端にプライバシーを犠牲にして50点の安全をとるのではなく、
プライバシーにおいて100点(完全なE2EEの実現)を取ることを優先したいと考えたからです。
また、ファイルのハッシュスキャンの回避はやろうと思えばいくらでもできてしまうので
そのためにわざわざ中途半端な選択肢を取りたくないという思いもあります。
もちろん公開後に悪用が目立つようであれば通報ボタンを設置するなど改めて対策を検討しますが、
まずは「誰も中身をのぞけない」という自分が求めているものを追求します。
データベース設計
最後にPrismaを使用して管理する、メインとなるFileテーブルのスキーマ案を考えます。
model File {
id String @id @default(cuid())
fileName String // 暗号化されたファイル名、または非秘匿のファイル名
fileSize Int
mimeType String
storageKey String // R2上のオブジェクトキー
virusStatus String @default("pending") // pending, clean, infected
createdAt DateTime @default(now())
expiresAt DateTime // createdAt + 7 days
}
こんな感じですかね。
まとめ
以上、Anzdropの要件定義と技術選定、
そして最大の課題であったウイルススキャンへの方針をまとめました。
当初、盛り込む予定だったウイルススキャンを、E2EEというプライバシーの追求と、ハッシュスキャンの限界という技術的現実を天秤にかけた結果、あえて見送るという決断に至るまでの道のりは、
私自身にとっても大きな学びとなりました。
「中途半端な妥協はせず、100点のプライバシーを目指す」
この方針を軸に、実際の開発を進めていきます。
次回の予定
技術的なアーキテクチャは固まりましたが
このようなサービスを一般公開するにあたっては避けて通れないがいくつかあります
- 収益化:R2の転送量は無料だけどストレージ容量が増えればコストがかさむため、維持管理はどうするか
- 法的リスク:違法アップロード等にはどう対処すればよいか、利用規約はどう定めるか
次回はここら辺を考えていきたいと思います。
もちろん何も考えずに突っ走った方が多分完成するスピードは速いのですが
急に家に警察がご来館なさっちゃったーとかになると困るので慎重に考えていきます。
以上です。