背景
二つの異なるウェブサイトに二要素認証を実装した。いわゆる管理画面で、プロダクション用とステージング用の二つ。 Label も Issuer も同一で Secret だけ異なる otpauth URI と QR コードが生成された*1。
Microsoft Authenticator アプリで QR コードを読み込んだところ、一つ目は問題なく登録できたが二つ目の登録時に警告メッセージが表示された。二要素認証アプリの画面に表示される「アカウント名」のような情報が同一だから重複と判定されているのだろう。幸い Secret の文字列がわかっていたので、手入力することで回避できた。
しかし、他の二要素認証アプリを使っている人は同様の問題が発生しなかった。アプリによってアカウントの重複(衝突, conflict)を検知する方法が異なるようだが、衝突検知方法や衝突検知時の振る舞いに決まりはあるのか?が気になったので調べた。
調べてわかったこと
30 秒ごとに 6 桁の数字が変わるあれは RFC 6238 - TOTP: Time-Based One-Time Password Algorithm という仕様に基づいている。
今回知りたかったのはそっちではなくて、アプリ側の仕様。それらしい RFC は見つけられず、かわりに Google Authenticator の GitHub Wiki ページを見つけた。
- Key Uri Format · google/google-authenticator Wiki · GitHub
- otpauth URI フォーマットの詳しい説明。これを読むと Label と issuer が衝突検知の鍵になっていそうとわかる。
- Conflicting Accounts · google/google-authenticator Wiki · GitHub
- アカウントの衝突を避けるための指針が書いてある。
以下の二つの otpauth URI は Label に含まれる issuer prefix も issuer parameter も同一で、 Secret だけが異なる*2。
- URI 1:
otpauth://totp/ACME%20Co:john@example.com?secret=7U5FG3XE6T7XPXGZSDX57LCNFNZZCF6D&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
- URI 2:
otpauth://totp/ACME%20Co:john@example.com?secret=SVY3WJKNE54W6RDANT5GOJTKU32FU3BE&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
Label | accountname | issuer prefix | issuer parameter | |
---|---|---|---|---|
URI 1 | ACME%20Co:john@example.com | john@example.com | ACME%20Co | ACME%20Co |
URI 2 | ACME%20Co:john@example.com | john@example.com | ACME%20Co | ACME%20Co |
Recommendation for code generation app developers に以下の記述がある。 issuer が同一の場合、既存アカウントの上書きは避けなければいけない、との指針があるが、具体的な振る舞いについては記載が無い。
The internal logic your app needs to avoid overriding existing entries with the same username that belong -or might belong- to completely different websites.
つまり、衝突を検知した時の振る舞いは実装依存ということだ。少なくとも以下の異なる振る舞いを発見した。
- 同名の別アカウントとして登録する
- 警告を表示し、上書きするか別名のアカウントとして登録するか選択させる
- 警告を表示し、上書きするか登録をやめるか選択させる
- アカウント名称の変更を促し、変更しない限り登録させない
- 黙って既存アカウントを上書きする
黙って上書きするアプリがあったら怖いな、と思ったら案の定あった(LastPass Authenticator)。挙動を理解した上で使うならともかく、上書きされていることに気づかなかったら大変だ。二要素認証アプリは「信頼と実績」で選ぶのが良いと思う。
実験
二要素認証アプリはたくさんあるが、いくつかピックアップして検証した。原則としてすべて iOS アプリでの挙動。
- Google Authenticator
- Microsoft Authenticator
- Authy
- LastPass Authenticator
- Duo Mobile
- IIJ SmartKey
検証に利用した otpauth URI は以下の二つ。
- URI 1:
otpauth://totp/ACME%20Co:john@example.com?secret=7U5FG3XE6T7XPXGZSDX57LCNFNZZCF6D&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
- URI 2:
otpauth://totp/ACME%20Co:john@example.com?secret=SVY3WJKNE54W6RDANT5GOJTKU32FU3BE&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
QR コードの生成には https://rootprojects.org/authenticator/ を利用した。
Google Authenticator
警告無く、同名の別アカウントとして登録された。
Android アプリの場合
URI 1 を登録済みの状態で URI 2 の QR コードをスキャンすると以下の警告が表示された。「両方のアカウントを保持」を選ぶと自動的に別名で保存された。「変更」を選ぶと既存の URI 1 アカウントが上書きされた。
Microsoft Authenticator
URI 1 を登録済みの状態で URI 2 の QR コードをスキャンすると以下の警告が表示された。「続行」を選ぶと既存の URI 1 アカウントが上書きされた。
Authy
警告無く、同名の別アカウントとして登録された。登録前に Account Nickname の入力画面が出るので、別名をつけることができる。
LastPass Authenticator
黙って上書きされた。びっくりしたので動画を撮った。 LastPass のサポートに報告した。
Duo Mobile
- https://apps.apple.com/jp/app/duo-mobile/id422663827
- バージョン 3.33.3
警告無く、同名の別アカウントとして登録された。
IIJ SmartKey
URI 1 を登録済みの状態で URI 2 の QR コードをスキャンし、登録を試みると以下の警告が表示された。サービス名を変更しないと登録できない。
考察
- LastPass Authenticator は既存アカウントを黙って上書きする。この挙動はかなり怖いので、これを使う特別な理由が無い限り、安全のために他の二要素認証アプリを利用した方が良いと思う。
- Microsoft Authenticator の挙動が最も説明的だが、初見殺しでもある。見慣れないダイアログが突然出現すると、びっくりして反射的にボタンを押してダイアログを閉じてしまうかもしれない。もし内容を読まずに「続行」してしまうと、何が起こったか分からない。
- Google Authenticator の Android 版の挙動も、ユーザーが取れる選択肢が多く、優れている。「両方のアカウントを保持」を選ぶと自動的に別名がつくので、後で見分けやすい。ダイアログが出るのでうっかり操作ミスをして事故るリスクがあるのは Microsoft Authenticator と同様。
- IIJ SmartKey の「上書き登録できない」という挙動が最も安全だ。欲を言えば、登録ボタンを押す前に警告が表示されると、さらに分かりやすいと思う。少し知識のあるユーザーは、既存アカウントとサービス名やアカウント名が同じだと気づいた場合、登録ボタンを押すのを躊躇するかもしれない。
まとめ
- 二要素認証アプリは issuer の値でアカウントを区別する。自分で otpauth URI を生成する場合は、 issuer にユニークな値を使うこと。
- アカウント衝突時の振る舞いはアプリの実装に依存する。自分で用意した otpauth URI を登録する場合は、二要素認証アプリの挙動を確かめておくと安全。
- アカウント衝突時は手入力を試すと回避できることがある。それでも無理なら別の二要素認証アプリを使う。