TOTP 生成

base32 シークレットから時刻ベースのワンタイムパスワード(TOTP)を生成・検証。Google 認証システムおよび RFC 6238 互換。

ワンタイムパスワード、RFC 6238。 base32 シークレットから現在の TOTP を生成し、受け取ったコードを検証。カウンターは現在時刻から導き、30 または 60 秒ごとに更新されます。すべてブラウザ内で。

------

コードを検証

TOTP の仕組み

共有秘密と時計

TOTP は RFC 6238 で定義され、HOTP の上に薄い層をかぶせたものです。両者は同じ秘密鍵を保持し、通常は登録時に QR コードとして一度だけ受け渡して認証アプリに保存します。コードを生成するとき、双方は現在の Unix 時刻をステップ幅(既定 30 秒)で割ってカウンターを求め、そのカウンターに対して秘密鍵で HMAC-SHA1 を計算し、結果を必要な桁数に切り詰めます。

カウンターがメッセージではなく時計から得られるため、登録後は両者の間で何もやり取りする必要がありません。サーバーはチャレンジを送らず、アプリはネットワークを必要としません。認証アプリが機内モードでも動くのはこのためです。

切り詰めと桁数

HMAC の出力は 20 バイトで、6 桁のコードにははるかに余ります。動的切り詰めでは最終バイトの下位 4 ビットをオフセットとして使い、そこから 4 バイトを読み、符号の問題を避けるため最上位ビットをマスクし、その値を 10^桁数 で割った余りを取ります。固定ではなく可変のオフセットを使うことで、攻撃者はダイジェストの既知の一部に解析を集中できなくなります。

6 桁がほぼ普遍的な既定値です。8 桁も多くのアプリが対応しており、推測耐性が 10 進 2 桁ぶん増えますが、レート制限に比べれば重要度は大きく劣ります。6 桁コードの 1 回あたりの的中確率は 100 万分の 1 なので、実際の防御は数回の失敗でロックアウトすることです。

時計のずれとリプレイ

端末の時計はずれます。サーバーは通常、現在のウィンドウに加えて前後のウィンドウのコードも受け入れ、±30 秒程度の許容を持たせます。これを 1〜2 分より広げると攻撃面が目に見えて増え、たいていは端末の時計を直すべきサインです。

運用時に覚えておきたい性質が 2 つあります。コードはウィンドウの間ずっと有効なので、傍受されたコードはそのウィンドウ内で再利用できます。サーバーが使用済みカウンターを消費済みとして記録していないかぎり防げません。もう 1 つ、秘密鍵は対称です。サーバー側の複製を手にした者は永久に有効なコードを生成できるため、パスワードデータベースと同等の保護が必要です。保存時は暗号化し、ログに出さず、git リポジトリに置かないことです。

オープンソースに関する注記:ブラウザ標準の Web Crypto(crypto.subtle)API と自前の base32 デコーダで実装。サードパーティライブラリは使用していません。

よくある質問

認証アプリの代わりになりますか?
テスト用途や、スマートフォンが手元にないときにコードを取得する用途には便利ですが、ブラウザのタブは長期の共有秘密を保管する安全な場所ではありません。
なぜコードが拒否されるのですか?
ほとんどの場合は時刻ずれです。TOTP は現在の UNIX 時刻からコードを導出するため、サーバーと 30 秒のステップ 1 つ以上ずれた端末は、サーバーが既に通過したコードを生成してしまいます。
Base32 のシークレットは何を表しますか?
双方が保持する共有鍵です。Base32 は大文字小文字を区別せず紛らわしい文字も避けているため、手入力や QR コードへの埋め込みに適しています。
なぜコードは 6 桁なのですか?
RFC 6238 は HMAC を 31 ビットに切り詰め、10^6 で剰余を取ります。6 桁は使いやすさと 1 回あたり 100 万分の 1 の的中率のバランスであり、レート制限でさらに安全になります。
TOTP と HOTP の違いは何ですか?
TOTP は 30 秒の時間ステップを数え、HOTP はイベントを数えてコードが使われたときだけ進みます。TOTP は時刻同期を、HOTP はカウンター同期を必要とします。
同じコードを 2 回使えますか?
使うべきではありません。コードは時間ステップの間ずっと有効なので、サーバー側で利用済みのステップを記録し、再送を拒否することが求められます。