TOTP 認証アプリのコードは実際どう動くのか
TOTP (RFC 6238) が共有秘密と現在時刻をどうやって 6 桁のコードに変えるか、なぜ両者が一致するか、そして何を守り何を守らないかを整理します。
認証アプリが 6 桁のコードを表示し、サーバーがそれを受け入れるとき、両者の間で ネットワーク呼び出しは起きていません。スマホとサーバーは、どちらもすでに持って いる 2 つの入力から同じ数字を独立に計算しました。登録時に固定された共有秘密と、 現在の時刻です。それが TOTP の背後にある発想のすべてです。ネットワークの要らない 第 2 の要素で、飛行機の中でも、サーバーに到達できなくても動きます。両者が同じ 時計に対して同じ決定論的な関数を走らせるからです。
TOTP は RFC 6238 です。HOTP (RFC 4226) の上の薄い層で、HOTP 自体が HMAC の上の 薄い層です。それを理解するとは、互いの上に積み重なったちょうど 3 つを理解する ことです。
HOTP: カウンタの鍵付きハッシュ
HOTP は共有秘密 K とカウンタ C からコードを作ります。カウンタは 8 バイトの
整数です。構成は次のとおりです。
HMAC-SHA1(K, C)を計算します。20 バイトの MAC です。- その 20 バイトを数字に崩すために 動的切り詰め を適用します。
- その数字を
10^digitsで割った余りを取り、表示するコードを得ます。
動的切り詰めが唯一自明でない手順です。HMAC の最後のバイトの下位ニブル (0〜15 の
値) をオフセットとして読みます。そのオフセットから始めて HMAC から 4 バイトを
取り、結果を正に保つために最上位ビットをマスクし、得られた 31 ビットの整数を
10^digits で割った余りを取ります。デフォルトの 6 桁なら mod 1000000 で、
000000 から 999999 までの数字が残ります。
mac = HMAC-SHA1(secret, counter) # 20 バイト
offset = mac[19] & 0x0f # 最後のニブル、0..15
chunk = mac[offset .. offset+3] # そのオフセットから 4 バイト
binary = chunk & 0x7fffffff # 符号ビットをマスク -> 31 ビット
code = binary % 10^digits # デフォルト digits = 6
オフセットがハッシュ自体から導かれるので 動的 と呼びます。常に同じウィンドウを 読む代わりに、標本の 4 バイトを MAC 全体に散らし、出力が固定された切片ではなく ダイジェスト全体を反映するようにします。
TOTP: カウンタを時刻に置き換える
HOTP はカウンタを手動で増やします。ハードウェアトークンを押すたびに C が 1
上がり、サーバーが期待値を追跡します。その同期は壊れやすいです。押下を取りこぼす
と両者がずれるからです。
TOTP は手動のカウンタをなくします。ユーザーが進める数字の代わりに、カウンタが すなわち 時計です。
counter = floor(currentUnixTime / period) # period のデフォルトは 30
code = HOTP(secret, counter)
30 秒周期でカウンタは 1 分に 2 回変わります。12:00:00 から 12:00:29 までは 1 つの 値を保ち、12:00:30 に次へ進みます。スマホとサーバーはどちらも自分の時計を読み、 30 で割り、切り捨て、その整数を上記の正確な HOTP 構成に入れます。両者の時計が ウィンドウ内で一致すれば、互いに一言も交わさずに同じ 6 桁を計算します。
それがメカニズムのすべてです。TOTP は文字どおり現在時刻の鍵付きハッシュを 6 桁に 切り詰めたものです。
登録: QR コード
共有秘密は設定時にちょうど一度、両側に届かなければなりません。標準の運び手は
otpauth:// URI で、ほぼ常に QR コードとして配られます。
otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30
フィールドは次のとおりです。
- secret — 共有鍵、Base32 エンコード。手で打たれ、フォントで表示されても
耐えるアルファベットだからです。デコードされたバイトが HMAC に与える
Kです。 - issuer と account — アプリが表示するラベル (「Example」、 「[email protected]」)。装飾的で、計算には入りません。
- algorithm —
SHA1、SHA256、またはSHA512。デフォルトは SHA1。 - digits —
6または8。デフォルトは 6。 - period — 1 ステップあたりの秒。デフォルトは 30。
デフォルトは SHA1、6 桁、30 秒で、実務では事実上必須です。仕様は SHA-256 や
SHA-512 と 8 桁を許可しますが、多くの人気の認証アプリは algorithm と digits
のパラメータを黙って無視し、デフォルトを仮定します。だから 8 桁の SHA-256 の
秘密を発行するサーバーは、ユーザーの半分のアプリが決して検証されない 6 桁の
SHA-1 のコードを生成するのを目にすることがあります。両端を制御できるなら、より
強いオプションを使えますし、ユーザーが自分の認証アプリを持ち込むなら、デフォルト
を守ってください。
(ここでの HMAC-SHA1 は SHA-1 の衝突問題ではありません。TOTP は HMAC に依存し、 HMAC は SHA-1 を署名用に破った衝突とは別の、はるかに強い攻撃に対する SHA-1 の 耐性に依存します。HMAC-SHA1 は今でも安全と見なされています。それは Webhook 署名 を認証するのに使われる同じ 鍵付きハッシュのプリミティブで、TOTP はメッセージが現在時刻であるその構成です。)
時計のずれと検証ウィンドウ
時計はずれます。時刻が 40 秒進んだスマホは、サーバーがまだ現在のステップにいる 間に 次の ステップのコードを計算します。サーバーが自分の現在のステップのコード しか受け入れなければ、そのユーザーたちは毎回ログインに失敗します。
そこでサーバーはウィンドウを受け入れます。よくある選択は ±1 ステップです。 サーバーは現在のカウンタ、前のもの、次のものを調べ、その 3 つのどれに一致しても 受け入れます。30 秒周期で、これは約 ±30 秒のずれを許容しつつ、どの瞬間でも有効な コードの数を 3 つに保ちます。
ウィンドウを広げるのは直接のトレードオフです。
- 広く — ずれによる正当な失敗は減りますが、より多くのコードが同時に有効に なり、盲目的に推測する攻撃者はより大きな的を持ち、中継されたコードがより長く 使えます。
- 狭く — より厳しいセキュリティですが、時計がいい加減なユーザーが締め出され ます。
±1 が普通の落としどころです。慢性的なずれを見るサーバーは、全員のためにウィンドウ を広げるより、各ユーザーの測定したオフセットを記録してウィンドウをずらすことも あります。
TOTP が守るもの、守らないもの
TOTP は 1 つの特定のものを守ります。パスワードを盗んだ攻撃者が、コードなしでは 依然としてログインできず、コードは 30 秒ごとに変わります。クレデンシャル スタッフィングやデータベースの漏洩に対して、それは実質的で大きな改善です。 強い第 1 要素のパスワード の後ろの 自然な第 2 要素です。
限界も同じく特定です。
- フィッシングされえます。 コードはユーザーが読んで打つ短い文字列です。 リアルタイムのフィッシングサイトはそれを尋ね、同じウィンドウ内で本物の サーバーへ中継できます。TOTP の何も、コードを要求するサイトに結び付けません。 資格情報がオリジンに暗号学的に限定され、ほかの場所で再生できない WebAuthn/パスキーとは違います。フィッシング耐性が必要なら、TOTP はそれでは ありません。
- ウィンドウ内のリプレイ。 コードはステップ全体 (と受け入れられた隣) で 有効です。サーバーは成功した瞬間にコードを使用済みと印を付け、そのウィンドウの 残りの間、同じコードを拒否すべきです。さもなければ上記の中継が攻撃者に数十秒を 買います。
- 保存時の秘密のリスク。 サーバーはスマホが持つのと同じ秘密を保存します。 TOTP の秘密を露出するデータベースの侵害は、攻撃者がすべての被害者のコードを 生成できるようにします。これらの秘密は保存時の暗号化と、パスワードのハッシュと 同じ取り扱いの規律が必要です。動くコードに戻せるので、むしろそれ以上です。
その注意点があっても、TOTP は SMS のワンタイムコードを決定的に上回ります。SMS は SIM スワップや SS7 攻撃で傍受されえ、キャリアの配信に依存し、ログインのたびに 秘密をネットワークに送ります。TOTP は秘密を一度交換し、二度と送信しません。 パスキーが選択肢にないとき、アプリベースの TOTP が正しいデフォルトです。
一行のまとめ
TOTP は時計の HMAC です。秘密を一度共有し、時刻に合意し、鍵付きハッシュを 6 桁に 切り詰め、ずれのために小さなウィンドウを受け入れてください。残りのすべて、つまり QR コード、Base32、検証ウィンドウは、その中核を取り囲む配管です。メカニズムを 直接見たければ、私たちの TOTP 生成器 に Base32 の秘密を 貼り付け、コードが 30 秒の境界ごとに切り替わるのを眺めてください。上記の正確な 構成をブラウザ内で走らせ、サーバーには何も送りません。