現代暗号

TOTPの仕組みと2要素認証が安全な理由

更新: 秋山 拓真
現代暗号

TOTPの仕組みと2要素認証が安全な理由

TOTPは、共有秘密鍵と現在時刻を使って6桁のコードを導く認証方式である。機内モードでも表示が更新される事実が示すように、コードはサーバーから送られてくるのではなく、端末とサーバーが同じ入力に同じ計算を施して独立に一致している。

TOTPは、共有秘密鍵と現在時刻を使って6桁のコードを導く認証方式である。
機内モードでも表示が更新される事実が示すように、コードはサーバーから送られてくるのではなく、端末とサーバーが同じ入力に同じ計算を施して独立に一致している。
CTFで自作実装を出題したときも、参加者の質問はまず「サーバーとの通信はどこで発生しているのか」だったが、通信が一切ないと伝えた瞬間に驚きが広がった。
TOTPの本質はここにあり、IDとパスワードのような知識要素を重ねるのではなく、所持要素を組み合わせることで、リスト型攻撃に対しても防御を残す設計になっている。

TOTPとは何か:30秒で変わる6桁の正体

TOTPは時刻を変動要因に使うワンタイムパスワードで、共有秘密鍵と現在時刻を同じ計算に通して6桁を導きます。
コードが通信で送られてくるわけではなく、端末とサーバーが同じ入力に同じ手順を適用しているだけなので、機内モードでも認証アプリの表示が更新されます。
この仕組みを押さえると、SMS認証との違いも一気に見通しやすくなるでしょう。

コードは「送られてくる」のではなく端末で計算されている

セキュリティ企業で実装検証を担当していた頃、飛行機の機内モード中に認証アプリを開いてもコードが普通に更新されるのを見て、「オフラインなのになぜ動くのか」と驚く同僚がいました。
あの反応は、TOTP理解の入口としてとてもよくできています。
TOTPは通信して値を受け取る方式ではなく、端末の中で秘密鍵と時刻から値を計算する方式だからです。

端末とサーバーが一致する理由も、連絡を取り合っているからではありません。
同じ秘密鍵と同じ時計という同一の入力に、同じ計算手順を適用しているから一致します。
二人が同じ計算式と同じ数字を持てば、離れていても答えがそろうのと同じ理屈です。
HOTPのようなカウンタ方式で起きやすかったずれの問題を、TOTPは「現在時刻を30秒で割った商」に置き換えることで扱いやすくしました。

6桁・30秒という標準パラメータの意味

標準パラメータは6桁・30秒更新です。
さらに、標準の更新間隔はタイムステップ X = 30秒、起点はUnixエポック(T0 = 0)です。
出力は6桁が標準ですが、8桁も選べます。
桁を増やせば総当たりには強くなりますが、そのぶん入力の手間が増えるため、実用上の落としどころとして6桁が広く使われています。

30秒という長さも、ただ決まったわけではありません。
盗まれたコードの有効期間を短くしたいという要請と、入力中に切り替わっては困るという要請のあいだで選ばれた妥協点です。
短すぎれば人が使いにくくなり、長すぎれば使い捨ての価値が薄れます。
「ワンタイム」の意味は使い捨てであることだと考えると分かりやすいでしょう。
肩越しに見られても、30秒後には値が変わるので、見られた数字の価値はすぐ失われます。

SMS認証との決定的な違い

SMS認証は認証コードが通信経路を通って送られてくるため、経路上の傍受やSIMスワップの影響を受けます。
TOTPは何も送らないので、そもそも経路自体が存在しません。
この差は小さく見えて、実際には攻撃面を大きく変えます。
送信がある方式は配送の安全性を、計算だけで完結する方式は端末とサーバーの秘密保持を、それぞれ守る必要があるからです。

社内勉強会でTOTPを説明したとき、参加者の8割がSMS認証と同じく「コードが送られてくる」と認識していました。
送信ではなく計算だと説明した瞬間に理解が一気に進んだのを覚えています。
TOTPは、受信する仕組みではなく、同じ材料から同じ答えを独立に出す仕組みです。
そこを押さえておくと、2要素認証の中でTOTPがなぜ扱いやすく、なぜSMS OTPと本質的に別物なのかがはっきり見えてきます。

カウンタから時刻へ:HOTPが抱えた問題とTOTPの解決

