TOTP: Time-Based One-Time Password AlgorithmのRFC 6238を読んだ

こんにちは、AWS担当のwakです。

弊社のAWSのIAMユーザーアカウントは(もちろん)スマホアプリを使った2段階認証で保護しています。また、ルートアカウントは(もちろん)ハードウェアMFAデバイスを使った2段階認証で保護しています。しかしこのMFAデバイス、割と頻繁に認証に失敗して再同期が必要となってしまいます。月に1回~2回程度は使っているためか、こちらの記事のように

qiita.com

「再同期が出来ず、Amazonに電話で連絡」するような状態に陥ったことはないのですが、それにしてもなんでこうなるのか、また再同期とは何をしているのかとは少し疑問に思っていました。

……という前振りのもと、時刻ベースのワンタイムパスワードアルゴリズム(TOTP: Time-based One-time Password Algorithm)について定めたRFC 6238を読んだのでネタにします。

f:id:nurenezumi:20151105105639j:plain

ちゃんと我慢ができるお利口な猫

TOTPの簡単な説明

最初にシステムで決めておくものは次の通りです。これは固定値となります。

  • 何秒おきにOTP(One-Time Password)が変わるかを決めた数値X(30秒が推奨されています。またこの値をtime stepと呼びます)
  • OTPの桁数(6桁が多い)
  • ハッシュ関数アルゴリズム

そして認証者(verifier: サーバーとか)と証明者(prover: スマホアプリとかハードウェアMFAデバイスとかのトークンを持っている人)は、これに加えて

  • 最初に決めた共通鍵(ただの乱数列)。QRコードに含まれていたり、物理MFAデバイスのラベルに書いてあったりする。最低128ビットの長さが必要

をともに持っておきます。ただ、この共通鍵を認証時にそのまま送信してしまうとただの「2個目のパスワード」と同じことになってしまうので、時刻という要素を混ぜてハッシュを取り、さらに扱いやすいように情報量を適当なところまで減らして、仮に盗聴されても鍵そのものを推測されることを不可能にするわけです。

TOTPはこの4つの要素+現在時刻だけで決まります。たとえば現在時刻が2015/11/6 9:32:59だとしますと、

  1. 現在時刻をUNIX timeで取得 (1,446,769,979)
  2. 1をXで割る(48,225,665)
  3. 2と共通鍵を合わせてハッシュ関数に突っ込む
  4. 出てきた値を非負の整数だとみなして、1,000,000で割った余りを計算する*1

で6桁のOTPが出てきます。これら4つの要素と時計は認証者も証明者も同じものを持っているはずですから、同じように計算すれば必ず同じ結果が出てくるはずです。認証者はこれを付き合わせて、一致していれば共通鍵を知っている相手、つまり本物の証明者だとみなすという形で認証を行います。

許容する幅

このようにOTPを算出するには正確な時計が必須です。しかしスマートフォンは時計のズレを簡単に補正して常に正しい時刻を保つことができますが、ハードウェアMFAデバイスではそうはいきません。「一般的なクォーツ時計の誤差は1ヶ月で15-30秒程度」だそうなので(Wikipediaより)、デバイスに内蔵された時計もこの程度はズレてしまう可能性があります。

そこでRFC 6238ではOTPの許容幅と自動再同期についても触れられています。たとえばあるシステムで、18時頃のOTPが

  • 18:00:00~18:00:29 : 111111
  • 18:00:30~18:00:59 : 222222
  • 18:01:00~18:01:29 : 333333
  • 18:01:30~18:01:59 : 444444
  • 18:02:00~18:02:29 : 555555
  • 18:03:30~18:03:59 : 666666

となっていたとしましょう。そして時刻18:01:41(つまり正しいOTPは444444)の時点で、証明者から222222が送られてきたとします。

認証者は、あらかじめ「2回分前のOTPと、1回分先のOTPまでは受け付ける」のようなポリシーを定めておきます。つまり18:01:41の時点で認証が要求された場合、

  • 2回分前のOTP(222222
  • 1回分前のOTP(333333
  • 現在のOTP(444444
  • 1回分先のOTP(555555

の4個のOTPを計算し、どれか一つとでも一致すればOKとみなすわけです*2。また、その範囲から外れた111111666666は誤ったOTPだとして認証を拒否することになります。

tech.sanwasystem.com

こちらの記事の最終行にあるVerificationWindowはこの幅を指定しているわけですね。

自動再同期

さてこの場合、証明者からは2回分前の(つまり1分前の)OTPが送られてきました。これはおそらく証明者が持つトークンの時計が1分程度遅れていることを意味していると思われます。そこで認証者は、今後このトークンを使った認証を行う場合は1分前の時刻を基準にOTPを算出するように記録しておくことができます。これが自動再同期です。この仕組みが実装されているシステムでは、ポンコツな時計を内蔵したデバイスでも、前回認証成功時からのズレが一定範囲におさまっていれば問題なく利用し続けることができます。

眠らせておくと面倒かも

上述の仕組みを実装しているシステムであっても、このように認証を行うたびに同期が行われて時計のズレが補正されてゆく仕組みになっているため、トークンを長時間使わないでいると時計のズレは次第に広がり、最終的には許容幅から外れてしまいます。また自動再同期がなければ*3、いかに頻繁に利用していようとも、トークンの時計が正確な時刻から一定以上ズレた時点で認証に失敗します。こうなると再同期が必要になったり、あるいはそもそも認証が全く通らなくなったりしてしまいます。私が頻繁にAWSで再同期を求められるのはこれが理由でした。

これを防ぐには、時計が正確な(そう期待される)タイプのデバイスを選んで購入する、そもそもハードウェアMFAデバイスを諦めてスマートフォンを使う、頻繁にログインしておく*4、といった対策があり、会社ごとのポリシーによっていずれかを選ぶことになるでしょう。使わないまま金庫に長時間保存しておいたりすると、いざ必要なときに使えないという事態に陥りかねませんので、ここはAWSに限らず、ハードウェアMFAデバイスを使う上での注意点となると思いました。

*1:OTPが6桁の場合。また最上位ビットを無視を無視することとRFC 4226に書いてあります

*2:むやみに広げてはいけません。この場合だと、第3者が適当に入力したとしても100万分の5の確率でヒットしてしまうことになります

*3:この仕組みは必須とはされていません。上で触れた記事で利用したOTP-Sharpでも実装されていません

*4:自動再同期が実装されているシステムでのみ有効