ウェブサービスにおける認証の話
プログラミング::web
序
ウェブサービスにおいては、なにかしらのユーザー認証が必要である(本当か?)。
ウェブサービスにおける認証は主に2つの要素がある。 ひとつは、利用者を特定することだ。認証を通過したということは、その者は認証された人物である、として扱う。 もうひとつは、その利用者の権限を参照することだ。
本来の利用者でない者が認証を通過した場合、一般には「不正利用」と呼ばれるが、システムから見れば認証を通過した以上は正当なユーザーだと見做すものである。 実際に当人でないとしても、代理人である(法的に見て妥当である)という可能性も排除できないし、それはシステムレベルの問題ではない。 システムレベルでの「不正利用」とは認証をバイパスすること(脆弱性を利用して認証を経ずに利用したり、あるいは認証情報を獲得する不正な方法が存在したり)を意味する。
だが、現実には(本来本人の責任であるが)本人の意図しない利用がなされることはユーザーの不利益であるとみなされる。そして、(たとえサービス提供側に責任がないとしても)なぜかサービス提供者が責められるのだ。
ここではそんなウェブサービスの認証について多角的に取り扱う。
本記事は認証に関わるキーワードを並べてコメントしたものであり、それぞれが同じレイヤーにあるというわけでもないので、ご留意いただきたい。 主に認証を取り扱うときに留意すべき知識の一端と捉えていただければ幸いである。
パスワード
ユーザーIDとパスワードというのは古典的な知識ベースの認証である。
ユーザーIDは単なるユニークな文字列であることもあれば、eメールアドレスや電話番号であることもある。 ユーザーIDが秘密であれば実用上のセキュリティは僅かに向上するが、ユーザーIDは保たれるべき秘密ではないため、セキュリティ要素ではない。
パスワードは様々な話があるが、現代においてどのようにすべきかは非常に明確である
- 任意の文字列を
- Bcryptによって不可逆に暗号化して保管し
- 通常変更を求めない
任意の文字列で重要なのは、使える文字種を限定したり、文字数を限定したりしないという原則である。 そもそもシステム側はパスワードとして与えられた文字列に干渉せず、取り扱うのはBCryptを経たもののみにしたほうが良い。
もしも脆弱なパスワード(辞書上にあるパスワードや短すぎるパスワード)を制限するのであれば、サーバーで制限するよりもクライアントで完結してチェックしたほうが良い。 それを突破してまで脆弱なパスワードを登録する人は、そのようにしたいと考えている人なのだから、自己責任であるべきだ。
パスワードは古典的なセキュリティだが、適切に取り扱えば(また、堅牢なパスワードを使い、適切なパスワード管理がなされていれば)強度はかなり高く、むしろセッションハイジャックのような認証に関わらない形でセキュリティを破られる可能性のほうが高い。
にも関わらずパスワードが愛されないのは
- 使いまわすことなく
- 十分な長さを持つ十分なランダムな文字列を使い
- 他の人の目に決して触れないように取り扱う
というパスワードを安全に保つ原則を守る人がとても少ないからだ。
つまり、機構としてのパスワードに問題があるわけではないのだが、それを扱う人間の方に問題があるので、人間を脆弱性にしない仕組みを使おう、という話になりやすい。
パスワードを使いつつその問題を解決する方法として、ブラウザが生成し、ブラウザに保存するという技術がある。 だが、これはパスワードを知識ベースの認証から所有ベースの認証に変えているものであり、ユーザーがコントロールできない状態になってしまう。 ブラウザ上にある(正確にはブラウザのプロバイダーが保有する)データというのは所有ベースのものとしてはかなり不安定な部類に入る。 これは現実的にセキュアというよりも、ブラウザの囲い込みというニュアンスのほうが強い。
それよりは、パスワードマネージャを使うほうがずっと良い。これも同様に生成まで担わせることができるものもあり、ものにもよるが大抵は独立している。
一例としてLinuxのDBus Secret Service APIを使うことを考えてみよう。 まず何らかの形で堅牢なパスワードを生成する。生成したパスワードはSecret Service API(を使うソフトウェア)を通じて任意の(そのサービスに固有の)キーでストアする。 これ以降はサービスに対してログインが必要になるたびにSecret Service API(を使うソフトウェア)を介してパスワードを獲得し、入力/送信する。
このAPIを介してパスワードを獲得するのに通常パスワードは必要なく、一般的にはログインを必要とする。
実際、secret-tool
はログインさえしていればSecret Service
APIを通じてパスワードを引き出すことができる。
ログインしていない状態ではディスク上のデータは暗号化されてストアされているため持ち出すことはできない。
第三者がこのパスワードを入手するには、ストアされているマシンに(不正に、当該ユーザーで)ログインする必要がある。
この仕組みはユーザーはパスワードの具体的な内容を知る機会がなく、条件はそのマシン、そのユーザーであり、所有ベースになる。 他のマシンでも使えるようにしようと思うと、エクスポート/インポートなど別の仕組みが必要になる。 同期などという話になると安全な同期方法を実装するのは難しくなるので余計なことをする話になってしまう。
秘密の質問
非常に悪いものだ。 普通に使うとその知識は「他人が知り得ないもの」ではない。
一般的にパスワードリカバリーに使うが、パスワードリカバリーに使えるということはこの秘密の質問さえクリアできれば認証を突破できるということになる。パスワードよりも弱く、ただ面倒なだけだ。
一方、パスワード以上に強固な、ランダムな文字列を使った場合、単純にパスワードリカバリーを捨てるということになる。 いずれにせよ、ユーザーに悪手を強いることになる。
時代遅れとされているのだが、なぜかMicrosoftは今更になってWindowsに必須にしている。 また、サウンドハウスはログインするたびに秘密の質問を要求する。
電話番号
SMS、あるいは電話によって認証を行う方法だ。 スマートフォンを軸にしたアプリやソーシャル系のサービスに多いが、Google, Yahooなども登録時必須で要求する。 ちなみに、現在はVivaldiも要求する。
SMSではリンクを送信することもあるが、どちらかというとPINを送信することが多い。 電話の場合はほとんどPIN。
ログイン時まで電話認証になっているようなものは少ない。 通信経路がインターネット回線ではないため、実現のハードルも高い。
電話番号認証を行う最大の理由は故人の実在の証明だが、電話番号がひとりひとつあるとは限らないため、あまり良いとは言えない。 特に、アカウントを電話番号に紐付ける必要がそれほどないにも関わらず、認証に電話番号を要求するのはかなりevilな感じがする。
この方法は認証を電話へのアクセスに委ねている。
eメール
eメール認証は、登録時にはアクティベーションリンクを送ることはよくあるが、eメールで認証そのものを行うことはあまりない。
eメールでの認証は、ログインリンク、あるいはPINをeメールに送信する。 基本的にこれらは短い有効期限を持つワンタイムなものである。
パスワードリカバリがeメールにパスワード再設定メールを送るだけのものであるならば、eメールにアクセスできれば必ず認証を突破できるため、パスワードを使わずにeメール認証にしたほうがセキュリティは向上する。
eメール認証はパスワードの管理よりも面倒がないと考える人がいる一方で、eメール認証を非常に手間に感じる人もいる。 特に厄介なのは、サービスを利用しようとしているデバイスがメールを受け取れるデバイスと同一であるとは限らない、という点だ。
eメールはスマートフォンだけ、あるいはPCだけということもありえるだろう。 私はWindows系のサービス(DTM関係はeメール認証の割合がとても多い)だと特に困る。私はWindowsを信用していない(そもそも、ディスクの暗号化がなされていないシステムでeメールを取り扱うのはセキュリティ方針上ダメである)ので、Windows上でeメールを扱うことはない。 このために、別のLinux PCを起動し、Linux上でeメールを受け取り、そのリンクをsocatで送信、Windows上のBashを使って受け取る、という非常に手間な手順を踏んでいる。 ネットカフェなどでは利用不可能、というようなこともあるだろう。
eメール認証を行うなら、PINにすべきだ。
この方法は認証をeメールアカウントに委ねている。
OpenID/OAuth
提供する側でないのであれば、認証そのものを他のサービスに委ねるというものである。 場合によっては個人の特定を委ねるという意味もある。
なんでもかんでもログインし、ログインしっぱなしにする人であれば便利に感じるだろうが、これは非常にリスクが高い危険な運用である。このような人がアカウント乗っ取り被害に遭っても自業自得と言うよりほかにない。
また、自身のサービスのアカウントの管理を他者に委ねることは、危険でもある。 実際、Twitterが大規模にTwitterアプリケーションを登録しているアカウントをBANした際には、TwitterのOAuthを使っているサービスがログイン不能になるといったことがあった。
OAuthを推進する人の立場は、「デジタルアイデンティティ」、つまり個人を特定し、個人情報を共有しようという考えからで、「インターネット上の人物であるべからず」という考え方に寄っている。 これは、電話番号を使うというのと似通った要素で、Googleのように電話番号を必須として個人=実社会の主体を特定することをスタート地点にしている。ちなみに、Twitterも現在は電話番号を登録しないと、登録を行っている最中に凍結されてしまう。
OAuthの採用は、様々なサービスにログインしっぱなしにするというセキュリティ面でも、個人情報を収集し共有するというプライバシー面でも私のポリシーに反するものであるから、私はこれに賛同しない。
デバイス
デバイス認証は、「既に有効であるアカウントが動作しているデバイス」を要求する所有ベースの認証である。デバイスを追加したり、変更したりするには既存のデバイスを必要とする。
アカウントの認証にデバイスを必須とするものは稀だが、デバイスがあることで認証をパスできるものは多い。Telegramが必須に近いが、SMSを用いてリカバーは可能。 デバイス認証を必須としているものでよく知られるのはLINEだろう。
デバイス認証が唯一、あるいは必須の要素であるものは、登録と認証は別のメソッドが使われる。例えばTelegramは電話番号によってアカウントを作成する。
デバイス認証の最大の難点は、デバイスの故障、あるいは紛失によってデバイスにアクセスできなくなった場合のリカバーが困難なことだ。 今はどうか知らないが、LINEは基本的にカスタマーサポートへの連絡が必要であった。 また、リカバーできるようにしてしまうと、デバイス認証をバイパスする手段があることになり、デバイス認証がセキュリティを担保するものではなくなってしまう。
秘密鍵
秘密鍵認証は「デバイスに秘密鍵が登録されていれば認証される」という、所有ベースの認証である。デバイス認証に近いが、バックアップが可能だ。
例えばSessionメッセンジャーがこれを利用する。コピーすることによって(というかフレーズを入力することで)、他のデバイスでも同じSessionIDを共有することが可能だ。 フレーズを覚えることを前提にしてしまえば、知識ベースの認証と化してしまうこともできる。
秘密鍵認証のメリットのひとつとして、それ自体を暗号鍵として使いうということがある。 もちろん、秘密鍵に何を使うかによるが、認証がデータの所有であるため、暗号鍵の所有を認証にすれば、そのまま暗号化に利用できる。そして、暗号化のメカニズムを認証にそのまま使うこともできる。
例えば、PGPは署名と暗号化の2つが可能である。 そこで、ユーザーIDにPGP秘密鍵で署名して送信し、サーバーはそのユーザーIDの公開鍵で署名を検証し、成功すれば認証されたものとして扱う、といったことが可能だ。
この方法は認証の主要な部分を完成度の高い既存のものに担わせることができるため、かなりセキュアな方法であると見ることができる。一方、単にパスワードをファイルとして置いておくのと変わらないようなものであるとしたならば、ハックされないメカニズムを用意する必要があり、パスワードと同等以上の難しさを持つ。
PIN
PIN認証は何かしらの形でワンタイムのPINを受け取り、それを入力する形式だ。 「PINを受け取れること」が認証条件になるため、実際の認証要素はPINを受け取る手段と等しい。多くの場合、eメールあるいは電話番号が使われる。
ショルダーハッキングのようなリスクを考えれば、パスワードを打つ機会は少ないほうが良い。 その意味で、ワンタイムなPINはパスワードよりも安全である。 (もちろん、ワンタイムでないPINはただの脆弱なパスワードに過ぎない。)
銀行などではPINを発行する専用デバイスを持たせる場合もある。 ハードウェアトークンや専用アプリを使う方法は意外ととられている。
PINで認証する前提になると、その認証はPINを受け取る方法に委ねることになる。 なぜならばワンタイムである以上は何らかの形でPINを受け取らねばならず、その受け取りを秘密の中に置くことができないからだ。 これは暗号鍵の交換のように難しい問題である。
オリジンを持っているアプリ(例えばLINEやTelegramのように)であればそのアプリのログイン状態は永続しているからそのアプリにPINを送信すれば良いとなる。これはデバイス認証の一種だ。 そうでなければeメールやSMSなどが使われる。 PINというアイディアの良さに対し、PINならではのセキュリティはいまひとつ発展していないのが実情だ。 ただし、打ち込みやすいという点でワンタイムURLよりは良いだろう。
QRコード
画面上にQRコードを表示し、アプリで読み込むことで認証ができる方式。 結構広く使われているが、webサービスの範疇に限定すると少ない。
QRコードの読み取りがそれほど快適なものではないことと、ログインしようとするデバイスと読み取りに使うデバイスが同じ場合は迂回手段が必要となるなど、単純なレベルで問題が多い。
これもデバイスの所有で認証する形だが、最初に認証に使用するデバイスを登録する段で工夫の余地が大きいのがメリット。 電話番号を要求するようなものもあるが、どちらかというとアプリをインストールした時点で特に認証などを要求せずにログインしたままの状態にし、他のデバイスでログインするときに使うというような緩和したやり方に適している。
しかし、そのような場合でもwebサービスにおいてPINを送信するよりも良い手段であるかは疑問だ。
URL
個人に紐付いたURLを発行し、URL自体を秘密にする、という方法である。
簡易な方法とみなされがちだが、それは単にユーザーが「URLを秘密にする」という感覚に乏しいという理由に過ぎない。 Googleなど多くのサービスで、「共有」においては「URLを持っている人が権限を持っているとみなす」という方法がとられており珍しいものではない。
ただし、ユーザー認証レベルで恒久的なURLを使うのは、簡易なものとして実装されている場合が多い。
権限の認証程度の話であればURLベースというのも悪くはない。 悪くはないのだが、あまりセキュアではない。
認証情報の暗号化という意味では、結局HTTPSを使わなければならないという点で変わらないのでパスワードと同等である。 だが、ブラウザ上ではヒストリやブックマークは平文で読める要素であり、悪意あるブラウザアドオンなどによって漏洩する可能性がある。
ただ、パスワードと違って手打ちしない上に、パスワードのように容易に覚えられないものを用意することもできるので、対ショルダーハッキングという点では優れている。
アプリを使っている状態から、アプリを出てウェブサイト上のサービスを利用する場合に、ワンタイムなURLを使う方法はよく取られている。これはワンタイムなURL(実際は有効期限が短いURLであることが多い)を開かせるだけで良いため、優れた方法だ。 ただし、常にデフォルトブラウザで開かれてしまうため、ユーザーがブラウザを選択する機会がない。 ここは不自由になってしまう。
生体認証
生体認証は指紋、声、顔、虹彩、静脈など、人が持っている機能を使うものだ。
優れたアイディアに見えるが、欺く方法が結構あることと、認証精度の問題がある。 指紋が減った、指が濡れている、指がふやけているなどで指紋認証がうまく動かないのは日常茶飯事だ。
結果、生体認証はセキュテリィを従来より高めるものというよりも利便性を高めるものとしての側面が強い。 Androidスマートフォンでもそのように扱われており、認証の手間が減り、またパスワードを使う場合はパスワード入力機会が減ってセキュアだ、というような面がある。
また、専用の装置が必要であるため、webアプリケーションではかなり難しい。
セキュリティを高めるなら多要素認証の一部として組み込むのが良い。
多要素認証
多要素認証は知識・所有・生体といった複数の要素を組み合わせて行う認証である。 「eメールでPINを受け取る」というようなものは多要素認証ではない。
実際に有力な認証方法が多くないため、多要素認証を成立させるのは結構難しい。
webサービスで多要素認証が必要なものは多くはなく、実際あまり使われていない。 銀行でも必要かどうかは銀行による。 ログイン時に多要素認証が必須(暗証番号+ハードウェアトークンで受け取るPIN)であるのはりそな銀行くらいか?
多要素認証はともすると「馬鹿げた行為」になりやすく、本当に必要なのかどうかよく考えたほうが良い。
そもそも認証いる?
サービスとしてパーソナライズがあれば認証したがるのが人の性だが、実際はパーソナライズしているということと認証が必要であるということは別の話である。
認証が不可欠になるのは、個人情報が含まれる場合で、例えばメッセージをやりとりするだとか、課金情報があるだとかになると必要である。
だが、サービスを提供する上でそもそも認証しないという選択肢はあるのだ。 それが誰であるかを特定することなく、オープンに使え、パーソナライズはブラウザの中に保存した情報だけで処理する。 そういう選択肢もある。
個人的に試してみたいやつ
WindowsやMacでもOpenSSHが使える時代である。 OpenSSHは認証において非常に素晴らしいものを持っている。これに勝るものはそうそうないと思うのだ。
そこで、公開鍵認証を用いたSSHによってPINを取得する、という方法を考えている。
これは公開鍵をセットアップし、OpenSSHのconfigを書く、という前準備が必要で、ログイン時にコマンドを打つという話になるから、結構手間な上にハードルは高いのだが。
もうひとつは、OpenSSHのような公開鍵認証をweb上で実装する、というものである。 OpenSSHの公開鍵認証は、共通の秘密を秘密鍵で署名し、これに公開鍵を添付して送信する。 相手は添付された公開鍵で署名を検証し、正しく秘密鍵で署名された共通の秘密であるならば、この公開鍵をIDとして権限を付与する。
ひとつのユーザーに対して複数の公開鍵を結びつけることができるから、マルチデバイスの問題も解決しやすい。
アプリなら簡単だが、ウェブだとクライアント側の実装がちょっと大変だ。 JSEncryptを使う感じだろうか。
もうひとつ、「送信させないパスワード」というのもある。 公開鍵認証もそのひとつではあるが、サーバー側は秘密を知らない状態で、クライアント側が一方的に秘密を使って認証できるメソッドについてはもっと考える余地があると思う。