認証機能について考える

認証機能について考える

個人アプリをせこせこと開発中の今日このごろ。現在作っているアプリはアカウント機能もあるので、認証まわりの実装も必要になってきた。最初は、BaaSなど使って楽をしようと思ったが、久しぶりに認証機能も実装してみることにした。今回はバックエンド、フロントエンドともにTypeScriptで実装しているので、最近流行ってるBetter Authを使うことにした。

しかし、単純にアカウント登録を踏まえた認証機能を実装するとなると、考えることが山のようにあり、非常に奥が深い…。昔のようにただアカウント名とパスワードを組み合わせて実装すればいいだけではなくなっていた。

たくさんある認証方式

他にもありそうだが、おおまかに下記のように様々な認証方式がある。

  • アカウント名+パスワード
  • ソーシャルログイン(OAuth、OIDC)
  • 二段階認証(2FA)
  • マジックリンク
  • メールやSMSのOTP(ワンタイムパスワード)
  • パスキー(FIDO)

アカウント名+パスワード

昔から使われてきたアカウント名(メールアドレスやユーザ名)とパスワードを組み合わせたもの。

昨今、パスワードリスト攻撃(クレデンシャルスタッフィング)が増えていることもあり、少し不安だがまだまだ使われることは多い。

  • パスワードポリシーを考慮する必要がある
  • 自前で保存する場合、実装・管理コストが上がる

ソーシャルログイン

Google、Apple、LINEなど他社の認証基盤を使う方法。パスワードを管理する必要がないので、安全性と実装コスト削減が見込める。

  • Googleなど対応アカウントを持っていないといけない
  • 連携先のアカウントがBANや、ログインできなくなった場合に認証できなくなる
  • ユーザーのOAuth、OIDCの理解度が低い場合、アカウント連携のハードルが上がる

二段階認証(2FA)

パスワードを使った認証を行ったあと、追加でOTPなどの認証をおこなう。流出したパスワードで突破されても、追加認証が発生するので、不正ログイン耐性が上がる。

  • 追加認証の手間が発生するのでUXが下がる
  • SMS OTPの場合、運用コストが上がる
  • メールOTPの場合、メール到達率を上げるための考慮が必要
  • OTPの送信トラブルが発生した場合、ログインできなくなる

マジックリンク

メールにログイン用のワンタイムアドレスを送信し、ログインさせる方法。パスワードを管理する必要がないので、安全性と実装コスト削減が見込める。

  • メール受信できる環境が必要
  • メール受信できるデバイスが異なる場合、ログインがやや面倒(例えば、デスクトップでログインしたいが、スマホでしかメールが見れないなど。メールリンクをデバイス間で送信したりで対応はできそうだがUXは下がる)
  • メール到達率を上げるための考慮が必要
  • メール送信トラブルが発生した場合、ログインできなくなる

メールやSMSのOTP

マジックリンクに似ているが、数字6桁などメールやSMSで送信し、入力させる方式。こちらもパスワードを管理する必要がなくなる。

  • メール・SMSでOTP受信が必要
  • OTPを入力する必要があるのでUXは下がる
  • デバイスが異なっていても、ワンタイムパスワードを入力すればいいが、入力の手間が発生
  • メール到達率を上げるための考慮が必要
  • メール送信トラブルが発生した場合、ログインできなくなる

パスキー

生体認証やPINなどから生成した秘密鍵をスマホやPC等の端末に保存し、本人認証に使う方式。公開鍵暗号方式のため、サーバ側には公開鍵のみ保存されるので安全性が高い。パスワードを保存する必要もない。

  • パスキーで使用していた端末が紛失した場合、ログインできなくなる
  • パスキーへの理解度が低い場合、使用させるハードルが高い

結局どの方式がいいのか

安全性だけを考えればパスキーが良さそうに思える。ただ、どのようなシステム・サービスに導入するかを考慮して選択する必要がありそう。

  • ユーザーの利用者層(IT知識、年齢層など)
  • UXの考慮
  • アカウントの復旧フロー
  • システム・サービスの性質(個人情報を扱うか、金融関係か、toC、toB向けかなど)

例えば、若者が多くライトなサービス・スマホアプリであれば、ソーシャルログインのみでいいかもしれない。お金を扱うような証券会社などは、フィッシング対策が必要なのでパスキーが必要になりそう。IT知識が低い層や年齢層が高いユーザーは、ソーシャルログインやパスキーにやや抵抗感があるかもしれない。

