一言でいうと
TOTPとは、共有された秘密情報と現在時刻から、常に変化する一時的な数字のコードを生成するアルゴリズムのことで、ログイン時の第二の要素として使われます。
こいつが解決する問題
ぶっちゃけ、パスワードって必要悪ですよね。忘れるし、使い回すし、攻撃者はフィッシングやデータベース侵害、古き良き総当たり攻撃で、うんざりするほど巧みにパスワードを盗み出します。パスワードだけに頼るのは、玄関のドアに鍵をかけたのに、鍵を玄関マットの下に隠しておくようなもんです。
そこで登場するのが、多要素認証(MFA)、中でも一番よく使われる二要素認証(2FA)です。アイデアは、あなたが知っていること(パスワード)以上の方法で本人確認をすること。追加であなたが持っているもの(スマホなど)や、あなた自身であること(指紋など)も必要になります。
初期の2FAは、SMSでコードを受け取るのが一般的でした。何もないよりはマシでしたが、これは「SIMスワップ」攻撃に弱いんです。詐欺師があなたの携帯会社を説得して、あなたの電話番号を彼らのSIMカードに移させちゃう手口です。そうなったら最後、あなたの2FAコードは詐欺師の元へ。イケてないですよね。
そこで登場したのが、アプリベースの認証システム。不安定な電話網のセキュリティに頼る代わりに、デバイス上のアプリがローカルでコードを生成します。でも、あなたのGoogle Authenticatorアプリは、どうやってGitHubアカウント用のコードを生成するんでしょう?そして、あなたのMicrosoft Authenticatorは、どうやってDropboxと会話するんでしょう?
実はみんな、TOTP(Time-Based One-Time Password:タイムベース・ワンタイムパスワード)という、同じオープンスタンダードな言語を話しているんです。TOTPは、あなたの第二要素を特定の企業から切り離してくれる、縁の下の力持ち。準拠しているアプリならどれでも、準拠しているサービスで使える、オープンで相互運用可能な標準です。これは、単純なカウンターをベースにしていた初期の標準、HOTPの進化版ですが、時間という「動く部品」を使うことで、TOTPは格段にユーザーフレンドリーになりました。
裏側の仕組み
TOTPはまるで魔法のようです。一度QRコードをスキャンすれば、その後、あなたのスマホと地球の裏側にあるサーバーが、二度と通信することなく、30秒ごとに同じ6桁の数字に合意できるのですから。でもこれは魔法ではなく、マジで賢い暗号技術なんです。
そのレシピを分解してみましょう。
共有された秘密:王国の鍵
すべては一つの秘密から始まります。あなたがサービスで2FAを有効にすると、サーバーはランダムな秘密鍵(例えば、160ビットの純粋なランダムデータ)を生成します。この秘密は、あなたとサーバーだけが知っておくべき、唯一のものです。システム全体のセキュリティは、この秘密が守られるかどうかにかかっています。
もちろん、160ビットのランダムな文字列は、人間にとってあまりフレンドリーではありません。そこで、私たち人間が扱いやすいように、Base32という形式にエンコードされます。いとこのBase64とは違って、Base32はより小さな文字セットを使い、大文字小文字を区別せず、1とl、0とOのような紛らわしい文字を避けています。これにより、QRコードをスキャンできない場合でも、手で入力することが可能になります(それでも面倒ですが)。
典型的なBase32の秘密情報はこんな感じです:JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP
この秘密情報を認証アプリに取り込む最も一般的な方法は、特別なURIをエンコードしたQRコードを使うことです:
otpauth://totp/FlowingDev:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=FlowingDev
あなたのアプリはこれをスキャンし、発行者(FlowingDev)、アカウント名(user@example.com)、そして最も重要なsecret(秘密情報)を保存します。これで「握手」は完了です。
動的な要素:時間
TOTPの「T」はTime-Based(時間ベース)の略。これがコードを変化させる極めて重要な材料です。しかし、システムはミリ秒単位の正確な時刻を使いません。そんなものを同期するのは不可能ですから。代わりに、離散的なタイムステップを使います。
計算式はシンプルです:
TimeStep = floor(CurrentUnixTime / 30)
- CurrentUnixTime: 1970年1月1日(Unixエポック)から経過した秒数。
- / 30: タイムステップは、ほぼ例外なく30秒です。
この計算結果は、30秒ごとにしか変化しない整数になります。午前10:00:00のTimeStepがある値Tだとすると、午前10:00:29でもまだTのまま。しかし、午前10:00:30になると、T+1にインクリメントされます。このTimeStepが、私たちが認証しようとしている「メッセージ」になるわけです。
暗号の魔法:HMAC-SHA1
さて、静的な秘密情報と、動的なタイムステップを組み合わせます。これはHMAC(Hash-based Message Authentication Code)と呼ばれる暗号プリミティブを使って行われます。
HMAC_Result = HMAC-SHA1(SecretKey, TimeStep)
HMACは、安全な署名を作成する方法だと考えてください。秘密鍵と署名したいデータ(TimeStep)を受け取り、ハッシュ関数(この場合はお馴染みのSHA-1)を使ってごちゃ混ぜにします。
出力は20バイト(160ビット)のハッシュ値です。これは、その特定の秘密情報と、その特定のタイムステップに完全に固有の、長くて16進数の文字列です。秘密情報の1ビットでも変えるか、タイムステップが変わるまで30秒待つかすれば、まったく異なるハッシュ値が得られます。
SHA-1は証明書の署名など他の用途では弱点が知られていますが、HMACの仕組みの中で使う分には、まだ完全に安全だと考えられています。多くのモダンな実装では、より万全を期すためにHMAC-SHA256やHMAC-SHA512への移行が進んでいます。
切り捨て:6桁のコードへ
20バイトの16進文字列は、ユーザーフレンドリーな6桁のコードではありません。最後のステップは、このハッシュを実際に使える数字に「動的に切り詰める」ことです。
- オフセットを見つける: 20バイトのHMAC結果の最後の4ビット(ニブル)を見ます。これにより0から15までの数字が得られます。これが
offset(オフセット)です。 - 4バイトを取得する: 20バイトのハッシュの
offsetの位置から、次の4バイト(32ビット)を取得します。 - 最上位ビットをクリアする: この32ビットの塊に対して、
0x7FFFFFFFとのビット単位のAND(論理積)演算を行います。これにより、最初のビットが事実上取り除かれ、符号付き整数と符号なし整数の潜在的な問題を解決し、結果が常に正の数になることを保証します。 - 剰余の出番です! これで大きな31ビットの整数ができました。これを6桁の数字にするために、1,000,000(
10^6)で割った余りを取ります(剰余演算)。 - ゼロで埋める: その結果があなたのコードです!もし数字が、例えば
4521なら、先頭をゼロで埋めて(ゼロパディングして)004521にします。
以上です!あなたのスマホとサーバーの両方が、まったく同じ計算を実行します。あなたがコードを入力すると、サーバーは自分の計算結果とあなたのものを比較します。あなたのスマホとサーバーの間のわずかな時刻のズレを考慮して、サーバーは通常、現在のタイムステップ、その一つ前、そして一つ後のコードをチェックします。どれか一つでも一致すれば、ログイン成功です。
実話あれこれ
未遂に終わったGitHubアカウント乗っ取り
ある開発者がゾーンに入って集中していると、GitHubからセキュリティ問題を警告するメールが届きました。見た目は本物と寸分たがわぬメールです。一瞬の気の緩みでリンクをクリックし、ピクセル単位で完璧なGitHubのクローンログインページに飛ばされ、ユーザー名とパスワードを入力してしまいました。攻撃者はこれで彼の認証情報を手に入れました。フィッシング詐欺師はすぐさま本物のGitHubにログインしようとします。盗んだパスワードを入力すると…「二要素認証コードを入力してください」というプロンプトが表示されました。攻撃者は完全に手詰まりです。開発者のスマホは持っておらず、TOTPコードを生成するすべがありません。一方、開発者はGitHubから新しいログインに関する本物のメールを受け取り、自分のミスに気づき、すぐにパスワードを変更しました。
教訓: TOTPは重要なファイアウォールとして機能します。壊滅的なパスワード漏洩になりかねない事態を、無害なログイン失敗に変えてくれるのです。
オフラインでの緊急アクセス
あるネットワークエンジニアが、カンファレンスに向かう飛行機に乗っています。離陸から30分後、彼は重要な本番システムの状態を確認する必要に迫られました。機内Wi-Fiはなく、携帯の電波もゼロ。SMSやプッシュ通知ベースの2FAは役立たずです。しかし、彼のダッシュボードはTOTPで保護されていました。彼はノートPCを開き、ログインページを表示し、スマホの認証アプリを開いて6桁のコードを入力します。アプリはインターネット接続を一切必要としません。計算は自己完結しており、共有された秘密情報とスマホの内部時計だけに依存します。彼はログインし、客室乗務員がドリンクサービスを始める前に問題を解決しました。
教訓: TOTPが完全にオフラインで機能する能力は、どこにいても重要なアクセスを確保するための、信じられないほど堅牢で信頼性の高いものにしています。
クロスプラットフォーム移行の頭痛の種…を解決
長年のAndroidユーザーがiPhoneへの乗り換えを決意しました。彼はGoogle Authenticatorを開き、「新しいスマホに同期」ボタンがないことに愕然とします。30以上のアカウントが設定されており、そのすべてで2FAのリセット手続きをやり直すことを考えただけで悪夢でした。しかし、彼は自分なりのセキュリティポリシーを思い出しました。新しいサービスごとに、初期のQRコードをスクリーンショットとして暗号化されたパスワードマネージャーに保存していたのです。彼は新しいiPhoneに認証アプリをインストールし、パスワードマネージャーを開き、保存しておいたQRコードを一つずつ再スキャンしていきました。10分後、30すべてのアカウントが移行され、完璧に機能していました。
教訓: TOTPの秘密情報(Base32文字列またはQRコード)こそが本当の鍵です。秘密情報を安全にバックアップしておくことで、特定の認証アプリに依存することなく、真の所有権とポータビリティを手にすることができます。
よくある間違いと落とし穴
- 秘密情報を軽々しく扱うこと。 設定時に与えられるQRコードやBase32文字列は、それ自体が秘密鍵です。もし攻撃者にそれを手に入れられたら、彼らは永遠に有効なコードを生成できてしまいます。スクリーンショットを撮ってデスクトップに放置したり、自分宛にメールで送ったり、プレーンテキストファイルに保存したりしないでください。マスターパスワードのように扱いましょう。
- リカバリーコードの保存を忘れること。 2FAを有効にすると、ほとんどすべてのサービスが一度しか使えないリカバリーコードのセットを提供してくれます。それを印刷してください。そして安全な場所(本物の金庫、あるいは最低でもパスワードマネージャー)に保存してください。スマホをなくした場合、これらのコードが、面倒なサポートプロセスを経ずにアカウントに戻る唯一の方法です。
- 認証アプリがバックアップされていると思い込むこと。 多くのシンプルな認証アプリ(オリジナルのGoogle Authenticatorなど)は、秘密情報をその一台のデバイスにしか保存しません。スマホを紛失したり初期化したりすると、秘密情報は永遠に失われます。安全な暗号化クラウド同期を提供するアプリ(Authyや1Passwordなど)を使うか、手動で秘密情報をバックアップすることを徹底しましょう。
- 時刻のズレを無視すること。 TOTPは時間ベースです。もしあなたのスマホの時計が実際の時刻と1〜2分以上ずれていると、生成されるコードは拒否されます。スマホとコンピュータは常に「時刻を自動的に設定」にしておきましょう。有効なはずのコードが突然機能しなくなった時、だいたいこいつが犯人です。
なぜ知っておくべきか
もしあなたがユーザーアカウントを持つシステムを構築する開発者なら、MFAを実装すべきです。そしてTOTPは、アプリベースの2FAにおいて、クラス最高でオープンスタンダードな選択肢です。SMSより安全で、独自のプッシュ通知システムよりオープンです。秘密情報を生成し、otpauth:// URIを作成し、受け取ったトークンを検証する方法を理解することは、安全なアプリケーションを構築するための基本的なスキルです。
たとえあなたが自分でTOTPシステムを実装することがなくても、AWS、Cloudflare、GitHub、npmのようなプラットフォームで、あなたはそのユーザーです。裏側の魔法がどのように機能するかを知ることは、それをより安全に使いこなす力になります。初期の秘密情報を保護し、リカバリーコードを保存し、自分のニーズに合った認証アプリを選ぶことの重要性を理解する助けになります。あなたのデータと悪者たちの間に立ちはだかる、あの小さな6桁の数字の謎を解き明かしてくれるのです。
もっと詳しく
- RFC 6238: Time-Based One-Time Password Algorithm — TOTPの公式仕様書。
- RFC 4226: HMAC-Based One-Time Password Algorithm — TOTPがその上に構築された基盤であるHOTPの仕様書。
- RFC 4648: The Base16, Base32, and Base64 Data Encodings — 秘密情報の共有に使われるBase32エンコーディングを定義した仕様書。
- Key URI Format · google/google-authenticator Wiki — QRコードで使われる
otpauth://URIの事実上の標準。 - Time-based One-time Password - Wikipedia — このアルゴリズムのハイレベルな概要と歴史。