ぽっぺんしゃんにょろりんこ

匿名・非追跡型アダルト動画検索エンジンの設計ノート

個人情報を追跡せずにできる!CryptJSでボットクリックからDBを守る方法

こんにちは、にょろりんこの備忘録的技術ブログです。

SaePornsでは、動画の人気度や関連ワードの精度を上げるために、CTR(クリック率)を計測しています。

これは「特定の検索語で表示された動画のうち、実際にどれがクリックされたか」を記録するもので、ランキングやレコメンドロジックの基盤となる重要なデータです。

しかし、SaePornsは非追跡型の設計を徹底しており、ユーザーのIPアドレスやブラウザ指紋といった個人情報は一切保存していません

この方針はプライバシー保護の観点では正しいものの、その一方でボットや自動スクリプトによる不正なクリックを検出しづらいという課題も抱えていました。

サングラスをするティアラ

では、個人情報を記録せずに、どうやって「正しいクリック」だけを記録すればいいのか?その答えの一つが、CryptoJSによる「クライアント署名付きクリック」の導入です。

そもそも署名付きCTRとは?(理論編)

「署名付きCTR」とは、クリックログをサーバーに送るときに、クライアント側で「署名」を付ける仕組みです。ここで言う「署名」とは、単なる文字列ではなく、「このクリックが正当な手続きを経て発生したものである」ことを証明するための暗号学的なチェックコード(HMACなど)のことを指します。

たとえば、以下のような構造です:

{
  video_id: 123456,
  word: "癒し系",
  sig: "3f4a8c...(HMAC署名)"
}

この sig の部分は、あらかじめ共有してある「秘密鍵」と、video_id や word などのパラメータを元にクライアント側で計算して送るものです。

他のどんなサイトでも起こり得る問題

これはSaePornsに限った話ではありません。どんなWebサイトでも、フロント部分を公開しているということは、それ自体が「操作可能なインターフェース」を晒していることになります。

つまり、誰かがブラウザの開発者ツールを使えば、クリックログ用のAPIエンドポイントも、送信されるパラメータの構造も、簡単に把握できてしまいます。その上で curlPython の requests ライブラリを使えば、架空のクリックを何万回と自動で送信することが理論的にはどんなサイトでも可能なのです。

だから、ちゃんとCTRとかを計測しようと思ったら、ボット対策はどのサイトにも必要になります。検索サイトでも、動画サービスでも、ECでも、ユーザー操作を記録して最適化するあらゆるサイトに共通して発生します。

そのため、多くのWebサービスでは、クリックログやイベントトラッキングに対するボット対策を、何らかの形で導入しているのです。

大手サイトの手法(GoogleYouTubeなど)

GoogleYouTubeのような個人情報収集を前提に扱うプラットフォームでは、以下のようにしてボット対策が可能です。

  • IPアドレスやブラウザ指紋で、同一人物を高精度にトラッキング
  • アカウント単位での行動分析(ログイン必須)
  • CTRログにタイミングやUI位置、操作履歴などを含め、「自然な動き」かどうかをAIで判定
  • 一部リクエストに署名トークン(JWT)やXSRFトークンなどを導入

つまり、「誰がクリックしたかを特定する前提で、精度を高める」方向の対策です。

しかし、SaePornsにはこの手法は使えない

一方、SaePornsは「匿名・非追跡」をポリシーに掲げているため、

  • IPアドレスは保存しない
  • ログイン機能もない
  • ブラウザの個体識別もしていない

つまり、Google的な「識別ありきの防御」は、設計思想として採用しておらず、SaePornsでは、このアプローチ自体が成立しない設計になっています。

SaePornsのやり方「事前署名によるクライアント検証」

そこでSaePornsでは、「本物のJavaScript実装だけが生成できる署名」を使ってクリックを認証するという、軽量なゼロトラスト方式を採用しています。

つまり、クリック時に「video_id」や「word」を使って クライアント側で HMAC 署名を生成し、サーバー側ではその署名が正しいかどうかだけを検証するという仕組みです。こうすることで、匿名性を保ちながらも「怪しいスクリプト」からの不正クリックを遮断できるのです。

それでは実際のコードを見ていきましょう。

index.html

<!-- ライブラリ読み込み -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

api540-select.php

※CTRを測定するapi

// JSON 形式で受信
$data = json_decode(file_get_contents('php://input'), true);
$videoID   = isset($data['video_id']) ? (int)$data['video_id'] : 0;
$word      = trim($data['word'] ?? '');
$timestamp = isset($data['timestamp']) ? (int)$data['timestamp'] : 0;
$token     = $data['token'] ?? '';

JSON形式のリクエストボディは php://input を通じてそのまま受け取り連想配列化しています。PHPにおける標準的なJSON APIの受信方法です。

// タイムスタンプ検証(5分以内)
if (abs(time() - $timestamp) > 300) {
    echo json_encode(['error' => 'request expired']);
    exit;
}

リプレイ攻撃トークン再利用)対策として、リクエスト発行時刻が5分以上ずれていたら拒否します。時間ベースの署名は、有効期間付きのワンタイムトークとしても機能します。

// トークン再計算(UA はサーバー側から取得)

$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$expected = hash('sha256', "$videoID:$word:$timestamp:$userAgent");

クライアントで生成された token と同じアルゴリズムで、サーバー側でも署名(HMACではなくSHA-256)を再計算しています。

User-Agent は NginxなどのWebサーバー経由で自動的に受け取れる標準ヘッダーで、ここでは照合の強化材料として一時使用しています。(個人情報にあたるデータは含まれていない。)

// トークン一致を確認(Bot対策)
if (!hash_equals($expected, $token)) {
    echo json_encode(['error' => 'invalid token']);
    exit;
}

クライアントとサーバーの計算結果が一致しない場合は、スクリプトなどによる不正リクエストと判断して拒否します。hash_equals() を使うことで、タイミング攻撃(Timing Attack)への耐性も確保しています。

まとめ:非追跡でもCTRは十分に守れる

今回は、SaePornsで実装している「署名付きCTR」の仕組みを通じて、匿名性を保ちながらもボットによるクリック汚染を防ぐ方法を紹介しました。

改めて仕組みの概要を振り返ると…

  1. CTRの記録はAPI経由で行われるが、APIは誰でも叩けてしまう
  2. そのままではボットが curl などで何度でも偽クリックを送れてしまう
  3. そこで、クライアント側で video_id・word・timestamp・User-Agent を使ってトークンを生成
  4. サーバー側でも同じロジックでトークンを再計算して、照合
  5. トークンが一致しなければ、CTRのカウントを拒否

という流れになっており、これにより、JavaScriptからの操作だけを抽出し信頼できるCTRを作成するという仕組みになっています。

この手法の良いところは、IPアドレスCookieも使わずに、不正クリックを高精度に除外できるという点です。個人情報を一切保存しない設計でも、CTRの統計精度を維持できる、それが今回紹介した軽量なゼロトラスト方式の強みです。

SaePornsでは、今後も「匿名性」「データの信頼性」の両立を大切にしながら、サービスの改善を続けていきます。

それではみなさん、よい開発ライフを。

本文中に出てくるあなたを追跡しないアダルト動画の検索エンジンSae-Pornsはこちら。
※18歳未満の方はご利用いただけません。

sae-porns.org