認証アプリの6桁はどう作られる?──TOTP(ワンタイムパスワード)の仕組みを30秒ごとに追う
ログイン時に入力するあの6桁は、登録時に交換した共有シークレットと現在時刻からHMACで毎回計算された数字です。通信なしでサーバと同じ番号が出る理由と、SMSより安全な仕組みをやさしく解説します。
二段階認証を設定すると、ログインのたびに認証アプリ(Google Authenticator や Authy など)が表示する 6桁の数字 を求められます。あの数字、よく見ると 30秒ごとに勝手に切り替わって いきますよね。
「あの6桁って、どこから出てくるの?」「ネットにつながっていないスマホなのに、なぜサーバと同じ番号が出るの?」と不思議に思ったことはないでしょうか。
結論を先に書きます。
6桁は「登録時に交換した共有シークレット」と「現在時刻を30秒刻みにした値」を HMAC で計算した結果です。同じ材料が両側にあるから、通信しなくてもスマホとサーバが同じ数字に行き着きます。この仕組みを TOTP(Time-based One-Time Password / RFC6238) と呼びます。
TOTPの材料は「シークレット」と「時刻」だけ
TOTPが6桁を作るのに使う材料は、たった2つです。
| 材料 | 中身 | いつ決まる |
|---|---|---|
| 共有シークレット | 登録時のQRに埋まったランダムな鍵 | 二段階認証を設定した瞬間 |
| 現在時刻 | UTCの秒数を30で割った整数 | 6桁を表示する瞬間 |
ポイントは、シークレットは一度きりの交換で、以降は更新されないこと。一方の時刻は誰の時計にも刻まれていて、30秒ごとに値が変わります。
この2つを混ぜて毎回違う数字を吐き出すのが TOTP のアルゴリズムです。実際に材料から6桁が生まれる過程を観察したいなら、TOTPを整える で今この瞬間のコードと残り秒数がそのまま見られます。
6桁が生まれる4ステップ
TOTPの計算は、ざっくり4段階です。
- 時刻をカウンタに変換 … 現在のUNIX時刻(秒)を30で割って小数を切り捨て、整数 T にする。これが「今は何番目の30秒か」を表す。
- HMAC-SHA1 にかける … シークレットを鍵、T をメッセージとして HMAC-SHA1 を計算。20バイト(160ビット)のハッシュが出る。
- Dynamic Truncation(動的切り出し) … 20バイトの末尾4ビットを「読み始める位置」として使い、そこから4バイトを取り出して31ビットの数値に変換する。
- 6桁に丸める … その数値を 1,000,000 で割った余りを取り、6桁にゼロ埋めする。
T が30秒ごとに +1 されるので、HMACの出力が毎回まるごと変わり、6桁も総入れ替えになります。途中のHMACやハッシュがどういうものかは ハッシュを整える で実際に文字列を入れて出力を眺めると腹落ちします。
なぜ通信なしで一致するのか
ここが TOTP の一番面白いところです。
スマホとサーバは、6桁そのものをやり取りしていません。両者が持っているのは「同じシークレット」だけ。あとはそれぞれが「今の時刻」を見て、独立に同じ計算をしているだけです。
- スマホ:シークレット + 自分の時計 → 6桁を表示
- サーバ:同じシークレット + サーバの時計 → 同じ6桁を計算
時計さえ合っていれば、計算結果は必ず一致します。だから認証アプリは 機内モードでも6桁を出せるのです。番号を受信しているのではなく、自前で計算しているわけですね。
QRコードの正体と、紛失時のリスク
二段階認証の設定でスキャンするQRコード。あれは画像に意味があるのではなく、中に otpauth:// で始まるURI が入っています。
otpauth://totp/サービス名:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=サービス名
secret= の部分が、まさに共有シークレットです。QRをスキャンした瞬間に、この鍵がスマホの認証アプリに保存されます。
ここで重要なのが、シークレットはスマホの中にしか無いという点。だからこそ──
- 端末を紛失・初期化すると、6桁を生成できなくなる
- 復旧のために バックアップコード や シークレット文字列の控え を別の安全な場所に保管しておく
- 機種変更時は、移行機能か再登録で 新しい端末にシークレットを移す
「スマホを変えたらログインできなくなった」の多くは、このシークレット移行を忘れたケースです。設定時に出るバックアップコードは、必ず保管しておきましょう。
30秒ごとに変わる理由と、時刻ズレの許容
なぜわざわざ30秒で区切るのか。これは 安全性と使い勝手のバランスです。
- 短すぎる … 入力中に切り替わって打ち直しが頻発する
- 長すぎる … 盗み見られた6桁が長く有効になり、悪用の窓が広がる
30秒はその折衷点で、RFC6238 の標準値です。
ただ、スマホとサーバの時計がぴったり同じとは限りません。数秒ズレると、ちょうど境目で番号が食い違います。そこでサーバ側は 前後の窓 も許容するのが一般的です。
| サーバが照合する範囲 | 意味 |
|---|---|
| 1つ前の30秒 | スマホの時計が少し遅れている場合に救済 |
| 現在の30秒 | 本来一致するべき値 |
| 1つ後の30秒 | スマホの時計が少し進んでいる場合に救済 |
この「前後1ステップ」を許すことで、多少の時刻ズレでもログインできます。逆に スマホの時計が大幅にズレると6桁が通らないので、その時は端末の時刻を自動設定に戻すのが解決策です。
SMSのワンタイムより安全な理由
「SMSで届く6桁」と「認証アプリの6桁」、見た目は同じですが安全性は別物です。
SMSは番号を 電波に乗せて届ける ので、次のような攻撃に弱い面があります。
- SIMスワップ … 攻撃者が携帯会社をだまして電話番号を乗っ取り、SMSを横取りする
- 傍受 … 通信経路上でSMSを盗み見る古典的な攻撃
一方 TOTP は、6桁を どこにも送信していません。シークレットは登録時の一度きりしか流れず、以降はスマホ内で計算するだけ。だから SIMスワップや傍受の影響を受けにくいのです。
ただし TOTP も万能ではありません。フィッシング には弱点が残ります。偽サイトに誘導されたユーザーが「今の6桁」を入力してしまうと、攻撃者がそれを本物のサイトへ即座に中継してログインできてしまう。6桁は30秒有効なので、その窓で抜かれるリスクは消えません。
この フィッシング耐性 まで備えたのが、次世代の パスキーです。パスキーは「サイトごとに鍵が紐づく」ため、偽サイトでは原理的に認証が成立しません。仕組みの違いは パスキーの仕組みを整える で整理しています。TOTP からの上位互換として理解しておくと、どちらを使うべきか判断しやすくなります。
実際にコード生成を見るには
文章で4ステップを追っても、やはり 動いているものを見る のが一番です。
TOTPを整える にシークレット(または otpauth:// URI)を入れると、
- 今この瞬間の6桁 と、次の切り替わりまでの残り秒数
- 30秒ごとに数字がカウントダウンとともに更新される様子
- 同じシークレットを別タブで開いても 同じ番号が出る(=通信なしで一致する証拠)
がその場で確認できます。「自分の認証アプリと同じ番号が出るか」を試すと、TOTP が本当に時刻とシークレットだけで動いていることが体感できます。
関連ツールとのシナジー
- 6桁の土台になる HMAC・ハッシュ計算を試すなら → ハッシュを整える
- 30秒ごとのコード生成を実際に観察するなら → TOTPを整える
- フィッシングにも強い次世代認証を理解するなら → パスキーの仕組みを整える
「ハッシュ → TOTP → パスキー」の順で触ると、認証の進化が一本の線でつながります。
まとめ
- 認証アプリの6桁は 共有シークレット + 30秒刻みの時刻 を HMAC-SHA1 にかけ、Dynamic Truncation で切り出した数字
- これが TOTP(RFC6238)。同じ材料が両側にあるから 通信なしでサーバと一致 する
- QRコードの正体は
otpauth://URI で、中にシークレットが入っている。紛失に備えてバックアップコードを保管 - 時計のズレは 前後の窓 で救済される。大幅にズレると6桁が通らない
- SMSより安全(SIMスワップ・傍受に強い)が、フィッシングで「今の6桁」を抜かれるリスクは残る → そこを塞ぐのが パスキー
- 仕組みを目で見るなら TOTPを整える が早い
「毎回入力しているあの6桁が何者か」を知っておくと、二段階認証の設定もバックアップも、ぐっと安心して扱えるようになります。