復旧フローについても考慮しておく必要がある。ソーシャルログインで、ユーザーの認証情報が利用不可になった場合、復旧するのが難しいため再設定の設計が必要。

パスキーについても端末紛失時に再登録するための考慮も必要になる。例えば、メールOTPによるパスキー再登録など。でも、第三者がメール受信出来てしまったらパスキーリセットされてしまう?SMS OTPで、SIMスワップされたらどうする?など、考えればきりがない。

アカウント名+パスワードを出来るだけ安全にする

がんがんアカウント情報が流出している現状だと、アカウント名とパスワードだけで認証させるのは、リスクが高いので、おすすめできない気がする。

しかしながら、出来るだけ安全に扱うためには最低限、パスワードの保存方法とパスワードポリシーは考慮しておきたい。

認証情報の保存について

  • DBに保存するパスワードは、平文で保存しない
  • Argon2やbcryptを用いてハッシュ化して保存
  • ソルトとペッパーを組み合わせてハッシュ化する
  • パスワード強度をチェックし、簡単なパスワードは弾く
  • 可能ならHave I Been Pwnedで、漏洩パスワードをチェックする

ログイン時は、下記のことも考慮する。

  • タイミング攻撃
  • レートリミットを設けて、ブルートフォース耐性を上げる
  • CAPTCHAサービスを用いて機械的な試行を防御する

推奨パスワードポリシー

ふと気になったのは、パスワードポリシーはどうするか。最低文字数をある程度長くして、数字・大文字・小文字・記号を強制すればいいか、と当然のように考えていた。

IPA、NIST、OWASPを参考にしてみる。

IPA

パスワードポリシーのガイドラインという形では公表していないが、パスワードを扱う際の推奨事項などが公開されている。

  • 可能な範囲で複雑な長い文字列
  • 大小英字、数字および記号を混在させて、最低でも10文字
  • コアパスワードを決めて、サービス毎の識別子と組み合わせる(例えば、hogehogeがコアパスワードとしたら、サービス識別ipaを組み合わせ、ipahogehogeのようにする。)
  • 多要素認証(MFA)を推奨

https://www.ipa.go.jp/security/anshin/measures/everyday.html

https://www.ipa.go.jp/security/anshin/measures/account_security.html

https://www.ipa.go.jp/security/anshin/attention/2016/mgdayori20160803.html

NIST

NIST SP 800-63B-4が公開され、ニュース記事で少し話題になっていた。NISTは、米国の国立標準技術研究所のことらしい。

  • パスワードの定期変更は不要
  • 複雑なパスワードは不要(大小英字、数字および記号を混在を必須にするなど)
  • 絵文字なども含め、文字種制限は不要(Unicodeすべて許容)
  • パスワードのみで認証させる場合、最低15文字以上の長いパスフレーズにする
  • 最大文字数は、最低64文字以上にする(長ければ長いほどいい)
  • 漏洩パスワードや単純パスワードのブロック必須
  • パスワードヒント、秘密の質問禁止
  • パスワードマネージャー、多要素認証(MFA)を推奨
  • 多要素認証を用いる場合、最低8文字
  • パスワードのペーストは許可してもいい

https://csrc.nist.gov/pubs/sp/800/63/b/4/final

https://www.watch.impress.co.jp/docs/topic/2071110.html

OWASP

OWASPとは、Webアプリケーションのセキュリティ向上を目的とした国際的な非営利団体およびオープンコミュニティとのこと。

NISTのガイドラインも考慮されており、認証機能を実装する上で全般的に書かれていて、かなり参考になる。

  • 複雑なパスワードは不要(大小英字、数字および記号を混在を必須にするなど)
  • 少なくとも8文字以上、最低12文字以上を推奨
  • 最大文字数は、最低64文字以上にする
  • 文字種制限は不要(Unicodeすべて許容)
  • 漏洩パスワードはブロック
  • 単語リストを用いてブロック
  • パスワードマネージャー、多要素認証(MFA)を推奨
  • パスワードのペーストは許可してもいい
  • パスワード強度メーターを付ける
  • パスワードマスクを全体もしくは、最後の文字を確認できるような切り替えを実装

https://github.com/coky-t/owasp-asvs-ja/blob/master/5.0/ja/0x15-V6-Authentication.md

https://zenn.dev/uyas/articles/fefbdf54f24429

https://note.com/mnuma/n/nfb1c11aef236

推奨通りのパスワードポリシーにするべきか

IPAに関しては、公式でパスワードポリシーのガイドラインのようなものを出しているわけではないが、いわゆる多くのサイトで採用されている最低限のパスワードポリシーといった感じ。

