Passkey への道 #1
Intro
まず、今使われているパスワード認証について、これがなぜ問題で、なぜ Password-Less が求められているのかを振り返っていこう。
平成のパスワード感
このエントリを読むような読者であれば、平成にわたって「パスワード」というものがどう扱うべきかは、さんざん啓蒙されてきただろう。
まずはこのあたりを振り返ってみよう。
使う側
預かる側
使い回し
実はこのルールの中で、ユーザにとって最も重要で、かつ運用が難しいのは「使い回さない」ことだ。
一般のユーザですら、日常的に多くのサービスを使い分け、加えて何年かに一回しか使わないようなサービスを登録している状態が普通になった。そんな中で、すべてのサービスで別のパスワードを「記憶」に頼る運用は破綻が近い。
代わりに、記憶しているパスワードに、サービス名やドメイン名を追加して一意性を担保するような運用をしている人も多いだろうが、サービス名やドメイン名が変わることは多々あるので、やはり人力での運用には限界がある。
結果、多くの人が未だにパスワードを使い回している。
そこに輪をかけて、同じサービスでも定期変更を求められようものなら、いよいよ覚えきれる簡単なルールでパスワードを作るしかなくなる。NIST でもパスワード変更が非推奨になったのは、こうした背景があるのだ。
使い回しの問題は、サービスがベストプラクティスをすべて完璧に実装していたとしても、他の脆弱なサービスから漏れたパスワードで、自分たちもなりすまされてしまう点にある。漏洩したサービス自体が悪用されるのはまだしも、その魔の手が他のサービスにも及ぶ。と、わかっていながら、それを防ぐ手立てはない。
Have I Been Pwned に、自分が普段使っているアドレスを入れてみれば、自分の使っているパスワードが過去にどのくらい漏洩しているかを知ることができる。
長く使っているアドレスなら、少なからず報告が表示されるだろう。しかし、表示されなかったからといって安心できるとは限らない。ここにはバイアスがあるのだ。
攻撃の検知
Have I Been Pwned は、漏洩が発覚したサービスと連携し、その事実をユーザに通知する目的を持っている。つまり、ここに載っているのは「漏洩に自分で気づけているサービス」から提供された情報だけなのだ。
サイバー攻撃関連のニュースは連日あるが、そもそも攻撃されたこと自体に気づくのも、一定の体制が必要だ。そこまでの体制が組めているようなサービスは、とっくの昔から平文でパスワードを保存したりしていないだろう。適切な方式でハッシュ化し、通信やディスクを暗号化し、ログどころかメモリ上にすら残らないように平文を消去していれば、そもそも「パスワードの漏洩」リスクはかなり抑えられる。ハッシュの漏洩でも立派なインシデントではあるが、それが他のサービスにまで迷惑をかける可能性は格段に下がる。
一方、未だにパスワードを平文で保存しているようなサービスは、古い実装を更新をしていないか、信じられないほどの素人がノリで作ったサービスだろう。そうしたサービスは、漏洩どころか攻撃されていることにすら、気づいていない可能性が高いのだ。
ユーザは、自分のパスワードが「どうせどこかしらで漏洩している。」と思って行動するくらいで良い。これは大げさではない。
平文保存しているサイトの見分け方
パスワードはハッシュ化して保存するのが、預かる側の大原則だ。bcrypt, argon2, PBKDF2 などの知られた方式とライブラリがあるため、それらを導入することになる。受け取ったパスワードをそれらに通したら、パスワード自体はログからもメモリからも跡形もなく消し去るべきだ。
この時、DB に保存するのは固定長のハッシュ値が基本だ。ユーザの指定したパスワードの文字数に限らず一定になる。仕様で定義されたハッシュ値が入るカラムサイズを、DB のテーブルに確保すればよい。認証時は、受け取ったパスワードを同じハッシュにかけ、結果を比較する。
つまり、もしあなたがパスワードを登録する時、「15 文字以下」などの文字数上限がついていたり、「記号は &
, \\
以外」といったルールが設けられていれば、そのサイトは平文で保存している可能性が高い。
長さに上限を設けるのは、カラムのサイズ以上に大きいとそのまま保存できないから。使えない記号があるのは、ろくにエスケープもせず SQL に埋め込んで DB を検索するような実装になっているから。といった可能性が高いのだ。
これはあくまで経験則だが、もし現代のプラクティスで適切に実装されていれば、ユーザが何文字のパスワードをどんな文字列で指定しようとも、ハッシュを通して終わりだ。入力長に上限があっても、NIST の推奨のように 64 文字などで十分であり、少なくとも Ascii 可視文字範囲で、使ってはいけない文字種など指定する必要は基本無いのだ。
複雑さよりもリトライ制限
パスワード登録時に「N 文字以上で、英数記号のうち少なくとも〜」などのように、多くのルールを課したり、簡単な文字列だったらエラーを出すようなサービスは多くある。
しかし、そうしたルールを強いておきながら、認証のリトライ制限をきちんと実施していないサービスは、体感ではかなり多い。アカウントのロックはユーザにとって避けたいことなので、自分のアカウントが攻撃にあった時に適切にロックされるかをわざわざ確認するユーザもいない。
リトライの制限すらしていないということは、ろくに監視すらしていない可能性がある。攻撃者が Bot を走らせて、何万回もの総当たりをかけていても、開くまで放置されている可能性があるのだ。正門をこじ開けるのだから、ハッシュ保存などをしていたとて意味がない。
10 回連続で失敗したら、メール確認が通るまでロックする、いや 10 分ロックするだけでも攻撃難易度はだいぶ変わる。また、パスワードを間違えると、エラーのレスポンスをあえて遅延するのも効果的だ。ログインに失敗したときだけ、一瞬止まったように見えるログイン画面などは、攻撃難易度のためにやっている。
それらができていれば、むしろパスワード自体の文字の組み合わせなど、そこまで問題ではないのだ。パスワードの本質はエントロピーなので、辞書にあるような簡単な単語などを避ければ、基本は長さだけ気にすれば良い。英数だけであっても、記号を入れさせるより一桁増やさせる方がエントロピーは上がる。
例えば Google なんかはだいぶ前からパスワードに文字種の指定はない。最低が 8 文字で簡単な単語でなければ良い。異常なアクティビティは試行回数にとどまらず、ロックの実装はかなりちゃんとしている。
もちろん、DB ハードウェアごと盗まれて(こっちはスパイ映画のように忍び込んでではない、ある日突然データセンターにシャベルカーが突っ込んできて、DB をサーバーラックごと引っ剥がして盗まれるみたいなやつだ)、オフラインで攻撃されることを考えると、複雑性の問題も多少は関わってくる。が、むしろそれはディスクそのものの暗号化や、単純な建物の堅牢性みたいな方向で対処すべきだ。まあ、そこまでやる攻撃者がいたら、多少の抵抗をしてもどうせ開けられるだろうという気はする。
自分で持たない
パスワードを預かる実装がきちんとやり遂げられないなら、自分では預からず、認証を移譲する方法もある。
マッシュアップ全盛の OAuth を経て、OpenID Connect が整備された今、自分よりも技術力もリソースも大きいビックテックに、認証を頼ることもできるようになった。
それで済むなら、サービスとしてはその方が堅牢にはなるだろう。使う側も「このサービスにパスワードを預けて大丈夫だろうか?」と心配する必要もなくなる。
しかし、現実にはそうはいかないサービスも多い。
銀行のサービスを Sign-In with Apple にするわけにもいかないだろうし、病院のアカウントを Sign-In with Google にしたら「医療情報を Google に渡してるのか?」と思われてしまう可能性もある。全部がマイナンバーに統合されようものなら、政治的な理由から離れるユーザも出るだろう。
本当はパスワードなど預かりたくはないとしても、認証を独自に持たないといけなければ、必然的にパスワードを預かるしかないのだ。
パスワードに期待しない
結果、平成における「パスワード」とは「なんとか死守するもの」として扱われていた。啓蒙されるプラクティスはすべてそれを補強するものだった。
しかし、何らかの形で漏洩したパスワードは、リストとしてダークネットで取引されている。その中のすべてが使い回されていなくても、使い回している人が少しでもいれば、そのリストは商品価値を持つのだ。
とにかく弱いサービスを見つけ、攻撃し、パスワードを窃取し販売する。別の攻撃者は、サイバー攻撃を企てる際に、ダークネットで使えそうなリストを購入し、攻撃の足しにする。これらがすべて分業されており、どの犯人も足取りを掴むのは困難だ。
目の前で陳列され、売買されているリストたちを見ていれば、自然と同じことを思うだろう。もはや「パスワードに期待してもしょうがない」のだ。
ユーザは、とにかく使い回さないことにだけ注意して登録しつつ、「このパスワード、どうせ何割かは漏洩するんだろうな」という前提でいる。
サービスは、とにかく推奨されるプラクティス通り実装することに注意しつつ「このパスワード、どうせ何割かはどこかで使い回してるんだろうな」という前提でいる。
こうして「決してパスワードだけで認証しようとしない」のが、今の認証の最重要プラクティスとなっていった。