10.2. Web の認証

Web の世界では Web サーバは通常ユーザ認証をするのに SSL もしくは TLS を使い、 サーバ側が認証しています。しかしユーザが誰なのかの認証は、簡単なことではあり ません。 SSL や TLS はクライアント側の認証ですが、実際に使用するに当たって、問題を たくさん抱えています(たとえば、Web ブラウザは共通のユーザ認証形式をサポート しておらず、ユーザがインストールするのは面倒です)。 Java や Javascript を使うと、それ自身に問題があります。それは、ユーザの多くが 無効にしていたり、ファイアーウォールにフィルタをかけていたりするからです。 そして、どちらかというと遅くなります。 たいていの場合、ユーザ毎にプラグインをインストールするのは非現実的でもあり ます。しかし、システムが比較的ユーザが少ないイントラネット向けなら、この 方法は適切かもしれません。

イントラネット用のアプリケーションを構築しているなら、通常は認証システムが 何であれ、ユーザが利用しているものを使った方がよいでしょう。 つまり、ユーザが Kerberos に依存しているなら、システムは Kerberos を使うように 設計してください。 認証システムは、アプリケーションの他の部分から独立させておいてください。 組織というものは、そのうち認証システムを変更するかもしれないからです(変更 したがります)。

テクニックには、機能しないか、動作してもうまくいかないものがたくさんあります。 「ベーシック認証」という方法を使うとうまくいく場合があります。この方法は すべてのブラウザやサーバで基本的に備わっています。 うまくないことに、ベーシック認証はパスワードを暗号化せずに送ります。したがって パスワードを盗み取るのは本当に簡単です。ベーシック認証はたいして重要ではない 情報を扱うだけなら、単独で十分役に立ちます。 ベーシック認証のパスワードを SSL や TLS 通信(暗号化します)ですべて被せて しまえます。しかしこれはパフォーマンスを犠牲にします。 「ダイジェスト認証」という手も使えます。これは優れた手法ですが、ブラウザが広く サポートしているわけではありません。 認証情報をユーザが選んだ URL に入れることもできますが、そうしてはいけない状況 がほとんどです。この情報をリークする方法はあまりにたくさん存在します(たとえば、 ブラウザの多くが保存している履歴ログやプロクシのログ、そして Referer: フィールド経由で他の Web サイトを使って)。

そういう訳で、今日 Web で最も良く使われている認証方法は、クッキーを利用した ものです。 クッキーは、認証のために設計されたわけではありません。しかし認証目的にも 使うことができます。しかし使い方を誤れば、セキュリティの脆弱さをさらして しまいます。注意してください。 クッキーについて詳しい情報は、IETF RFC 2965 を以前の仕様書とともに見てください。 クッキーを使う場合に、ブラウザには(たとえば、Microsoft Internet Explorer 6) プライバシー・プロファイル(p3p.xml という名前で、サーバのルートディレクリに 存在します)を強要するものもあります。

ユーザにはクッキーを受け取らない人もいて、この解決方法ではまだ問題がある 点を気に留めておいてください。 欲を言えば、この認証情報は HTML フォームの hidden フィールドを経由して やり取りすべきです(大部分のブラウザでサポートしていますので心配無用です)。 これまでまったく違った技術を用いて、データをユーザからサーバに送っていた としても、クッキーと同じような解決方法を使ってきていると思います。 もちろんこの解決方法を実現するなら、それらのページのキャッシュが、第三者に 絶対に使われないようにする設定が必要になります。 クッキーを使わない方が望ましいとは思いますが、その他の解決方法では実際には さらに多くの開発負荷がかかります。 そのため、多数のアプリケーション開発者が皆そろって実行するのは困難なので、 今はこの解決方法を強くお薦めはしません。 正しく使うのがあまりにも難しい方法(開発者もユーザも)よりも、それなりに安全 で、それなりに使いやすい方法について説明したいと思います。 しかしそれほど苦労なくできるなら、ぜひとも認証情報をフォームの hidden フィールドと暗号化したリンクを使って送ってください(たとえば、SSL や TLS)。