NISTやOWASPを考慮すると、

  • 長さ15文字以上で、64(128)文字以内(一応上限を付けておく)
  • 文字種制限はしない
  • 漏洩パスワード、頻出ワードなど単語リストをブロック
  • 多要素認証必須
  • パスワード強度メーター(単純な単語など、強度が低い場合はブロック)

しかし、ここまで制限してしまうとかなり使い勝手が悪く、ユーザーのストレスが大きくて微妙な気がする。パスワードが上手く設定できなくて、面倒だから登録しない、なんてことになるかもしれない。

あとは、どんなサイトやアプリに認証機能を導入するかによっても条件が変わる。例えば、金融サイトのような場合は、フィッシング対策で多要素認証は必須にした方がよさそう。

多要素認証を必須にするならば、パスワードの長さはそこまで必要ではないかもしれない。

Unicode文字すべて許可するとしたら、半角・全角など入力ミスで認証できない、なんてトラブルも増えそう。エンコーディングの問題や、普段と違う環境で日本語が打てずに入力できないなんてことも考えられる。Inputフィールドのtype=passwordに設定した場合、日本語入力に切り替えできないケースもある。

漏洩パスワードのチェックで、Have I Been PwnedのAPI使用した場合は、コストも考えなくてはいけない。

色々考慮するときりが無く、時間・コスト・使いやすさからどの程度まで許容できるのか決めて実装する必要がある。

文字数と組み合わせ

