PlainApp セキュリティ詳細ガイド
PlainApp がローカル Wi-Fi 上でどのようにデータを保護しているか — オープンソースコードに基づく解説です。
なぜローカルネットワークのセキュリティが重要なのか?
PC のブラウザから Android スマートフォンを管理するアプリを使うと、SMS、写真、連絡先、ファイルが Wi-Fi ネットワーク上を流れます。多くのアプリはこれらすべてを読み取り可能な平文で送信します。同じネットワーク上にいる誰もがその内容を見ることができます。
「自宅の Wi-Fi だから誰も覗かないだろう」と思うかもしれません。しかし、以下のケースを考えてみてください:
- 公衆 Wi-Fi — カフェ、空港、ホテル — 近くにいる誰でも通信を傍受できます
- 共有ネットワーク — ルームメイト、ゲスト、または侵害された IoT デバイスが存在する環境です
- ルーターの脆弱性 — 既知の脆弱性を含む古いファームウェアが使われている場合があります
アプリが SMS メッセージを HTTP で平文のまま送信していれば、Wireshark のような無料ツールを使って誰でも内容を読めます。これは理論上の話ではなく、極めて簡単に実行可能です。
ほとんどのスマートフォン管理アプリがまさにこれをしています。Web UI を使用中に Chrome DevTools(F12 → Network タブ)を開けば、すべての API 呼び出しが平文の JSON で読めます — SMS の本文、ファイルパス、連絡先の名前、すべてです。
PlainApp はどこが違うのか
PlainApp は暗号化と認証を複数のレイヤーで適用しています。各レイヤーの役割をわかりやすく説明します。
GraphQL API — 必要なデータだけを取得
多くのアプリは従来の REST API を使用しており、リソースごとに URL があり、必要かどうかに関わらずすべてのデータを返します。PlainApp は GraphQL を採用しています。これはブラウザが必要なデータだけを正確にリクエストできる、モダンなアプローチです。
これが重要な理由:
- データ露出の最小化 — ファイル名だけが必要な場合、送信されるのはファイル名だけです
- エンドポイントが一つだけ — 何十もの URL ではなく単一の /graphql URL を使用するため、攻撃対象が減ります
- クエリごとの権限チェック — 各クエリは個別の権限を確認します(例:ログイン後でも SMS の読み取りには READ_SMS 権限が必要です)
暗号化された API 通信(XChaCha20-Poly1305)
これが PlainApp と他の多くの競合アプリとの最大の違いです。
すべての API リクエストとレスポンス——HTTP と WebSocket の両方の通信——は、HTTP モードでも HTTPS モードでも XChaCha20-Poly1305 で暗号化されます。これは WireGuard VPN、Cloudflare、Google の Tink ライブラリで使用されている最新の AEAD 暗号化アルゴリズムです。
わかりやすく言うと:
- 1ブラウザがリクエストを作成します(例:「SMS を見せて」)
- 2リプレイ攻撃防止のためにタイムスタンプとランダムな nonce を付加します
- 3XChaCha20-Poly1305 でペイロード全体を読み取り不能なバイナリデータに暗号化します
- 4暗号化されたバイナリがそのままネットワーク上を流れます——base64 変換なし、JSON なし、純粋なバイナリです
- 5あなたのスマートフォンだけが復号・検証できます
誰かが通信を傍受しても、見えるのは意味のないバイナリデータであり、読み取り可能な JSON ではありません。
暗号化の詳細:
- 256 ビット鍵(極めて強力です)
- 192 ビット nonce(他の暗号方式に影響する特定の攻撃を防ぎます)
- 改ざん検出機能内蔵 — 転送中にデータが変更されると復号が失敗します
- Android 側:Google Tink を使用
- Web 側:@noble/ciphers(十分に監査された JavaScript 暗号ライブラリ)を使用
デジタル署名(Ed25519)——デバイス間通信
2 台の PlainApp デバイスが通信するとき(例:スマートフォン同士のチャット)、暗号化だけでは不十分です——各デバイスはメッセージが本当に相手のデバイスから来たものであり、偽造されていないことを検証する必要があります。
タイムスタンプ
受信側デバイスは送信者の公開鍵で署名を検証し、タイムスタンプが 5 分以上前のリクエストを拒否します。これにより偽造とリプレイ攻撃の両方を防止します。
安全なデバイスペアリング(ECDH 鍵交換)
2 台の PlainApp デバイスがペアリングするとき(例:スマートフォン 2 台)、共有暗号鍵が必要になります。しかし、鍵をネットワーク経由で送信しては意味がありません。
PlainApp はこの問題を ECDH(Elliptic Curve Diffie-Hellman)鍵交換で解決します — TLS、Apple の Secure Enclave、WebAuthn で使われているのと同じ数学的手法です:
- 1各デバイスが鍵ペア(公開鍵 + 秘密鍵)を生成します
- 2公開鍵のみを交換します
- 3各デバイスが自分の秘密鍵と相手の公開鍵を組み合わせます
- 4双方が同じ共有シークレットに到達します — ネットワーク上に共有シークレットが流れることはありません
暗号化された URL — ファイルパスの隠蔽
多くの人が気づかない点があります。URL パスも情報を漏洩するのです。
ファイルをダウンロードする際、URL はサーバーにどのファイルが必要かを伝えます。多くのアプリでは URL が以下のように表示されます:
/fs?path=/sdcard/DCIM/Camera/photo_2024.jpg通信を傍受している人(またはブラウザの履歴を見た人)は、アクセスしたファイルとスマートフォンのフォルダ構成を正確に把握できます。
PlainApp は URL 内のファイルパスを暗号化します:
/fs?id=AxK9f2mQ7vR3xP8nWz...id パラメータは、URL Token と呼ばれるランダムキーを使って XChaCha20-Poly1305 で暗号化されたファイルパスです。デフォルトではこのキーは永続的で、アプリを再起動しても変わりません。そのため、共有したリンクはそのまま使い続けられます。キーなしでは、実際のパスを推測したり、ファイルを列挙したり、ダウンロード URL を作成したりすることはできません。
自己署名証明書による HTTPS
PlainApp はスマートフォン上で自己署名の TLS 証明書を生成します。これにより暗号化された HTTPS トンネルが作られます — 銀行や Web サイトが使用しているのと同じ技術です。
証明書の仕様:
- ECDSA(P-256 曲線を使用、主要な Web サイトと同等です)
- SHA256 署名
- アプリのプライベートディレクトリに保存されます
アプリケーション層の XChaCha20 暗号化と組み合わせることで、2 つの独立した暗号化レイヤーが実現されます。一方を突破しても、もう一方を突破しなければ意味がありません。
ログイン保護
PlainApp はパスワード推測を防ぐためにログイン試行回数を制限しています:
- IP アドレスあたり 60 秒間に 5 回まで
- 制限を超えると接続が拒否されます
- 古いレートリミットの記録は自動的にクリーンアップされます
HTTP と HTTPS:正直な比較
PlainApp は HTTP モードと HTTPS モードの両方を提供しています。その違いを正直に解説します:
| HTTP モード | HTTPS モード | |
|---|---|---|
| ファイルパス URL | 暗号化済み(XChaCha20) | 暗号化済み(XChaCha20) |
| API 呼び出し | 暗号化済み(XChaCha20) | 暗号化済み(XChaCha20 + TLS) |
| ファイルダウンロード | 転送中は暗号化されません | TLS により暗号化済み |
| 対象ユーザー | 初心者向け(ブラウザの SSL 警告なし) | プライバシーを重視するユーザー向け |
どちらのモードでも API 呼び出しは XChaCha20-Poly1305 で暗号化されます — HTTP でも HTTPS でも、SMS・連絡先・その他のデータは常に暗号化されています。
HTTP モードが存在するのは、自己署名証明書がブラウザに「この接続ではプライバシーが保護されません」という警告を表示させるためです。技術に詳しくないユーザーにとって、この警告は混乱を招きます。HTTP ならこの問題を避けられます。
ただし、HTTP モードではファイル転送が暗号化されません。共有ネットワークや公衆ネットワークでは、必ず HTTPS を使用してください。
推奨:可能な限り HTTPS を使用してください。ブラウザの自己署名証明書の警告は、ローカルネットワークアプリでは正常かつ想定どおりのものです。
サプライチェーンセキュリティ — 依存関係を減らしてリスクを低減
多くのユーザーが見落としているセキュリティの脅威があります。それがサプライチェーン攻撃です。アプリが何百ものサードパーティライブラリを使用していると、そのどれか一つが侵害される可能性があります — データの窃取、暗号通貨のマイニング、バックドアの設置といった悪意のあるコードが注入されるかもしれません。
これは JavaScript エコシステムにおいて特に危険です。近年、event-stream、ua-parser-js、colors といった人気の npm パッケージが乗っ取られ、何百万ものプロジェクトに悪意のあるコードが注入されました。スマートフォン管理アプリの Web UI が何百もの JS パッケージを読み込んでいる場合、それぞれが潜在的な攻撃経路になります。
PlainApp は意図的なアプローチをとっています:依存関係を可能な限り少なくし、合理的な場合には独自コードを実装します。
Android アプリ(plain-app)
- DI フレームワーク不使用(Hilt も Dagger もなし)— シンプルなシングルトンを使用しています
- 暗号処理は、Google Tink、Bouncy Castle、組み込みの Java Cryptography Extension の 3 つの著名なライブラリのみで実現しています
- サーバーは Ktor(JetBrains 公式の Kotlin フレームワーク)で動作します — 不明瞭なサードパーティのサーバーライブラリは使用していません
Web UI(plain-desktop)
ランタイム依存関係はわずか 28 個 — 一般的な Web アプリと比べてはるかに少ないです。通常であれば npm パッケージが必要になるいくつかの機能を、ゼロから構築しています:
| 機能 | 一般的なアプリ | PlainApp |
|---|---|---|
| 通知音 | howler.js / tone.js | 独自の Web Audio API シンセサイザー |
| UUID 生成 | uuid npm パッケージ | 独自の RFC 4122 実装 |
| ファイルアップロード | uppy / axios | 独自のチャンク分割アップロードとキュー |
| WebRTC ビデオ | Twilio / Daily.co SDK | 独自の PeerConnection コード |
| HTTP クライアント | axios / ky | ブラウザネイティブの fetch |
Web 側の暗号処理には @noble/ciphers を使用しています — 十分に監査された、依存関係ゼロの JavaScript 暗号ライブラリであり、大規模で肥大化した代替ライブラリの代わりとなります。
なぜこれが重要なのか
追加するすべての依存関係は、自分がコントロールできないコードです。PlainApp のアプローチがもたらす効果:
- 悪意のあるコードが潜む場所が少なくなります
- 攻撃対象が縮小されます
- 監査が容易です(コードベース全体がオープンソースです)
- 放棄された、または乗っ取られた npm パッケージのリスクがありません
多くの競合アプリが見落としていること
特定のアプリ名を挙げるものではありません。このカテゴリで共通して見られるパターンについてです:
| セキュリティの観点 | PlainApp | 競合アプリに多い状況 |
|---|---|---|
| API 通信 | 暗号化済み(XChaCha20)HTTP でも HTTPS でも | HTTP 上の平文 JSON |
| WebSocket 通信 | 暗号化バイナリフレーム(XChaCha20) | プレーンテキスト JSON または WebSocket なし |
| 認証トークンの扱い | 暗号化キーとして使用、ヘッダーには含めない | HTTP ヘッダーで送信(漏洩しやすい) |
| ファイル URL | 暗号化されたパス ID | 平文のパスが露出 |
| 鍵交換 | ECDH(P-256) | なし |
| リクエスト署名 | Ed25519(デバイス間のみ) | なし |
| リプレイ防止 | 30 秒 + nonce(Web)、5 分 + 署名(デバイス間) | なし |
| ログイン保護 | レート制限 + 二要素確認 | 無制限 |
| データ経路 | 100% ローカル、クラウド不使用 | クラウド経由のものもあります |
| 依存関係 | 最小限の著名なライブラリ | 何百もの npm パッケージ |
| ソースコード | オープンソース(GPL-3.0) | ほとんどがクローズドソース |
どのアプリでも簡単にチェックできます:アプリの使用中に Chrome DevTools(F12)→ Network タブを開いてください。API レスポンスが JSON で読めるなら、そのアプリには暗号化がありません。
自動化・追跡可能・改ざん耐性のあるリリースパイプライン
すべてのインストールパッケージは GitHub と F-Droid の基盤で自動ビルドされます。ビルドは追跡可能で安全、手動介入の痕跡を残しません。PlainApp は検証可能ビルド(SLSA)と VirusTotal スキャン済みリリースの両方を提供する数少ない Android アプリです。
GitHub と F-Droid での自動ビルド
リリース成果物はローカル手動パッケージングではなく CI パイプラインで生成されます。各成果物はソースコミットと再現可能ビルドワークフローまで追跡できます。
SLSA Provenance(レベル3)+ VirusTotal スキャン
各リリースには SLSA provenance 証明と VirusTotal スキャン結果が含まれ、サプライチェーン監査性が向上し、インストール前の完全性検証が可能になります。
よくある質問
HTTP モードでも PlainApp は本当に暗号化されていますか?
はい。すべての API 呼び出し(SMS、連絡先、ファイル一覧など)は HTTP モードでも HTTPS モードでも XChaCha20-Poly1305 で暗号化されます。HTTP モードで暗号化されないのはファイルダウンロードのみです。完全な保護が必要な場合は HTTPS を使用してください。
ブラウザにセキュリティ警告が表示されるのはなぜですか?
PlainApp は暗号化のために自己署名の TLS 証明書を使用しています。公的な認証局が発行した証明書ではないため、ブラウザが警告を表示します。これはローカルネットワークアプリでは正常かつ想定どおりの動作です。「詳細設定」→「続行」をクリックして進んでください。
同じ Wi-Fi 上の人に自分のデータが見られることはありますか?
PlainApp を使用していれば、見られることはありません。API 呼び出しは XChaCha20-Poly1305 で暗号化されており、HTTPS モードではファイル転送も TLS で暗号化されます。しかし多くの競合アプリでは、API 呼び出しが平文の JSON で送信されるため、見られる可能性があります。
PlainApp は VPN と比べてどうですか?
VPN はデバイスと VPN サーバー間の通信を暗号化します。PlainApp はローカルネットワーク上のスマートフォンとブラウザ間の通信を暗号化します。目的が異なります — PlainApp はローカルのデバイス管理を保護し、VPN はインターネット通信を保護します。
コードは本当にオープンソースですか?
はい。PlainApp は GPL-3.0 ライセンスで提供されています。完全なソースコードは github.com/plainhub/plain-app で公開されています。すべての暗号化実装を確認したり、ソースからビルドしたり、開発に貢献したりできます。