Fu [2001] では、Web におけるクライアント側の認証について論じています。 これまでに推奨した方法と並んで、ほとんどのサイトでお薦めできる方法です。 これの基本的な考え方は、クライアント側の認証には「ログイン手続き」と 「その後に続く要求」の2 つに分けられるという考え方です。 ログイン手続きでは、サーバはユーザにユーザ名とパスワードを求め、ユーザは それらを提供し、サーバはそれに対して「認証トークン」で答えます。 引き続き起こる要求で、クライアント(Web ブラウザ)は認証トークンをサーバに (リクエストと同様に)送ります。サーバはトークンが正当なものか確認し、正当 なものなら要求をかなえます。 Seifried [2001]は、Web での認証について Fu [2001]に匹敵する情報です。

10.2.1. Web の認証――ログイン

ログイン手続きは通常 HTML のフォームを使って実装しています。 そのフィールド名には、「username」や「password」が適切です。 そうしておけば、Web ブラウザは自動的にうまい具合に動作をしてくれます。 パスワードは、必ず暗号化された通信で送られるようにしてください(https を使った SSL や TLS を利用した通信)。さもないと盗聴する人がパスワードを収集できて しまいます。 パスワードを入力するテキストフィールドはすべて、パスワードを扱うものとして 作成してください。そうすればパスワードのテキストはユーザ画面が見られる人 誰もが読めなくなります。

ユーザ名とパスワードが送られてきた時には、ユーザ・アカウントのデータベースを チェックしてください。 このデータベースには、パスワードを「平文」で保存しないでください。誰かがこの データベースをコピーしてしまえば、またたく間にすべての人間のパスワードを 手に入れられます(おまけにユーザはよくパスワードを使いまわしします)。 crypt()を使って平文を扱う場合もありますが、crypt はちょっとした入力だけしか 扱えませんので、別の方法を使用することをお薦めします(これは私の解決方法で、 Fu [2001]ではこの点を論じていません)。 そのかわりに、ユーザ・アカウントのデータベースでは、ユーザ名とサルト(salt) そのユーザに対してのパスワードのハッシュを入れてください。 サルトはランダム・シーケンスな文字列で、攻撃者がパスワードの入ったデータベース を入手したとしても、パスワードを割り出すのが困難にするために使われます。 8 文字のランダム・シーケンスをお薦めします。これは暗号的にランダムである 必要はありません。ただし他のユーザとは違った値にしてください。 パスワードのハッシュは、「server key1」とユーザのパスワード、サルトをつなげて 計算する必要があります。計算するのには、暗号的に安全なハッシュをつくる アルゴリズムを用います。 server key1 は秘密鍵で、このサーバに一意に与えられたものです。この鍵は パスワードのデータベースとは別にしておいてください。 誰かが server key1 とパスワードの入ったデータベースを手に入れれば、プログラム を動かしてユーザのパスワードをクラックできます。 パスワードを記憶しておく必要はありませんので、長くて複雑なものにしておけます。 最も安全なのは HMAC-SHA-1 か HMAC-MD5 です。 SHA-1(SHA-1 を使うことで可能となる攻撃を、たいていの Web サイトではそれほど 心配していません) や MD5(後述の MD5 についての議論を見てください) も利用 できます。

このように、ユーザがアカウントを作成する時、パスワードはハッシュされ、 パスワードを入れるデータベースへと登録します。 ユーザがログインしようとすると、パスワードとして入力されたものがハッシュ され、データベースにあるハッシュと比較します(等しくなければいけません)。 ユーザがパスワードを変更する時には、古いものと新しいもの両方を入力します。 新しいパスワードは 2 回入力します(ミスタイプしないように)。また一方で、 パスワード文字列が画面に出ないことを確認してください。

デフォルトで、クッキーを使ってパスワード自体をクライアント側のブラウザ に保存しないでください。ユーザはクライアントを共同で利用している時が あるかもしれません(たとえば、インターネットカフェのように)。 望むなら、ユーザにブラウザで「パスワードを保存する」選択を与えることも できますが、そうするなら、パスワードは必ず「安全な」接続の伝送にだけ 使うようにしてください。またユーザがパスワードを保存することを確かに 望んでいるかを確認してください(デフォルトにしてはいけません)。

よく利用するページが決してキャッシュされないようにしてください。プロクシ サーバが他人にそのページを見せないようにしてください。

ユーザがログインに成功したなら、サーバはクライアントに「認証トークン」を クッキーで送る必要があります。この点については次で述べます。

10.2.2. Web の認証 ログイン後の動作

ユーザがログインすると、サーバはクライアントに認証トークンとして クッキーを送り返します。 推奨するトークンは下記のようになります。
  exp=t&data=s&digest=m