半角英数字と記号(”#$%&'()*+,-./:;<=>?@[]^_`{|}~!)= 94種類で考えた場合、最低12、13文字にしておけば良さそうに思える。

文字数組み合わせ数(概算)エントロピー
86.10 × 10¹⁵52.4 bit
95.74 × 10¹⁷59.0 bit
105.39 × 10¹⁹65.5 bit
115.07 × 10²¹72.1 bit
124.76 × 10²³78.7 bit
134.47 × 10²⁵85.2 bit
144.20 × 10²⁷91.8 bit
153.95 × 10²⁹98.3 bit
163.71 × 10³¹104.9 bit
ビット数評価
~50bit現代GPUなら危険(オフライン攻撃)
60bit台一般用途なら可
70bit台かなり強い
80bit超実質総当たり困難
100bit近い非常に強固

できるだけ安全そうな実装

パスワードスプレー攻撃のようなパスワードリストを用いた攻撃を回避するために脆弱なパスワードはあらかじめて弾いておく。同時にレートリミットを設定し、ブルートフォース耐性を上げておく。

その上で、ユーザーの使いやすさ、トラブル回避なども考慮して、下記のような形にする。

  • 長さ12文字以上、128文字以内
  • 文字種は、半角英数字記号(”#$%&'()*+,-./:;<=>?@[]^_`{|}~!)の94種
  • Unicodeをすべて許容するのは、一般的なサービスではまだ導入は難しそう
  • 12文字以上とすることで、文字種の組み合わせは必須にしない(例えば、記号・大文字などの組み合わせ必須など)
  • パスワードマネージャーが生成したパスワードは通るような文字種・文字数は許容すること
  • パスキーは推奨として設定できるようにする
  • パスキー未登録の場合、UXは下がるがパスワードとメールOTPを併用
  • パスワード強度メーターを付けて、脆弱なパスワードは弾く
  • パスワードマスキングのオン・オフを付ける
  • 確認パスワードの入力は不要(コピペしたりするので意味がない)
  • パスワードのペーストは許可
  • 復旧フローは、簡易的ならメールOTPやメールリンクを利用。厳しくするならサポート側で本人確認の上で再設定

パスワード強度チェックは、zxcvbnを使えばある程度はチェック可能だが、脆弱パスワードの辞書が内蔵されているので、800KBほどあって重たい。(圧縮後でも400KBほど)

フロント側でチェックするとなると、重たくなる可能性があるので、少し考慮する必要はありそう。

現在は、zxcvbn-tsが主に使われている模様。日本語用の辞書もあるので、組み合わせて使うのがいい。

https://github.com/zxcvbn-ts/zxcvbn

お金に余裕がある場合は、Have I Been PwnedのAPIを使う。(上限は低いが、無料プランも一応ある)

もう少ししっかりした設計にするならば、ログイン時にメール送信、普段と違うIPからログインした場合に警告メール送信など考えられる。

パスワードレス、メールOTPのみはどうか

パスワードを管理したくないので、メールOTP(SMS OTP)のみで認証させるのはどうか。

OTPをメール送信するタイミングは、大まかに登録時とログイン、パスワード再設定になる。受信したOTPを入力したら認証完了だ。

問題になりそうなのは、第三者がメールアカウントにアクセスできたらログインできてしまうわけだ。要は、メールアカウントのID、パスワードを使うことで、認証管理を外部に肩代わりさせている。ただ、メールアカウントは通常、一度設定したら再認証するようなことはあまり起こらないので、少し脆弱なような気もするが、このあたりを許容できるかどうか。

では、パスワードも登録させて、二段階認証させた場合はどうなるか。

メールにアクセスできても登録時のパスワードが分からなければ、ログインできないので安全性は高まる気がする。でも、復旧フローでメールを経由してパスワードリセットができてしまえば、結局は同じことのようにも思える。

SMS OTPの場合は、eSIMが普及してきたとはいえ、物理的にSIMを入れ替えることができたら突破できてしまう。SIMスワップするには、物理的にデバイスにアクセスできる必要があるので、ハードルは高そうに思える。逆に物理的にデバイスにアクセスさえできれば、デバイスの生体認証やPINを入力する必要もないので、メールOTPより危ない気もする。

eSIMが安全なのかと言えば、何らかの方法で通信事業者のアカウントからeSIMを再発行できるとしたら、物理デバイスにアクセスせずにSIMスワップが可能とも言える。

メール到達率を考える

マジックリンクやメールOTPを利用する場合、おさえておきたいのはメール到達率だ。メールが届かない場合、そもそもログインできなくなってしまう。

自前でSMTPサーバを構築するだけであれば、それほど大変ではなく、要求スペックも大した事ない。

では自前で構築するべきかと考えると、考慮すべき点がいくつかある。

  • TLS、DKIM、SPF、DMARC、逆引きDNSなど適切に設定する必要がある
  • IPレピュテーション
  • 送信元IPのブラックリスト
  • バウンスメール処理

レンタルサーバーの場合、そもそも借りたサーバのIPがブラックリストに入ってる場合もある。

ここでやっかいなのが、IPレピュテーションとバウンスメールの処理だ。IPレピュテーションとは、送信元IPの信用度だ。

スパムメールが増えに増えた昨今、特定のIPから大量にメールが送信されると信用度が下がり、IPレピュテーションが低下する。信用度が下がった状態でメールを送信してもスパム判定されてしまうわけだ。これを回避するには、少しずつメール送信する量を増やして、信用度上げる必要がある。

そうなると複数のIPから分散して、メール送信量の調整をおこないながら配信する必要が出てきて運用が少し大変だ。

次にバウンスメール対応だ。バウンスメールとは、届かなかったメールのことだ。届かない原因は色々あるが、宛先不明や受信ボックスが一杯、送信先サーバダウンなど。存在しないメールアドレスに何度も送信しているとブラックリストに入ってしまうので、適切に処理する必要がある。

最近は、AWS SESやSendGridのようなサービスを使用するパターンが多いと思うが、どうしてこの手のサービスを使う必要があるのかと言えば、上記のような面倒なことをすべてやってくれるからということのようだ。

アカウント列挙攻撃はどうするか

アカウント列挙攻撃とは、存在するアカウント名(メールアドレスなど)を探し出す攻撃だ。

例えば、登録時にメールアドレスを入力した場合、すでに登録されています、など表示してしまうとアカウントの存在がバレてしまう。

バレたからどうだという話なのだが、アカウント名が分かれば、あとはパスワードを組み合わせて総当りしていくとか、流出しているパスワードリストから一致するアカウント名があれば、突破できる確率が上がるということになる。CAPTCHAやレートリミットを設定すれば、そう簡単には特定されないような気はする。

では、ログインフォームにメールアドレスとパスワードを入力し、特定されないように、「入力された内容が間違っています」とだけ表示するのがいいのか。ユーザーは、メールアドレスとパスワードどちらが間違っているのか分からないが、存在するアカウントかどうかはバレない。

復旧フローとして、パスワード再設定フォームはどうするか。メールアドレスを入力して、再設定リンクを送信したり、OTPを送信したりする。

存在しないメールアドレスについても、「送信しました」とだけ表示するのがいいのか。入力ミスしているかもしれない、でもユーザーは気づかずにメールが届かないとなり、サポートの負担が増えるかもしれない。

サービスの内容から必要なセキュリティ、UX、どの程度まで許容できるか、バランスを考えて決める必要がありそう。考えだしたら夜も眠れない。認証機能の実装は難しい。

コメントを残す

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