メインコンテンツへ
プログラミング

TOTP 認証の仕組み - 時刻同期で生まれる 6 桁コードの正体

TOTP とは - 30 秒ごとに変わる使い捨てパスワード

TOTP (Time-based One-Time Password) は、サーバーとユーザーの端末が共有する秘密鍵と現在時刻を組み合わせて、30 秒ごとに変化する 6 桁の数字を生成する認証方式です。Google Authenticator、Authy、Microsoft Authenticator、1Password など、現在主流の二要素認証アプリはほぼすべてこの方式を採用しています。RFC 6238 として 2011 年に標準化され、ベンダーロックインのない相互運用可能な仕様になっています。

TOTP の最大の利点は、ネットワーク通信を必要としないことです。サーバーとアプリの間で秘密鍵を最初に共有しておけば、その後はそれぞれが時刻を知っているだけで同じコードを生成できます。SMS 認証のように電波状況に左右されず、サーバー側もコード生成にデータベースアクセスが不要なため、スケーラビリティに優れた認証手段です。SMS の SIM スワッピング攻撃のリスクも回避できます。

HOTP - TOTP の土台となるカウンタベース OTP

TOTP を理解するには、まず先行仕様の HOTP (HMAC-based One-Time Password、RFC 4226) を押さえる必要があります。HOTP はカウンタ値と秘密鍵を HMAC-SHA-1 でハッシュ化し、結果から 6 桁の数字を導出するアルゴリズムです。サーバーと端末がカウンタを同期させ、認証成功のたびにカウンタを 1 ずつ増やしていきます。コード自体は時刻に依存しないため、時計の精度が悪い環境でも動作する利点があります。

HOTP の弱点は、カウンタ同期が崩れた場合の復旧の難しさです。ユーザーがアプリで複数回コードを生成したのに認証に使わなかった場合、サーバーと端末でカウンタがずれてしまいます。サーバー側で「次の数個分のコード」を先読みして照合する必要があり、運用が複雑です。TOTP はこの問題を「カウンタの代わりに時刻を使う」というシンプルなアイデアで解決しました。

TOTP のアルゴリズム - 30 秒ステップの計算式

TOTP の核となる計算は、現在の Unix 時刻を 30 で割った商をカウンタとして HOTP を実行することです。Unix 時刻 1716000000 (2024-05-18 14:40:00 UTC) であれば、カウンタは 57200000 となります。この値を秘密鍵と組み合わせて HMAC-SHA-1 を計算し、結果から 6 桁の数字を抽出します。30 秒経過してカウンタが 57200001 になれば、まったく別の 6 桁が生成されます。

30 秒というステップ幅は RFC 6238 のデフォルト値で、セキュリティと使いやすさのバランス点として選ばれました。短すぎるとユーザーが入力する間にコードが変わってしまい、長すぎると総当たり攻撃の窓が広がります。多くのサービスはサーバー側でも前後 1 ステップ (合計 90 秒) を許容範囲として照合するのが一般的です。Steam Guard はステップ幅を 30 秒、X (旧 Twitter) のかつてのアプリは 30 秒など、サービスごとに微妙な調整が見られます。

QR コードに含まれる秘密 - otpauth URI

二要素認証を有効化するときに表示される QR コードは、otpauth:// で始まる URI 文字列をエンコードしたものです。例えば otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example のような形式で、Base32 エンコードされた共有鍵 (secret) と発行元 (issuer)、ユーザー識別子が含まれます。Google Authenticator や類似アプリはこの URI をパースして秘密鍵を保存します。

QR コードを写真撮影されると秘密鍵が漏洩するため、設定画面の表示は一度限り、画面共有中は避けるべきセキュリティ事項です。多くのサービスは「バックアップコード」を併せて発行し、端末紛失時の復旧手段を用意しています。バックアップコードは TOTP とは独立した使い捨てパスワードで、一度使用したら無効化される設計が一般的です。両方を別の安全な場所に保管するのが、ユーザーのリスク管理として推奨される運用です。

時刻ずれ問題 - 認証失敗の最大の原因

TOTP の認証失敗で最も多い原因は、サーバーと端末の時刻ずれです。サーバー側は通常 NTP で UTC に同期されていますが、ユーザーのスマートフォンが手動設定で時刻をいじっていたり、海外旅行で時刻設定が混乱したりすると、コードが合わなくなります。Google Authenticator にはアプリ内設定で「時刻補正」機能があり、Google のサーバーから取得した正確な時刻と内部時計のずれを記録して TOTP 生成に反映します。

サーバー側の時刻ずれも問題で、コンテナの時刻が NTP 同期されていない構成では数十秒のずれが生じることがあります。Kubernetes の Pod は通常ホストの時刻を参照しますが、仮想化のレイヤーで誤差が累積するケースもあります。TOTP を実装するサーバーでは、定期的な NTP 同期と時刻同期失敗時のアラート設定が運用上の必須事項です。本番障害の典型例として「サーバーの時刻が 5 分ずれていて全ユーザーがログインできない」という事例が業界で繰り返し報告されています。

実装上の注意点 - SHA-1 から SHA-256 への移行

TOTP の標準では HMAC-SHA-1 が使われますが、これは「OTP の用途には十分」とされている一方で、新規実装では SHA-256 や SHA-512 を使う選択肢もあります。otpauth URI には algorithm パラメータでハッシュアルゴリズムを指定でき、対応するクライアントは適切に処理します。ただしクライアント側の互換性に注意が必要で、Google Authenticator の旧バージョンは SHA-256 をサポートしていなかった経緯があります。

サーバー側の実装では、ユーザーが過去に使ったコードを再利用させない「リプレイ攻撃対策」も重要です。30 秒間に同じコードが 2 回成功してしまうと、攻撃者が傍受したコードを使って認証を通せる可能性があります。最後に成功したカウンタ値をユーザーごとに保存し、それより新しいカウンタ値しか受け付けないロジックを入れるのが定石です。コードの長さは 6 桁が標準ですが、digits パラメータで 7 桁や 8 桁を指定して安全性を高める選択肢もあります。

XB!LINE

この記事は役に立ちましたか?

関連記事