🐘

PHPでワンタイムパスワードのリプレイ攻撃対策

2024/06/21に公開

使用済みのワンタイムパスワードを再利用できないようにするには、データベースやキャッシュに保存する方法が一般的です。以下に、PHPでデータベースとキャッシュを利用した具体的な対策例を紹介します。

データベースを利用する場合

  1. データベースにテーブルを作成し、以下のカラムを定義します。

    • user_id: ユーザーID
    • otp: ワンタイムパスワード
    • timestamp: ワンタイムパスワードの生成時刻
  2. ワンタイムパスワードを生成したら、データベースにレコードを挿入します。

<?php

$userId = 1; // ユーザーID
$otp = generateOTP(); // ワンタイムパスワードを生成
$timestamp = time(); // 現在時刻を取得

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$stmt = $db->prepare('INSERT INTO otps (user_id, otp, timestamp) VALUES (:user_id, :otp, :timestamp)');
$stmt->bindParam(':user_id', $userId);
$stmt->bindParam(':otp', $otp);
$stmt->bindParam(':timestamp', $timestamp);
$stmt->execute();

?>
  1. ワンタイムパスワードを検証する前に、データベースにレコードが存在するか確認します。
<?php

$userId = 1; // ユーザーID
$otp = '123456'; // 入力されたワンタイムパスワード
$timestamp = time(); // 現在時刻を取得

$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$stmt = $db->prepare('SELECT * FROM otps WHERE user_id = :user_id AND otp = :otp AND timestamp >= :timestamp - 30'); // 過去30秒以内に生成されたワンタイムパスワードのみ検証
$stmt->bindParam(':user_id', $userId);
$stmt->bindParam(':otp', $otp);
$stmt->bindParam(':timestamp', $timestamp);
$stmt->execute();

$result = $stmt->fetchAll();

if (count($result) > 0) {
  // ワンタイムパスワードが有効
  echo 'Valid OTP';

  // 使用済みのワンタイムパスワードを削除
  $stmt = $db->prepare('DELETE FROM otps WHERE user_id = :user_id AND otp = :otp');
  $stmt->bindParam(':user_id', $userId);
  $stmt->bindParam(':otp', $otp);
  $stmt->execute();
} else {
  // ワンタイムパスワードが無効
  echo 'Invalid OTP';
}

?>

キャッシュを利用する場合

  1. Memcached や Redis などのキャッシュサーバーをインストールします。

  2. ワンタイムパスワードを生成したら、キャッシュに保存します。

<?php

$userId = 1; // ユーザーID
$otp = generateOTP(); // ワンタイムパスワードを生成
$timestamp = time(); // 現在時刻を取得

$memcached = new Memcached();
$memcached->addServer('localhost', 11211);
$memcached->set($userId . ':' . $otp, $timestamp, 30); // 30秒間キャッシュする

?>
  1. ワンタイムパスワードを検証する前に、キャッシュにレコードが存在するか確認します。
<?php

$userId = 1; // ユーザーID
$otp = '123456'; // 入力されたワンタイムパスワード
$timestamp = time(); // 現在時刻を取得

$memcached = new Memcached();
$memcached->addServer('localhost', 11211);
$cachedTimestamp = $memcached->get($userId . ':' . $otp);

if ($cachedTimestamp !== false && $cachedTimestamp >= $timestamp - 30) {
  // ワンタイムパスワードが有効
  echo 'Valid OTP';

  // キャッシュからレコードを削除
  $memcached->delete($userId . ':' . $otp);
} else {
  // ワンタイムパスワードが無効
  echo 'Invalid OTP';
}

?>

その他の対策

  • ワンタイムパスワードの長さを十分に長くする (少なくとも 6 桁)
  • ワンタイムパスワードの有効期限を短くする (30 秒など)
  • ワンタイムパスワードを複数回入力できないようにする (例えば、入力画面に残り試行回数カウンターを表示する)

これらの対策を組み合わせることで、より安全な二段階認証システムを構築することができます。

Discussion