t はトークンの有効期限(たとえば、数時間)とdata(たとえば、ユーザ名や セッション ID)が入り、digest には鍵化したダイジェストが入ります。 「data」のフィールド名は変更自由で、もっとわかりやすい名前(たとえば、 username や sessionid)にしてもかまいません)。 鍵化したダイジェストは、有効期限を暗号化したハッシュとデータを連結したもの にしてください。 データ・フィールドが 2 つ以上あるなら(たとえば、username と sessionid 両方)、 ダイジェストには認証をしているすべてのフィールドのフィールド名とデータ値を 使うようにしてください。それらをあるパタンで連結してください(「%%」や「+」、 「&」)。またそのパタンがデータ値としてどのフィールドにも現れないように してください。 鍵化したダイジェストは、HMAC-MD5 か HMAC-SHA1 を使うようにして、別のサーバ側 の鍵(key2)を用いてください。 この key2 が信頼できないと、誰もサーバ側で認証できなくなります。しかし key2 を変更するのは簡単です。変更した時は、「ログインした」ユーザに再認証を させればよいだけです。 詳しくは Fu [2001] を見てください。

ログインした時からずっと、サーバは有効期間とこの認証に使われているトークンの ダイジェストをチェックしてください。一致した場合にだけデータを提供してください。 トークンがなければ、ユーザにはログインするページを返してください(hidden フォームのフィールドがあって、ログインが成功した後に飛ぶべきところを表示)。

セッション ID を認証トークンに入れると、アクセスがさらに制限できます。 サーバがあるセッションでユーザがどのページを見ているかを「追跡」できます。 また、見てもかまわないページにだけアクセスを許可できます(たとえば、それらの ページから直接リンクされているものだけとか)。 たとえば、あるユーザが foo.html というページへのアクセスを認められていて、 foo.html というページには bar1.jpg と bar2.png というリソースを指している ところがあるとして、bar4.cgi へのアクセスを拒否できます。 セッションを切ることさえも可能ですが、認証情報が正しいことが条件です(さも ないと、攻撃者が他人に対してサービス拒否攻撃をしかけられます)。 たとえセッションの乗っ取りに成功したとしても、これで攻撃者がかけてくるアクセス を多少なりとも制限できます。しかし、攻撃者に攻撃する時間と認証トークンが あれば、通常ユーザがするようにリンクを「渡り歩く」ことが可能です。

決め所は、認証トークンが必要なのか、それとも安全な接続(たとえば、SSL)でデータ を送るのが必要なのか、それとも両者とも必要なのか、という点です。 認証トークンを暗号化せずに(安全ではなく)送ると、トークンを横取りすれば、 ユーザと同じことを有効期限が切れるまで実行できます。 また、暗号化されていない接続でデータを送ると、ユーザが気付かないうちに 攻撃者がデータを変更してしまう危険があります。このようにデータを誰かが 変更してしまうのが心配なら、伝送するデータに認証をかけなければいけません。 暗号化自体が認証を受け持っているわけではありませんが、不正行為を発見しやすく なります。また代表的なライブラリは TLS や SSL で暗号化と認証を両方とも サポートしています。 一般的に、メッセージを暗号化したいなら、その認証もあわせて行ってください。 要求が異なるなら、もう 1 つのやり方として認証トークンを 2 つ作成する方法も あります。 トークンの 1 つは「安全な」接続を行って、重要な操作をする目的に だけ使用します。一方もうひとつのトークンは、それほど重要でない操作に利用します。 「安全な」接続のために使われるトークンに注意を払って、安全な接続(暗号化した SSL か TLS 接続が一般的)にだけ使われているようにしてください。 ユーザが違っていたなら、認証トークンが「データ」を完全に削除してください。

繰り返しになりますが、認証トークンをともなうページは、決してキャッシュされない ようにしてください。 他にも手ごろな方法があります。このドキュメントの最終目標は少なくとも 1 つ、 安全な解決方法を提供することです。 可能な解決方法はたくさんあります。

10.2.3. Web の認証――ログアウト

「ログアウト」する仕組みをいつもユーザに提供してください。これはブラウザ を共有して使うユーザ(たとえば、図書館で)にとっては特に便利です。 「ログアウト」するルーチンの役目は単純です。クライアント側の認証トークンを 解除すればよいだけです。