著者:関 勝寿
公開日:2016年3月26日 - 最終更新日:2022年12月31日
キーワード: security

Google アカウントの2段階認証プロセスでは、Google 認証システムをモバイル端末にインストールして、QRコード(バーコード)を読み込ませることで、30秒ごとに変化する6桁の確認コードを生成することができる。通常のパスワード認証に加え、確認コードによる認証をすることで、セキュリティを高めている。Amazon, Microsoft, Facebook, Dropbox, GitHub 等多くのサービスで同じシステムが採用されている。この仕組みについて記す。

概要

Google 認証システムで採用されているRFC 6238時間ベースのワンタイムパスワード (TOTP)は、サーバーとクライアントで共有する秘密鍵と現在時刻から、確認コードを計算するアルゴリズムである。

Google 認証システム

Google のサーバーがランダムに生成した80ビットの秘密鍵(シークレットキー)QR コードあるいは16文字のBase32文字列(A-Z, 2-7)としてウェブブラウザに表示し、クライアント(モバイル端末にインストールしたGoogle 認証システムのアプリ)で読み取ることで、サーバーとクライアントに同じ秘密鍵が保存される。現在時刻は30秒ごとに値が変わるカウンターに変換されてから確認コードの6桁の数字が計算されてアプリに表示されるため、30秒間は同じ確認コードが表示される仕組みとなっている。なお、確認コードの計算アルゴリズムについては最後に記す。

秘密鍵と確認コード

ある時刻においてカウンターがサーバーとクライアントで等しければ、サーバーとクライアントで秘密鍵を共有しているため同じ確認コードが計算されるはずであるから、サーバーとクライアントで確認コードの計算結果が一致するかどうかで認証ができるとするのが TOTP アルゴリズムである。ここで、サーバーとクライアントではそれぞれNTPのような仕組みによってある程度正確に現在時刻を得ることができるという前提であり、RFC 6238では通信のタイムラグ時刻のずれを調整するための仕組みを実装することが推奨されている。

秘密鍵をモバイルアプリで読み込ませた端末を持っていなければ認証が成功しないことから、持っているものでアカウントを保護する仕組みであると説明される。端末を持っていなければ、漏洩した確認コードを知り得たとしても、30秒後にはその確認コードは無効となって認証が成功しなくなるためである。しかし、たとえば中間者攻撃によって素早く漏洩した確認コードが使われれば認証に成功してしまう。30秒間は何度でも同じ確認コードを使えるという意味ではワンタイムではない。サーバー側で最後に認証に成功した時のカウンターを記憶しておき、一致した時にはそのカウンターでは2回目は認証に成功させないようにすれば、真の意味でワンタイムになると考えられるが、RFC 6238ではそのような仕組みは提案されていないようである。

秘密鍵が漏洩すれば任意の時刻における確認コードを生成できるため、秘密鍵は適切なアクセス権限を設定して保存する必要があり、できれば耐タンパー性のあるデバイスに保存することが望ましいとされている。

TOTP に対応するサービス

Google 認証システムを使ってTOTPの確認コードを生成できるサービスには、例えば次のようなサービスがある。このように多くのサービスで同じモバイルアプリを使用できることが、このシステムの利便性であろう。

TOTP を計算するモバイルアプリ

TOTPをサポートするモバイルアプリには、例えば次のようなものがある。

複数端末への秘密鍵の登録

サーバーがQRコードを生成した時に、複数の端末でアプリから同じQRコードを読み込ませれば、同じ秘密鍵が登録されるため、どの端末からも同じ確認コードを生成できる。端末の紛失や故障、ソフトウェアの不具合によってモバイルアプリが起動できなくなった時のことを考えると、可能であれば2つ以上の端末に同じ秘密鍵を登録するのが望ましい。SMSや音声通話による2段階認証を設定する手段も有効である(端末を紛失しても電話番号の契約が継続していれば新しい端末で復活可能なため)。

Google 認証システムのアプリでは、アプリに登録されている秘密鍵をQRコードによってエクスポートできる。Microsoft Authenticator では、秘密鍵を一括してクラウドにバックアップすることができる。

IIJ SmartKey は、2016年の時点でアプリに登録されている秘密鍵をQRコードでエクスポートできるほぼ唯一のアプリとして重宝して使っていたが、2020年現在、著者が所有している iPad の中の1つで IIJ SmartKey が起動しなくなったため、これからは Google と Microsoft のアプリを使うこととした。

確認コードの計算アルゴリズム

RFC 6238TOTPアルゴリズムは、サーバーとクライアントで共有する秘密鍵と、現在時刻から計算されるカウンターから、一意にトークン TOTP すなわち確認コードを計算するアルゴリズムであり、RFC 4226HOTP(HMAC ベースのワンタイムパスワード)に基づいている。具体的には次のように計算する。

  • K秘密鍵TC現在時刻UNIX時間)、X時間ステップ(秒)、T0カウント開始時刻(UNIX時間)、Nトークンの長さとする。また、ハッシュアルゴリズムを決める。デフォルトでは X=30, T0=0, N=6, ハッシュアルゴリズムは SHA-1 であり、Google 認証システムではこのデフォルトを使って計算をする。なお、HOTPでは秘密鍵は128ビット以上が必要で160ビットを推奨としているが、Google 認証システムでは80ビットである。
  • T = floor((TC - T0) / X) により、時刻T0からの経過時間に応じたカウンター T を64ビットの符号なし整数型で得る。ここで、floor は床関数であり、Tを整数型としておけば通常は自動的にfloor関数が適用される。
  • H = HMAC-SHA-1(K, T) により20バイトのハッシュ H を計算する。すなわち、HMAC-SHA-1 アルゴリズム (RFC 2104) によって秘密鍵KとメッセージTからハッシュHを計算する。
  • 下記の Truncate 関数を使い、TOTP = Truncate(H) として10進数N桁のトークン TOTP を計算する。

ここで Truncate 関数は RFC 4226 に定められている20バイト文字列から10進数N桁のトークンを得る次のような関数である。

  • 20バイト、すなわち160ビット文字列 String = String[0]…String[19] から31ビット文字列を得るDT関数 DT(String) を次のように定義する。String[19] の下位4ビットを符号なし整数に変換して Offset を得る(0 <= OffSet <= 15)。次に、P = String[OffSet]…String[OffSet+3] とする。Pは32ビットとなり、最上位ビットを除いた31ビットを DT(String) とする。
  • DT(String) を符号なし整数に変換した数字を Snum として、D = Snum mod 10^N を計算する。DはN桁以内の正の整数となる。DがN桁よりも少ない時には先頭に0を埋めて10進数N桁のトークンとしたものが、Truncate(String) である。