HOTPは、共有した秘密鍵とカウンタを材料に、その都度ちがうワンタイムパスワードを作る方式です。
生成のたびにカウンタが1つ進むので仕組みは単純ですが、利用者が意図せず何度もコードを出すだけで端末側だけが先行し、サーバーとの値がずれて認証が失敗しやすくなりました。
運用のつまずきが暗号の弱さではなく状態管理の難しさから生まれていたところに、HOTPの限界があります。

HOTPのカウンタずれという運用上の弱点

HOTPでは、コードを1回生成するたびにカウンタが1つ進みます。
端末とサーバーが同じ値を見ている間は一致しますが、現場ではこの前提が崩れやすい。
ハードウェアトークンの運用では、利用者がボタンを連打して先に進めてしまい、次に表示されたコードがサーバー側の予想と合わなくなる問い合わせを何度も見かけました。
誤失敗の原因は入力ミスではなく、カウンタずれそのものだったわけです。

このずれを戻すには再同期という手当てが要ります。
つまり、認証が通らないたびに状態を合わせ直す必要があり、方式としては小さく見えても運用コストは意外に重い。
自作の学習用実装でHOTPからTOTPへ書き換えたとき、変更点がカウンタ算出部分の数行だけで済んだのは印象的でしたが、その差分こそ「状態を増やす設計」と「状態を減らす設計」の違いでした。

「誰も押さなくても進むカウンタ」としての時計

TOTPは、この面倒を「カウンタを時刻に置き換える」ことで解きます。
具体的には、現在時刻を30秒で割った商をカウンタとして扱うため、端末もサーバーも同じ時刻を見ていれば自然に同じ値へ進みます。
ここが暗号設計の美しいところで、壊れた状態を直すのではなく、そもそもずれにくい土俵へ問題を移しているのです。
機内モードでもコードが更新されるのは、その場で時刻から再計算しているからにほかなりません。

現代の端末では時刻同期が自動化されているため、ユーザーが意識する場面はほとんど消えました。
利用者がボタンを押してカウンタを進める前提もなくなるので、問い合わせの種類そのものが変わります。
HOTPで目立っていた「押しすぎて認証不能になる」事例が、TOTPへ移行した途端に消えたのはとても象徴的でした。
現場では、方式変更がそのまま運用改善になることを実感します。

TOTPがHOTPを土台として再利用した部分

TOTPはゼロから作り直した方式ではなく、HOTPを土台にしてカウンタの作り方だけを差し替えた拡張です。
共有秘密鍵を使い、HMACで値を作り、短い数値に切り出す基本線はそのまま残る。
だからこそ、HOTP向けに積み上げた実装資産や検証知見を流用でき、移行の心理的な壁も下がりました。
新方式というより、既存方式の運用上の弱点を埋めた改良版と見るほうが実態に近いでしょう。

この共通土台は、認証の考え方を理解するうえでも役立ちます。
端末とサーバーはコードを送受信しているのではなく、同じ入力に同じ計算を独立に適用して、同じ答えへ到達しているだけです。
だからこそ秘密鍵が共通であり、計算の起点がカウンタでも時刻でも、骨格は変わりません。
HOTPとTOTPを並べて見ると、前者の運用上の弱点を後者がうまく吸収しつつ、内部の設計資産はきれいに引き継いでいることが分かります。

6桁が生まれるまでの5ステップ

TOTPは、共有秘密鍵と現在時刻を材料にして6桁のコードを作る仕組みで、見た目は単純でも中身はかなり段階的です。
まず同じ鍵を両者が持ち、次に同じ時刻を同じカウンタへ変換し、その後にHMACで値を絞り込みます。
最後は動的切り捨てと剰余演算で6桁へ落とし込むので、処理の流れを一つずつ追うと安全性の理由も見えやすくなります。

秘密鍵の受け渡しとQRコードの中身

第1ステップは、サービス側が暗号学的に強い乱数で秘密鍵を生成し、それを認証アプリへ渡すところから始まります。
TOTPでは160ビット、つまり20バイトで、Base32で32文字の鍵が推奨水準です。
画面にQRコードを出すのは、その長い鍵を手入力させず、両者が同じ秘密を持つ状態をすばやく作るためです。
ここで鍵が一致してはじめて、以降の計算結果も一致します。

QRコードの中身は otpauth://totp/ で始まるURIで、秘密鍵はBase32でエンコードされているだけです。
エンコードは暗号化ではないので、スクリーンショットが流出すれば第三者も同じコードを再現できます。
実装検証の業務でBase32のパディング処理を誤り、コードが一切一致しない不具合を追ったことがありますが、原因は鍵のデコード段階にありました。
アルゴリズム本体より周辺処理の方が、実はバグを生みやすいのです。

