🎰

俺のワンタイムパスワードはいつ当たりが出るのか

2022/08/02に公開

EDIT: Google Authenticatorは 鍵を非暗号化状態でエクスポートできる ので表現を調整。

https://twitter.com/ceptree/status/1554033116179996672

... いいなぁ。。

というわけで調べてみました。2分くらいでできるよ。

やること

この種のワンタイムパスワード(TOTP)は、RFC https://datatracker.ietf.org/doc/html/rfc6238 で規定される関数で、

   Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer
   and represents the number of time steps between the initial counter
   time T0 and the current Unix time.

のように、秘密鍵 K と現在時刻 T で表現される関数となっている。このため、自分のTOTP秘密鍵とHOTPアルゴリズムの種別さえ知っていれば、任意の時刻についてワンタイムパスワードを計算できる。

よって、秘密鍵を入手してTOTPの値が 777777 になるような T をbrute force(総当たり)で探索すれば良い。

鍵の抽出

自分は全てのアカウントを1Passwordで管理しているので、1Passwordでアイテムを開いて、 編集... からワンタイムパスワードの鍵のURLを取り出せる。この secret= の部分が鍵にあたる。

この鍵はBase32エンコードされた鍵データのblobとなっている。ちなみに、URIの他の部分からHOTPのパラメーターを知ることもできる。細かい仕様は例えばGoogle AuthenticatorのWiki https://github.com/google/google-authenticator/wiki/Key-Uri-Format や Yubicoのドキュメント https://docs.yubico.com/yesdk/users-manual/application-oath/uri-string-format.html に記載がある。

探索

今回は Node.js で適当に探索した。 totp-generator が便利。

探索の実装

適当過ぎというかこんなん3〜4行で済ませろよという気はする。。

const secret = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"; // Change this
const totp = require("totp-generator");

const tokenconfig = {
    digits: 6,
    algorithm: "SHA-1",
    period: 30,
    timestamp: (new Date()).getTime()
};

let i = 0;
let n = 1;

for(;;){
    let token = totp(secret, tokenconfig);
    if(i == 100000){
        console.log(n,"*100k searched..");
        i = 0;
        n++;
    }
    if(token == "777777"){
        console.log("Found in",n-1,"*100k +",i);
        console.log((new Date(tokenconfig.timestamp)).toString(),
                     tokenconfig.timestamp,
                     token);
        break;
    }
    tokenconfig.timestamp += (tokenconfig.period * 1000);
    i++;
}

結果

1分ほどで結果が出た。

$ time node index.js
1 *100k searched..
2 *100k searched..
- snip -
20 *100k searched..
21 *100k searched..
Found in 21 *100k + 25484
Fri Aug 09 2024 13:22:25 GMT+0900 (日本標準時) 1723177345522 777777

real    1m7.435s

再来年の8月か...楽しみだなぁ... Googleカレンダーに入れなきゃ。

安全性

なんでこんな簡単で面白い事をみんなやらないの?というのは当然の疑問かもしれない。

アカウントとセキュリティ

