はじめに
Webアプリケーションを開発する際、ログイン機能は避けて通れない要素です。この記事では、認証と認可の基本概念から、パスワードを安全に保存する仕組みまでを解説します。
実際の開発では専用のライブラリを使うことがほとんどですが、認証の仕組みを理解することで、セキュリティ上の問題を未然に防ぐことができます。
対象読者
- Webアプリケーション開発の初心者
- 認証・認可の仕組みを理解したい方
- セキュアなログイン機能の実装方法を学びたい方
認証と認可の違い
認証と認可は似た言葉ですが、明確に異なる概念です。混同しやすいため、まずはこの違いを理解しましょう。
認証とは
認証はユーザーが誰なのかを確認するプロセスです。具体的には、ユーザー名とパスワードを使って「あなたは本当に○○さんですか?」と確認する作業になります。
認可とは
認可はユーザーが何ができるのかを確認するプロセスです。認証が完了した後、そのユーザーに特定の操作を許可するかどうかを判断します。
両者の関係性
認証は「誰なのか」を確認し、認可は「何ができるのか」を確認します。両方のプロセスを経て初めて、ユーザーは目的の操作を実行できるようになります。
パスワードを安全に保存する
なぜ平文保存はダメなのか
パスワードをそのままデータベースに保存してはいけません。これは絶対に守るべきルールです。
データベースが漏洩した場合、平文で保存されたパスワードは即座に悪用されてしまいます。さらに深刻な問題があります。
パスワード流用のリスク
世の中の多くの人はパスワードを複数のサービスで流用しています。あるサービスからパスワードが漏洩すると、他のサービスでも同じパスワードを試される攻撃(リスト型攻撃)を受けるリスクが高まります。
そのため、パスワードは必ずハッシュ化して保存する必要があります。
ハッシュ化の基礎
ハッシュ関数とは
ハッシュ関数は、任意の長さのデータを固定長のデータに変換する関数です。この関数には重要な特性があります。
ハッシュ関数の特性
ハッシュ関数には次の特性があります。
一方向性
ハッシュ関数は一方向性関数です。つまり、出力から元の入力を推測することが極めて困難になっています。
決定性
同じ入力に対しては常に同じ出力を返します。これにより、ログイン時にパスワードを照合できます。
衝突耐性
異なる入力に対しては異なる出力を返します。異なるパスワードが同じハッシュ値になることは極めて稀です。
暗号学的ハッシュ関数
パスワード保存に適したハッシュ関数
すべてのハッシュ関数がパスワード保存に適しているわけではありません。パスワード保存には暗号学的ハッシュ関数を使用する必要があります。
主要なアルゴリズム
パスワードのハッシュ化に使える関数は限られています。代表的なものに以下があります。
- BCrypt
- scrypt
- PBKDF2
- Argon2
これらは単純なハッシュ関数とは異なる特性を持っています。
暗号学的ハッシュ関数の要件
パスワード用のハッシュ関数は、以下の要件を満たしている必要があります。
衝突耐性の強化
異なる入力が同じ出力になる確率が極めて低く設計されています。
逆算困難性
出力から元の入力を推測することが計算量的に極めて困難です。
決定性の保証
同じ入力に対して同じ出力を返すことが保証されています。
意図的な処理の遅延
関数の実行が意図的に遅く設計されています。これは攻撃者が総当たり攻撃を行う際のサイクルを遅くするためです。処理を遅くすることで、短時間に大量のパスワードを試すことを困難にしています。
ソルトの役割
ソルトとは
ソルトは、パスワードをハッシュ化する前にランダムなデータを追加する仕組みです。
レインボーテーブル攻撃への対策
レインボーテーブルとは、よく使われるパスワードとそのハッシュ値の対応表です。攻撃者はこの表を使って、ハッシュ値から元のパスワードを特定しようとします。
ソルトを使うことで、同じパスワードでもユーザーごとに異なるハッシュ値が生成されます。これによりレインボーテーブルによる攻撃を無効化できます。
ソルトの実装
同じハッシュ関数が世の中で広く使われているからこそ、ソルトの仕組みが重要になります。ソルトはユーザーごとに異なるランダムな値を生成し、パスワードと組み合わせてハッシュ化します。
ソルトはハッシュ値とともにデータベースに保存します。ログイン時には、入力されたパスワードに保存されているソルトを結合してハッシュ化し、保存されているハッシュ値と比較します。
ログイン状態の管理
ログイン機能では、認証が成功した後の状態管理が重要です。
ログイン機能で重要なこと
安全なログイン機能には以下の要素が必要です。
- ログイン状態が保持されるか
- 一定期間操作がない場合にログアウトされるか
- ログアウトの仕組みが適切に実装されているか
これらの要素を実現するために、セッション管理の仕組みを使います。
セッション管理の仕組み
ログインが成功すると、サーバーはランダムなセッションIDを生成してクライアントに渡します。クライアントはこのセッションIDを保存し、以降のリクエストで毎回送信します。
サーバーは受け取ったセッションIDを確認することで、ユーザーがログイン済みかどうかを判断できます。
セッションIDの生成と保存
セッションIDは推測不可能なランダムな値として生成する必要があります。セッションIDが推測できてしまうと、他人のログイン状態を乗っ取られる危険性があります。
生成されたセッションIDはサーバー側のデータベースやメモリに保存され、どのユーザーのセッションかを紐付けておきます。
ログイン状態の確認
二回目以降のリクエストでは、クライアントから送られてきたセッションIDを確認することで、ログイン済みかどうかを判断します。セッションIDが有効であれば、ユーザーは認証済みとして扱われます。
ログアウトの実装
ログアウト処理の流れ
セッションの破棄
ログアウトは、サーバー側のセッションに入っている情報を削除することで実現します。セッション情報が削除されると、以降そのセッションIDを使ったリクエストは認証されていないものとして扱われます。
クライアント側でもセッションIDを削除することで、完全にログアウト状態になります。
まとめ
認証実装で押さえるべきポイント
認証と認可は異なる概念であり、両方を適切に実装する必要があります。パスワードは必ずハッシュ化して保存し、ソルトを使うことでセキュリティを強化しましょう。
ログイン状態の管理にはセッションの仕組みを使い、適切なログアウト機能を実装することが重要です。
実務での注意点
実際の開発では、これらの仕組みを自分で実装するのではなく、実績のあるライブラリを使用することをお勧めします。しかし、仕組みを理解しておくことで、セキュリティ上の問題を見逃さず、より安全なアプリケーションを開発できるようになります。