- 入力変数の本質的な違い: HOTPは単調増加する整数カウンター C を入力とし、TOTPはCをエポック秒に基づく時間ステップ T(Unix秒 / 30) に置き換えます。
- 完全な継承関係: 数学的にTOTPはHOTPの直接の派生形です:
TOTP(K, Time) = HOTP(K, T)。両アルゴリズムは全く同一の動的切り捨て(Dynamic Truncation)関数を共有しています。 - アーキテクチャの決定打: HOTPはステートフル(認証ごとにサーバーが最新カウンターをDBに排他書き込み保存する必要がある)であるのに対し、TOTPはステートレス(時刻同期のみでDB書き込み不要)であるため、現代の大規模分散クラウド基盤に最適です。
歴史的背景:専用ハードウェアトークンからオープンWeb標準へ
2000年代初頭、企業の2要素認証はRSA SecurIDに代表される専用ハードウェアキーフォブが独占していました。これらの独自トークンは高価であり、内部の水晶発振器の精度管理や専用認証サーバーの導入が必要だったため、中小企業や一般コンシューマーへの普及は極めて困難でした。
このベンダーロックインを打破し、世界中のエンジニアが自由に実装・監査できるオープンな標準を策定するため、業界団体OATH(Initiative for Open Authentication)が結成され、IETFにおいて2つの歴史的RFC標準が誕生しました:
HMAC-Based One-Time Password Algorithm。物理ボタンを備えた低電力マイクロコントローラー向けに策定。ボタンを押すたびに内部カウンターが増加し、LCD画面に6桁のコードが表示されます。
Time-Based One-Time Password Algorithm。スマートフォンの爆発的普及に伴い登場。携帯電話網のNTP時刻同期を活用することで、ボタン操作やカウンターズレの課題を根底から解消しました。
HOTP(RFC 4226)と動的切り捨てのバイト計算メカニズム
HOTPの数学的定義は極めてシンプルです:
構成パラメーターの解説:
K: 共有秘密鍵(Base32デコードされた160ビット / 20バイトのバイナリ列)。C: 8バイト(64ビット)ビッグエンディアン整数のカウンター。HMAC-SHA-1: 秘密鍵付きハッシュ関数。20バイト(160ビット)のダイジェストを出力。
🔍 動的切り捨て(Dynamic Truncation)が20バイトから6桁を抽出する手順:
HMAC-SHA1ハッシュ値(20バイト)から、暗号学的に偏りのない6桁の数字を決定論的に抽出するため、RFC 4226第5.3節で定義されたアルゴリズムを実行します:
秘密鍵をASCII文字列 "12345678901234567890"、カウンター C = 0 とした場合のHMAC-SHA1ダイジェスト(16進数):
1. 末尾バイトは 0x71 です。0x71 & 0x0F = 0x01 となり、Offset = 1 です。
2. インデックス1からの4バイトは 93 cf 18 50 です。MSBをクリアすると 0x13cf1850、10進数で 332,339,280 です。
3. 332339280 mod 1000000 = 339280。最終的な6桁ワンタイムパスワードは 339280 となります。
TOTP(RFC 6238)の進化:時刻ステップの魔法
HOTPでは、ユーザーが端末のボタンを誤って複数回押したり、ログインを途中で中断したりすると、端末側とサーバー側でカウンターCがズレて認証不能になる問題がありました。
RFC 6238は、この動的にズレるカウンターCを、Unixエポック時刻に基づく確定的な時間ステップ T に置き換えました:
- CurrentUnixTime: 1970年1月1日00:00:00 UTCからの経過秒数。
- T0: 開始エポック秒(現代の実装ではすべて0)。
- X(タイムステップ間隔): 標準では30秒。
- 床関数 ⌊ ⌋(切り捨て): 30秒間の枠内ではTの値は全く変動しません。例えばエポック秒 1728211200 から 1728211229 までの間、Tは同一の64ビット整数を保ちます。
バイナリ変換とHOTPへの引き渡し: 算出された整数Tは、HOTPのカウンターCと全く同じ8バイトのビッグエンディアン形式にパックされ、HOTPアルゴリズムに入力されます:
TOTP(K, Time) = HOTP(K, T)
この計算の美しさにより、認証アプリはネットワーク通信を一切行わず、時計の進みだけを頼りに30秒ごとにコードを同期更新できるのです。
徹底比較マトリックス(HOTP vs TOTP)
| 比較軸 | HOTP(RFC 4226) | TOTP(RFC 6238) |
|---|---|---|
| 入力変数 | イベントカウンター C(単調増加) | 時間ステップ T(30秒刻みのUnixエポック) |
| コードの有効期間 | 無期限(次の認証で消費されるまで有効) | 厳格に30秒間(自動失効) |
| サーバー構造 | ステートフル(認証ごとにDBのカウンター更新が必須) | ステートレス(NTP同期だけで独立検証可能) |
| リプレイ攻撃耐性 | 中程度(盗聴されたコードは使用されるまで有効) | 極めて高い(30秒経過で自動的に無効化) |
| 主な障害要因 | カウンターの非同期ズレ(空押し) | 端末の時刻ズレ(クロックドリフト) |
| 対応ハッシュ関数 | HMAC-SHA-1 のみ | SHA-1、SHA-256、SHA-512 |
| 実社会での採用状況 | 旧式銀行トークン、専用電子錠 | 現代Webサービスの99.9%(デファクトスタンダード) |
カウンター非同期ズレ vs 時刻ズレの技術的対策
実際のシステム運用において、クライアントとサーバーのズレは不可避です。各アルゴリズムはエンジニアリングによって信頼性を担保しています:
ユーザーがログインせずにトークンのボタンを誤って5回押した場合、端末は C+5 に進みますがサーバーは C のままです。
RFC 4226では、サーバーが探索枠 [C+1, C+s](通常 s=20)の範囲で総当たり計算を行い、一致した時点でサーバーのカウンターをジャンプさせて再同期します。
ネットワーク遅延やスマホの内蔵時計のズレに対応するため、本番サーバーは [T-1, T, T+1] の3つのウィンドウ(合計90秒間)のいずれかに一致すれば正当とみなします。
サーバー側でユーザー入力コードと計算結果を比較する際、通常の文字列比較演算子(===)を用いると、先頭文字が不一致になった瞬間に処理が打ち切られます。攻撃者はナノ秒単位の応答時間の差を計測することで1文字ずつ正解コードを特定できてしまいます。セキュアなシステムでは必ず定数時間比較(例:Node.jsの crypto.timingSafeEqual)を実装してサイドチャネル攻撃を防御します。
現代のインターネットでTOTPが勝利した理由
HOTPが衰退し、Web全体でTOTPが圧倒的な標準となった背景には、3つの決定的な理由があります:
世界中に分散したWebサーバー群において、ステートレスなTOTPはDBアクセスなしにNTP時計だけで検証可能。高トラフィック時もDBのロック競合を起こしません。
基地局電波やGPSによりスマホの時計はミリ秒単位で常時自動補正されるため、専用ハードウェアトークンが不要になりました。
盗聴されても30秒で無効化されるため、ログ漏洩や通信パケット傍受からの安全性が劇的に向上しました。
ブラウザ上でTOTP動的計算をリアルタイム体験
Base32シークレット文字列がどのように30秒刻みで6桁コードへ変換されるかを直接確認できます。W3C Web Crypto APIにより完全オフラインで動作します。