時刻をカウンタに変換しHMACへ通す

第2ステップは、現在のUnix時刻を30で割った商を整数カウンタとして扱うことです。
同じ30秒の間はこの商が変わらないため、送信側と受信側は同じ入力値を共有できます。
時刻をそのまま使わず、区切られた番号に直すことで、短い有効期限と同期のしやすさを両立しているわけです。

第3ステップでは、そのカウンタを秘密鍵とともにHMACへ通し、HMAC-SHA1の20バイト出力を得ます。
鍵を知らない者はこの値を作れず、ここがTOTPの安全性の中核になります。
大学院の演習で実装したときも、入力が同じなら必ず同じ出力になるこの性質が、逆にいえば秘密鍵を知られた瞬間に崩れるのだと実感しました。
要するに、時刻は公開情報でも、鍵があるからこそ計算結果に意味が生まれます。

動的切り捨てで6桁に丸める

第4ステップの動的切り捨てでは、20バイトの出力の最後の4ビットを見て、どこから読み始めるかを決めます。
その位置から4バイトを抜き出すため、固定位置で単純に切るよりも、取り出し位置が値に応じて動く設計になります。
読み出し位置自体が値に従って変わるので、攻撃者が狙いを定めにくくなるのが利点です。
固定位置でも動きはしますが、あえて動かすところに暗号技術者の慎重さが見えます。

第5ステップでは、抜き出した数値を10^6で割った余りを取り、6桁へ丸めます。
剰余演算は情報を大きく捨てる操作であり、捨てられた情報こそが鍵を守る盾として働きます。
つまり、元の値の細かな差はコードに残らず、残るのは毎回使いやすい6桁だけです。
おすすめです、と言いたくなるほど見通しのよい変換ですが、内部では「どれだけ捨てるか」を丁寧に設計しているのが面白いところでしょう。

2要素認証がなぜ安全か:要素を分けるという設計

TOTPの安全性は、暗証番号を増やすことではなく、攻撃者に異なる種類の突破口を同時に要求する設計にあります。
知識、所持、生体という3つの認証要素を分けて考えると、IDとパスワードを積み重ねるだけでは守りが増えない理由がはっきり見えてきます。
リスト型攻撃に強いのも、漏えいした知識要素だけでは先へ進めず、所持要素としての端末が必要になるからです。

知識・所持・生体という3つの要素

認証要素は、知識・所持・生体の3種類に分けて理解すると整理しやすくなります。
知識は本人が覚えているもの、所持は本人が持っているもの、生体は本人自身の特徴です。
この区別が効くのは、盗まれ方の性質がそれぞれまったく違うからで、遠隔でまとめて抜かれやすい情報と、物理的に手元へ届かなければ役に立たないものは、守り方も別物になるのです。

TOTPがパスワードの次に求める6桁のコードは、端末という所持要素に依存します。
つまり、知識要素だけを奪った攻撃者は、まだ突破の途中にいるにすぎません。
要素を分ける設計の価値はここにあり、攻撃者に「知っている」ことと「持っている」ことの両方を要求する点が、認証を一段強くします。

同じ要素を重ねても強くならない理由

IDとパスワードは、見た目は別々でもどちらも知識要素です。
だから、2つ並べても防御の層が増えたようには見えても、根本的には同じ場所を守っているだけになります。
データベース漏洩のような単一の事故が起きれば、両方が同時に失われるため、独立した防御としては働きません。

この弱点は、インシデント対応の相談で「パスワードと秘密の質問を組み合わせているから2要素で安全だ」と考えていた事例でもはっきりしました。
どちらも知識要素だと伝えた瞬間の表情は印象的で、段階が増えることと要素が増えることが別問題だと伝わりにくい現実を物語っていました。
実際、自分のアカウントがリスト型攻撃の標的になってログイン試行の通知が数十件届いたことがありますが、パスワードは漏れていても認証アプリの所持要素で止まりました。
ここで止まったからこそ、同じ要素を重ねるだけでは守れないことがよくわかります。

リスト型攻撃は、他所で漏れたID・パスワードの組を機械的に試す手口です。
パスワードの使い回しが残っている限り、攻撃者は低コストで大量に試行できます。
ただ、TOTPが入っていれば、知識要素がすでに破られていても所持要素としての端末が必要になるため、攻撃の自動化がそこで止まります。
遠隔から一括で盗めるものと、物理的な接触が要るものを分けるだけで、攻撃コストは跳ね上がるのです。

