PHP で2段階認証を実装してみて分かったこと

最近、Termius という SSH クライアントを購入しました。
ちょっとお高いですが、Windows, Mac, Android, iOS とマルチプラットフォームなので、デスクトップ機(Windows)、スマホ(プライベート用の Xperia、仕事用の iPhone)にインストールしていろいろ遊んでます。

この Termius ですが、Authy という2段階認証アプリを使用します。Google Authenticator じゃダメなのかなーと思っていたら、Authy は完全互換性がある上にマルチデバイスでアカウント同期などもできます。iPhone でコードを表示させておいて Android でコード送信、なんてことも可能。

ここで気になり始めたのが、「そもそも2段階認証ってどういうことやってんの」という疑問。
実際に作ってみた。

デモ

GitHub : https://github.com/RiinaKw/2fa-example
デモ : https://riina-k.dev/2fa-example/demo/

デモを動かすと QR コードが表示される

こんな感じで QR コードが表示され、Google Authenticator や Authy で読み込みます。するとアプリ上に6桁のトークンが表示されます。(アプリのポリシーか何かでスクショ撮れなかったので、物理スクショで)

QR コードを読み込むとこうなる

この番号を QR コードの下にある入力フォームに打ち込んで、エンター。congratulations と表示されれば、めでたく認証成功です。

仕組み

結局のところ何をやっているのかというと、プログラム側で決め打ちした秘密鍵の文字列を QR コードでスマホにコピーして、現在時刻と鍵をごちゃまぜにして6桁のトークンを生成します。PHP でもスマホでも同じ処理を行ない、数字が一致すれば認証成功というわけです。

ここで気付いた。Authy にアカウント移行作業をしていて、「今までのと同じ QR コード出してくれないかな、2段階認証を一旦切るのめんどい」と思っていたのですが、それができない理由は秘密鍵の扱いでした。

QR コードの本当の中身

実はこの QR コード、認証アプリではなく通常の QR コードリーダーアプリで見ると分かるのですが、秘密鍵をそのまま書いてます。今回の例では TahiriVeila という文字列を秘密鍵に指定しています。つまり、この秘密鍵さえ分かってしまえば、誰でもトークンを算出することができてしまいます。数字が分かるということは誰でもログインができるのと同じこと、セキュリティなんて皆無になってしまいます。
ということは、この秘密鍵をどう扱うかがセキュリティ上の最大のポイントなのです。

実際にはどう運用しているのか

ここで「一旦2段階認証をオンにすると、オフにしてオンにし直さないと QR コードが表示されない」ということを思い出してみましょう。そのときに「秘密鍵をランダムで生成」してすぐ消すことで、サーバとスマホだけが秘密鍵を知っている状態にしています。
セキュリティ業界では秘密鍵の受け渡しってものすごい難所なのです。

サーバとスマホしか知らない秘密鍵を元に、それぞれでトークン(ワンタイムパスワード)を別個に生成して照合する。これで何が保証されるかというと、「このスマホはユーザが所有していること」。言い換えると他人に盗まれたら乗っ取りが成立してしまいます。そこで盗まれても大丈夫なように、認証アプリ自体に指紋などの生体認証を組み込みことで、「本人であること」を保証しているのです。
さっきあった「スクショ撮れない」という件も、「本人でなくとも見れてしまうから」という理由で禁止しているんだと思います。(つまり複数端末持ってるとセキュリティがヤバry)

おわりに

まあ複数端末に同じ設定を入れていたら、1台しか持ってない場合と2台持っている場合では失くす確率は単純計算で倍になるので、それだけセキュリティは低下しているということです。
利便性とセキュリティはトレードオフ。ガチガチに縛り付けてしまっても使い勝手が悪くなるので、結局は「どこを気にするか」に帰着するんだと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です