====== TOTP ====== /** * TOTP (Time-based One-Time Password) を計算するシンプルな関数 * * @param string $secret 共有シークレットキー(Base32でエンコードされていることが多い) * @param int $timeStep タイムステップ(通常30秒) * @param int $digits 生成するOTPの桁数(通常6桁または8桁) * @param string $algorithm ハッシュアルゴリズム(通常'sha1') * @return string 生成されたTOTP */ function generateTotp(string $secret, int $timeStep = 30, int $digits = 6, string $algorithm = 'sha1'): string { // シークレットキーがBase32でエンコードされている場合、デコードする必要がある // ここでは、シークレットキーがすでにバイナリ形式であることを前提とする // もしBase32でエンコードされたシークレットキーを扱う場合は、 // 以下のようなデコード関数を実装または利用する必要があります。 // 例: https://github.com/christian-riesen/base32 // シークレットキーをバイナリ形式に変換(もし必要なら) // 通常、提供されるシークレットキーはBase32なので、ここでデコードが必要です。 // 例: $binarySecret = Base32::decode($secret); // デモのために、ここではプレーンなバイナリ文字列として扱います。 // 実際のアプリケーションでは、シークレットキーの形式に注意してください。 $binarySecret = $secret; // 実際の使用ではBase32デコード後のバイナリデータが入る // 現在のUNIXタイムスタンプを取得 $currentTime = time(); // タイムステップに基づいてカウンター値を計算 $timeCounter = floor($currentTime / $timeStep); // カウンター値を8バイトのバイナリ文字列に変換 // PHPではpack関数を使って'N*'で符号なしロング(32bit)をビッグエンディアンで表現できます。 // 8バイトにするためには、上位4バイトを0埋めする必要があります。 $paddedCounter = pack('N*', 0) . pack('N*', $timeCounter); // HMACを計算 // ハッシュアルゴリズム(SHA1, SHA256, SHA512など)とシークレットキー、カウンター値を使用 $hmac = hash_hmac($algorithm, $paddedCounter, $binarySecret, true); // trueで生のバイナリ出力を得る // 動的切り詰め (Dynamic Truncation) // HMACの結果からオフセットを決定し、OTPを抽出 $offset = ord($hmac[strlen($hmac) - 1]) & 0xF; $otp = ( ((ord($hmac[$offset]) & 0x7F) << 24) | ((ord($hmac[$offset + 1]) & 0xFF) << 16) | ((ord($hmac[$offset + 2]) & 0xFF) << 8) | (ord($hmac[$offset + 3]) & 0xFF) ) % pow(10, $digits); // 指定された桁数になるように先頭をゼロ埋め return str_pad($otp, $digits, '0', STR_PAD_LEFT); } // --- 使用例 --- // Base32エンコードされたシークレットキーの例 // Google Authenticatorなどでよく使われる形式 $base32Secret = "JBSWY3DPEHPK3PXP"; // 例: "Hello!" をBase32エンコードしたもの // 実際のアプリケーションでは、サーバーで生成されたシークレットキーを使用します。 // Base32デコーダーの仮実装 (本番環境では適切なライブラリを使用してください) // これは非常にシンプルな例で、完全なBase32デコーダーではありません。 // 実際のBase32デコードには、より堅牢なライブラリを使用することを強く推奨します。 function base32_decode_simple(string $base32): string { $map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $base32 = strtoupper($base32); $binaryString = ''; $buffer = 0; $bits = 0; for ($i = 0; $i < strlen($base32); $i++) { $char = $base32[$i]; $val = strpos($map, $char); if ($val === false) { continue; // 無効な文字をスキップ } $buffer = ($buffer << 5) | $val; $bits += 5; if ($bits >= 8) { $bits -= 8; $binaryString .= chr(($buffer >> $bits) & 0xFF); } } return $binaryString; } // Base32シークレットをバイナリ形式にデコード $binarySecret = base32_decode_simple($base32Secret); // TOTPを生成 $totp = generateTotp($binarySecret, 30, 6, 'sha1'); echo "シークレットキー (Base32): " . $base32Secret . '
'; echo "生成されたTOTP: " . $totp; // 異なるタイムステップや桁数での例 // $totp8Digits = generateTotp($binarySecret, 30, 8, 'sha1'); // echo "生成されたTOTP (8桁): " . $totp8Digits . PHP_EOL;