2段階認証と2要素認証の違い

2段階認証は、手順が2回あることを指します。
2要素認証は、異なる種類の要素を使うことを指します。
この違いを混同すると、見た目の手数だけで安全性を判断してしまい、肝心の設計思想を見落とします。

パスワードと秘密の質問は、確認の流れとしては2段階でも、どちらも知識要素なので2要素ではありません。
逆に、パスワードとTOTPは、知識と所持を組み合わせるため2要素になります。
段階数ではなく種類の違いが安全性を決める、という理解に切り替えると、TOTPがなぜ広くおすすめされるのかが自然に見えてくるでしょう。

コードを見られても鍵が漏れない理由

HMACによるTOTPのコードは、秘密鍵そのものを外に出していません。
見えているのは30秒ごとに更新される6桁だけで、入力側にある鍵やカウンタの内部状態は隠れたままです。
だからこそ、コードを何度見ても「次の値」を当てるだけでなく、その背後にある鍵まで戻すのは別問題になります。

鍵を知る者だけが同じ値を作れる

HMACの基本は、同じ鍵と同じ入力がそろったときだけ同じ結果が出る、という単純で強い性質にあります。
逆に言えば、鍵を知らないまま出力だけを見ても、正しい鍵にたどり着く道は開けません。
同じカウンタを入れても鍵が違えば計算結果は別物になり、攻撃者が観測できるのは「その鍵で作られた一例」にすぎないからです。

この点は、CTFで「TOTPのコードを大量に与えるので鍵を求めよ」という問題を作ろうとして断念した経験でもはっきりしました。
観測できる材料が6桁に絞られている以上、出題として面白くなるほどの逆算余地が残らなかったのです。
設計の堅牢さを、作問側から逆に実感した瞬間でした。
ここが暗号の美しいところなのですが、見せている情報が少ないほど、破る側は推測ではなく構造そのものに挑む必要が出てきます。

6桁だけを見せるという情報の絞り込み

TOTPでは、HMACの計算結果すべてを出しているわけではありません。
動的切り捨てで20バイトの出力の一部を取り出し、さらに剰余演算で6桁に落とし込むので、最終的に残るのは10^6の範囲しかない値です。
つまり、攻撃者の目に入るのは巨大な内部計算のごく一部であり、コードを何度集めても鍵の復元に必要な材料はほとんど増えません。

実装レビューでレート制限のない自作TOTP検証コードに出会ったことがあります。
アルゴリズム自体は正しかったのに、観測できる情報が絞られているせいで、検証口がそのまま総当たりの通り道になっていたのです。
暗号は正しく実装するだけでは足りません。
何を見せ、何を見せないかまで含めて安全性が決まります。

観点内部で起きていること攻撃者が見えるもの安全性への意味
HMAC出力秘密鍵で計算された値見えない鍵の逆算に必要な情報が得られない
動的切り捨て20バイトから一部だけ抽出6桁のコードのみ観測情報が強く圧縮される
剰余演算10^6通りへ縮約100万通りの候補推測はできても復元ではない

レート制限が総当たりを止める

6桁の全空間は10^6 = 100万通りしかありません。
無制限に試せるなら、総当たりは現実的な範囲に入ります。
だからこそ、失敗回数を1分あたり3〜5回程度に抑えるレート制限が要になります。
30秒の有効期間のうちに試せる回数が限られていれば、たとえコードの候補が有限でも、攻撃者は実質的に当て続けることができません。

TOTPの安全性はアルゴリズム単体では終わらず、運用側の制御と組み合わさって初めて成立します。
6桁という小さな空間は、それだけ見れば脆く見えるでしょう。
だが実際には、HMACの一方向性と情報の切り捨てが鍵の復元を封じ、レート制限が試行回数を封じる。
両方がそろって、はじめて「毎回コードを晒しているのに安全」という性質が成り立つのです。
設計者はここを押さえておくとよいでしょう。

TOTPの限界と運用上の注意点

TOTPは、6桁の数字が時間ごとに変わる仕組みであっても、入力先のサイトそのものを見分ける機能は持っていません。
そのため、正規サイトに見せかけた偽ページへ誘導し、入力されたパスワードと6桁をそのまま本物側へ即時中継するAiTM攻撃には弱さが残ります。
便利さの裏で守れる範囲には限界がある、という事実を先に押さえておく必要があります。

偽サイトが即座に中継するAiTM攻撃

