こんにちは!2ちゃんねる互換掲示板システム「 つゔぁいちゃんねる(2ch.biz) 」の開発録、第34回です。
前回(第33回)は、欲しい人だけがダウンロードできる「オンデマンドPull型ファイル転送」により、WebRTC特有の帯域爆発とメモリ枯渇問題を克服しました。
しかし、誰でも自由にP2Pでファイルを送り合えるようになったことで、次に私たちが直面したのは 「セキュリティと可用性の決定的な危機」 でした。
-
悪意あるファイル改ざんや破損 :
- 悪意を持ったピアが「最新ツールのzipだよ」と言って中身をウイルスや不正スクリプトにすり替えて送信してきたらどう防ぐのか?
- 途中のパケット欠落で壊れた画像やバイナリを開いて端末がクラッシュしないか?
-
送信者が落ちたらダウンロードできない問題 :
- 巨大ファイルを共有してくれた送信者(Alice)が部屋を退席した瞬間、後から「私もその画像が欲しい」と思った人がダウンロードできなくなってしまう。
本稿では、BitTorrentの分散共有思想を取り入れ、 Web Crypto API による SHA-256 暗号学的ハッシュ検証 と 「分散シーディング(代理再配布)」 を組み込んだ鉄壁のP2Pファイルセキュリティについて解説します!
1. P2P環境における「信頼の欠如」を技術で解決する
クライアント-サーバー型のサービスであれば、サーバー側でウイルススキャンを実行したり、AWS S3等の高可用ストレージにファイルを保管できます。
しかし、完全サーバーレスP2Pでは 「相手の端末も通信経路も一切信用できない(Zero Trust)」 という前提に立たなければなりません。
2. 防壁①:SHA-256 ハッシュによる改変検知と自動ブロック
ファイル共有を行う際、送信者はまずブラウザの Web Crypto API を使用してファイル全体の SHA-256 ハッシュ値を計算します。このハッシュ値はメタデータ(file_offer)に埋め込まれ、あらかじめ全ピアにブロードキャストされます。
受信完了時の厳密照合フロー
実装コード:Web Crypto API によるハッシュ照合
/**
* ArrayBuffer から SHA-256 ハッシュ文字列(16進数)を計算
*/
async function calculateSHA256(arrayBuffer) {
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
* ファイルチャンク結合と整合性検証ハンドラ
*/
async function finalizeFileAssembly(fileId, assembledBuffer, expectedHash) {
// 1. 受信したバイナリから SHA-256 を計算
const calculatedHash = await calculateSHA256(assembledBuffer);
// 2. メタデータのハッシュと厳格に比較
if (calculatedHash.toLowerCase() !== expectedHash.toLowerCase()) {
console.error(`[SECURITY ALERT] ハッシュ不一致!改変または破損を検知: expected=${expectedHash}, got=${calculatedHash}`);
// 危険なバイナリデータを即時破棄
assembledBuffer = null;
// UIに赤色警告を表示し、ダウンロードを物理的にブロック
markFileAsTampered(fileId);
showChatNotice('🚨 受信ファイルの改変または破損を検知したため、ダウンロードをブロックしました。');
return;
}
// 3. 検証成功:安全なファイルとしてBlob化
const fileBlob = new Blob([assembledBuffer]);
markFileAsVerified(fileId, fileBlob);
// 4. 自分自身を二次シーダー(配布可能者)として登録!
registerAsSecondarySeeder(fileId, fileBlob);
}
3. 防壁②:BitTorrent流「分散シーディング(再配布)」
ハッシュ検証にパスした安全なファイルを受け取ったBobは、そのファイルをブラウザメモリ内の sharedFiles マップに登録し、自動的に 「二次シーダー(配布者)」 へと昇格します。
【分散シーディングの恩恵】
1. Alice が「東京旅行の写真まとめ.zip」を共有。
2. Bob がダウンロード完了し、ハッシュ検証に合格(Bobがシーダーになる)。
3. Alice が「おやすみ〜」と言ってブラウザを閉じて退室。
4. 後から Carol が「その写真見たい!」とダウンロードボタンを押す。
5. Alice はもういないが、Bob が代理で Carol へP2Pストリーミング送信!
// 二次シーダー登録ロジック
function registerAsSecondarySeeder(fileId, fileBlob) {
// 正常性が保証されたファイルのみを再配布リストへ追加
this.sharedFiles.set(fileId, fileBlob);
// 全ピアへ「私もこのファイルを持っています」とアナウンス
broadcastPacket({
type: 'file_reseed_announce',
fileId: fileId,
seederPeerId: myPeerId
});
}
これにより、送信者が退席しても、部屋に1人でもファイルを受信した人が残っていればファイル共有が永続化されるという、驚異的な耐障害性を獲得しました。
4. UI/UX:視覚的に安心を届けるステータス表示
ユーザーが危険なファイルと安全なファイルを一目で識別できるよう、UIデザインにも明確なメリハリをつけました。
① 「🛡️ 検証済」グリーンバッジ
ハッシュ検証をクリアしたファイルには、鮮やかなエメラルドグリーンの「🛡️ 検証済 (SHA-256)」バッジが輝き、安心して保存できることを保証します。
② 「🚨 改変・破損検知」レッド警告ボックス
1バイトでもデータがすり替えられていた場合、ダウンロードボタンは完全に消滅し、太字の赤枠ボックスで 「🚨 改変・破損検知 (ダウンロードはブロックされました)」 と警告が表示されます。誤クリックによる被害を 100% 根絶します。
5. 自動改変注入テストによる検証
単体テストスクリプトを用いて、あえて転送中のチャンクの1バイトを故意に書き換える改ざん攻撃シミュレーションを実施しました。
| テストシナリオ | 条件 | 期待される動作 | 実行結果 |
|---|---|---|---|
| 正常系転送 | 無改変のバイナリ転送 | SHA-256一致、保存ダイアログ発火 | ✅ PASS (検証成功) |
| 中間改ざん攻撃 | チャンク内の1バイトを反転 | 即時破棄、警告UI表示、DLブロック | ✅ PASS (完全遮断) |
| 送信者退席シナリオ | 送信者が切断後、第3者が要求 | 二次シーダーから代理転送成功 | ✅ PASS (再配布成功) |
6. まとめと次回予告
Web Crypto API の SHA-256 による暗号学的検証と、BitTorrent流の分散シーディングにより、P2Pチャットのファイル共有は「安全」かつ「落ちない」強靭なシステムへと進化しました。
しかし、PCでの開発・検証が順調に進む一方で、スマートフォン(iOS Safari / Android Chrome)の実機でテストしていたチームから、思わぬ 「スマホ特有のUI崩壊」 が報告されました。
「スマホで文字を入力しようとすると、ソフトウェアキーボードが下からニョキッと競り上がってきて、 メッセージ入力欄が画面外に隠れて見えなくなる! 」
「キーボードが開くたびに画面全体のスクロール位置が激しくガタつく!」
ネイティブアプリなら当たり前の「キーボードにぴったり吸い付く入力バー」を、ブラウザのWeb技術だけでどう実現するのか?
次回は、 【第035回】スマホのキーボードで入力欄が隠れる!仮想キーボード追従とスクロール最適化 をお届けします。お楽しみに!
本番サービス : https://2ch.biz/