当然、 ↑で使ったSecretは本物ではなくてデモサイト( https://rootprojects.org/authenticator/ )で生成したものを使っている。このため、仮に自分の1Passwordが破られても、このSecretでログインできるサイトはどこにも存在しない。フィッシング犯が偽サイトを作らない限りは。

冒頭のツイートのリプライに

https://twitter.com/toshiki02499932/status/1554304903811837952

が有るが、コンピューターは博打をやらないので 777777491068 のようなランダムな数字の間には特に感情的な差は生じない。また数学的にも差が無い(= 選ばれるTOTPの値には有意な偏りが無い)と現時点では信じられている。このため、ワンタイムパスワードに採用された数字そのものでセキュリティが脆弱化することは無く、そもそもTOTPによるワンタイムパスワードは一定確率で保護に失敗するものであって、そのリスクは織り込み済と言える。

(通常のパスワードやPINのように人間が入力しやすいものを除外するという発想を持った要素(something you know)も存在するが、それらは人間が自分で設定するものであるからで、機械が生成して人間に決定権のないワンタイムパスワード(something you have)でそのような操作を行うことはないと言える。)

ただし、 予告ホームランは不味い 。つまり、この記事のように "2024年8月9日13時22分にワンタイムパスワードが777777になるので嬉しい!" みたいな書き込みをすると(本人がホームラン級のバカというのは認証ファクターにならないため、)当該時刻においては折角の2要素認証が1要素認証になってしまう。

では、冒頭のツイートのようにホームランを事後報告するのはどうだろうか。これについては以前書いた( https://qiita.com/okuoku/items/3cd283a28ff1bbc6d73f )ように基本的には危険は無いと思っている。つまり、これが危険になるのは:

  1. ワンタイムパスワードが 777777 であるうちに当人がログインできそうな2段階認証なサイトに盗んだパスワードを使って片端からログインできる (猶予は30秒x3[1]程度)
  2. ↑ のスクリプトの逆、つまり時刻とワンタイムパスワードを固定して secret を探索できる。

のいずれかが必要になる。前者は90秒ほどツイートを我慢すれば良い。後者は鍵の空間は128bitあるのでbrute forceするのは多分困難だろう。SHA1は危殆化しているがHMACの方は依然決定的な攻撃方法がない。

もっとも、画面から使用しているパスワードマネージャを特定され、 それが狙われる ことが考えられる。冒頭のツイートの場合、画面の感じからiOS上のMicrosoft Authenticatorではないだろうか。その場合、MSアカウントでログインしているデバイスが狙われるようなケースが問題になる。Microsoft AuthenticatorはMSアカウントを使用して鍵を同期する機能があるため、任意のデバイス上でMSアカウントが危殆化した場合は、そこに登録されていた全てのアカウントも危殆化[2]する。 もっとも 前回のエントリ で言及したような高度なゼロクリック攻撃を受けたりでもしない限りは安全だろう。

もともと、6ケタのワンタイムパスワードの場合、3/100万 = 1/33万[3]の可能性で秘密鍵を知らない人でも通してしまう可能性がある。その実現可能性よりも十分低いと考えられるものは脅威と見做す必要は無いといえる。

(1/33万でも十分ありえそうな確率に見えるが、例えば Windowsの生体認証は1/10万程度の他人受入率でヨシ としていて明かに安全性よりも利便性を取っている[4]。)

秘密鍵のエクスポート可能性の問題

今回はワンタイムパスワードの秘密鍵を1Passwordからコピペしたという設定で記事を書いているが、実際、これは本物のアカウントでも可能になっている。

パスワードマネージャのGUIから直接これを行えてしまって良いのかというのは疑問で、個人的にはダメな実装だと思う。実際、 Google等が提供している 通常の認証器(Authenticatorアプリ)は秘密鍵を表示する操作は存在しないようになっており、秘密鍵が"空中を漂う"のはアカウントの登録時のみとなっている。

1Password 8は更に悪いことに設定から"デバッグツールを表示"を有効にすると編集ボタンを押さなくても"アイテムJSONをコピー"から一発でエントリの全データをコピーでき、そこには当然ワンタイムパスワードのシークレットも含まれている。

例えばFacebookは開発者ツールを開くと↓のように警告 https://www.facebook.com/selfxss を表示するが、1Passwordにはこの種の警告が存在しない。

1Passwordはファミリープランを提供するなど一般消費者も想定したマーケティングを打っているが、割と開発者セントリックなプロダクトになっておりサブスクリプションを買ってる身としてはちょっと心許ない。

再現性(オチ)

... で、冒頭のツイートは多分事前に 777777 になる時点が既知であるような秘密鍵を用意して認証器に設定したと考えられる。↑のコードでは秘密鍵を固定して時刻を動かしているが、時刻を固定して秘密鍵の方を動かせば(おそらく同様に1分程度で)同じことができる。

(そもそも画像をPhotoshopした可能性があるじゃんというのを置いておいたとしても、)自分が本当に幸運の持ち主であることを証明する良い方法は無いとも言える。ある時刻において 777777 のワンタイムパスワードを出力する秘密鍵は原理的に 無数に 存在し、十分に早いコードを書けばリアルタイムにそのような秘密鍵を提供することも可能だろう。

かんそう

まぁ多分同ネタ多数だよね。

ワンタイムパスワードとパスワードを同じパスワードマネージャで管理して良いのか問題

1Passwordはワンタイムパスワード(TOTP)の秘密鍵とパスワードを同じデータベースに登録できるため、2要素認証もクソもない状況になっている。もっとも、AppleのキーチェーンでもiOS 15以降同様にTOTPの秘密鍵を登録できる("確認コード" と呼ばれる)。

これは2個の鍵を同じ箱に入れていると言えるので、直感的には間違ったデザインに見える。実際、パスワードマネージャの態度としては "需要が有るので仕方なく" という表現になっている。

更に、Lastpassは1Password同様に TOTPの秘密鍵を登録する機能を持つが 、企業の管理者はポリシでそれを無効にできる。

通常、Webサイトはワンタイムパスワードのことを"2要素認証"とは呼ばずに"2段階認証"としている。ユーザが指定したパスワードと同時に、Webサイトが指定した秘密鍵を提示する2段階で良しとしている -- とも言える。

認証器: 新たな囲い込みの戦場

今回のネタは、認証器がオープンスタンダードで、かつ認証情報のやりとりが容易だからこそ成立している。しかし、業界としては囲い込みに進んでいて、それにより、 今回のようなラッキーナンバー探しは面子とかコマ廻しと同様の懐しい遊びになっていく ように思える。

この5月にApple、Google、Microsoftの3者はWebAuthnのパスワードレスサインインを自社プラットフォームでサポートしていくことを表明した。

https://www.publickey1.jp/blog/22/applegoogle2_pcbluetooth2.html

これに関してはAppleがそのプレスリリースで対応方針を説明している:

https://www.apple.com/jp/newsroom/2022/05/apple-google-and-microsoft-commit-to-expanded-support-for-fido-standard/

これらの新機能は、来年中にはApple、Google、Microsoftのプラットフォームで利用できるようになる予定です。

保存された認証情報をプラットフォームを越えてやりとりする方法は通常提供されないので、このパスワードレス機能は新たなベンダロックインとして機能することになる。今、Appleは"Sign in with Apple"を自社プラットフォームのアプリに関連するサイトにしか展開できていないが、これに対応することで、どんなサイトも事実上"Sign in with Apple"にできる。

Appleのプレスリリースには

ユーザーは、使用しているOSプラットフォームやブラウザを問わず、自分のモバイルデバイスのFIDO認証を利用して、近くにあるデバイス上でアプリケーションやウェブサイトにサインインできます。

と有るが、この "OSプラットフォームやブラウザを問わ" ないのはWebブラウザが動いているデバイスのことであって、モバイルデバイスに含まれる認証情報の方は一生プラットフォームに幽閉されることになる。

今回のような秘密鍵ベースのワンタイムパスワードは、誰でも実装でき、KeePassや1Passwordのように秘密鍵の融通を許しているパスワードマネージャも存在するため、囲い込みには適さない。このため、徐々にセキュリティ保護やユーザビリティの名目で廃れていくのではないかと思う。

もちろん1Passwordも滅びるのを待っているわけではなく、短い動画を公開して自社の対応方針をアピールしている。

https://blog.1password.com/1password-is-joining-the-fido-alliance/

動画ではデスクトップ上での対応について示している。モバイルについても、AppleやGoogleはストアのフェアネスについて常に攻撃に晒されているので、1Passwordが同等のインテグレーションをできるようにAPI面で配慮するだろう。

... もっとも、残念ながら、この種の機構については 囲い込みこそベストプラクティス と言わざるを得ない。iPhoneにせよAndroidにせよ、携帯電話は十分にセキュアなセキュアエレメントを搭載し、所有と生体認証(顔認証や指紋認証)の2要素による認証を常に提供している。セキュアエレメントの鍵は本質的にプラットフォーマーのみが利用できるもので、1Passwordのような3rd partyがそれにアクセスできるかどうかは、AppleやGoogleの企業としての誠実さに懸かっていると言える。

よって消費者向けの1Passwordがこの先生きのこるかどうかは不透明だが、個人的にはセキュリティは多少犠牲にして1Passwordを買っている。自分のような誠実な一般市民が高度な攻撃のターゲットになる可能性よりも、AppleやGoogleのアカウントがbanされる危険性のほうがずっと高いとみていて、メールやパスワードマネージャのような重要な資産はアカウントの寿命に紐付かないように管理している。

脚注
  1. ある瞬間に認証を通過するコードは"画面に表示されるもの"と、時刻のズレを補償するために"その前後"の3つあると考えられるため。1(直前)+1(今)+1(直後)。 ↩︎

  2. 実際には2要素認証によって保護されているため、単にログインパスワードを盗むだけでは不十分で、実際にデバイスを侵害する必要がある。 ↩︎

  3. 6桁の数は 000000999999 の100万個あり、それらが十分にランダムに使われると現在の数学では仮定できる。TOTPの仕様である RFC6238 では、より多くのケタ数も許容しているものの、通常は6ケタで使用される。 ↩︎

  4. これは NIST SP800シリーズのような規格でも通常のOTPに比べて生体認証には細かい条件を追加している ように、割と一般的な運用方針となっている。例えば、携帯電話の指紋認証は電源の投入後1度は正しいパスコードを入力しないと機能しないようになっている。このため、携帯電話は指紋認証をパスコードよりも信頼していないと言える。 ↩︎

Discussion