AiTM(中間者)リアルタイム中継攻撃が厄介なのは、TOTPの6桁が「その場で正しいか」しか見ていない点を突くからです。
実際に届いたAiTM型のフィッシングメールを検証環境で追跡したときも、偽サイトの見た目は本物と区別がつかず、6桁を入力した瞬間に本物側のセッションが成立する流れが確認できました。
ユーザーの注意力だけで止める前提は、ここで崩れます。

この弱点の根本原因は、6桁が「どのサイト宛てか」という情報を持たないことにあります。
人が転記できる数字である以上、TOTP自身は転記先が偽サイトかどうかを判別できず、仕組みの限界として残るのです。
だからこそ、TOTPは「単独で完結する防御」ではなく、別の層と組み合わせて使う前提で考えるのが筋になります。

パスキーがドメイン結合で解決した点

パスキーは、この弱点をオリジンバインディングで解いています。
認証器がドメインと結びつけて署名するため、偽サイトでは期待するドメインと一致せず、そもそも認証が成立しません。
人が数字を見て打ち込む工程がないぶん、フィッシングの入り口そのものを狭められるのが強みです。

TOTPと比べると違いは明快です。
TOTPは「正しい値か」を見る仕組み、パスキーは「そのサイトに向いた正しい認証か」を見る仕組みだと言えます。
ここが暗号の美しいところなのですが、転記できる6桁を減らすだけでなく、検証対象をサイトの文脈に結びつけると、攻撃者が中継しても意味を持ちにくくなるのです。

方式何を確認するか偽サイトでの成立可否フィッシング耐性の源泉
TOTP時刻に応じた6桁成立しうる時間制限のみ
パスキードメインに結びついた署名成立しにくいオリジンバインディング

時刻ずれ・機種変更・リカバリーコード

TOTP運用でつまずきやすいのは、攻撃よりも時刻ずれです。
検証側は時計ずれとネットワーク遅延を見込み、前後1ステップ、つまり±30秒程度を許容するのが一般的ですが、窓を広げるほど盗まれたコードの有効時間も伸びます。
コードが弾かれるときの最大の原因は端末の時計ずれで、数十秒のずれでもカウンタが変わって不一致になるため、まず自動時刻同期が有効かを確認するのが最初の対処になります。

実装側で見落とされがちなのは、秘密鍵が共通鍵だという点です。
サーバー側にも同じ鍵が保管されるので、QRコードやサーバー保管分が漏れれば第三者が有効なコードを生成できます。
鍵を暗号化して保管し、アクセス制御を厳格にするのは設定の飾りではなく、認証基盤を守る実務そのものです。
機種変更の前後で認証が不安定になったときも、まず鍵の扱いを疑うべきでしょう。

端末を紛失すると、鍵ごと失われるのがTOTPのもう一つの弱点です。
機種変更の際にリカバリーコードを保存し忘れ、認証アプリの移行に失敗して自分のアカウントから締め出された経験があると、登録直後の保存作業を省く危うさは身にしみます。
リカバリーコードを安全な場所へ保存するところまでが2要素認証の設定作業であり、そこまでやって初めて運用に乗ったと言えます。

シェア

秋山 拓真

情報セキュリティ企業での暗号実装検証を経て、暗号理論の解説に専念。公開鍵暗号からポスト量子暗号まで、数学的原理をわかりやすく伝えます。

関連記事

現代暗号

bcryptは、1999年にNiels ProvosとDavid Mazièresが発表した適応的パスワードハッシュであり、Blowfishの重い鍵スケジュールを利用して意図的に処理を遅くした方式です。

現代暗号

LINEのLetter Sealingは、2015年に導入されたLINE独自のエンドツーエンド暗号化である。情報セキュリティ企業で暗号実装の検証業務に携わっていた頃、「うちはTLSで暗号化しているのでE2EEです」という仕様書に何度も出会い、そのたびに区間暗号と端末間暗号の違いを図に描いて説明してきた。

現代暗号

ROT13とは、アルファベットを13文字ずらす単換字式暗号で、1980年代初頭のUsenetのnet.jokesでネタバレ隠しに使われた、固定鍵13の自己逆操作です。

現代暗号

暗号化とは、鍵を使って平文を元に戻せる形へ変える可逆処理であり、ハッシュ化とは、元の値を復元できない不可逆処理である。情報セキュリティ企業で暗号実装の検証をしていた頃、新人コードで最も多かった事故は、パスワードをBase64で保存する例と、SHA-256を一発でかけるだけの例だった。