目次


PHP の新しい側面

一新された PHP: 最新の PHP でのパスワード・セキュリティー

PHP 5.5 ではどのようにして、よりセキュアなパスワード処理を可能にしているかを理解する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: PHP の新しい側面

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:PHP の新しい側面

このシリーズの続きに乞うご期待。

PHP は、そもそも Web サイトを構築するために作られたプログミング言語です。その考え方は、他のどのプログラミング言語より遥かに深く PHP のコアに根付いています。それがおそらく、PHP が Web アプリケーションを構築するための言語としてこれほどよく使われるようになり、その人気を維持している 1 つの理由でしょう。ただし、PHP が最初に作成されたのは 1990年代の半ばです。その当時は、「Web アプリケーション」という言葉すらありませんでした。そのため、PHP の作成者たちがリソースを投入して作成した機能には、パスワード保護が含まれていませんでした。結局のところ、サイトの訪問者カウンターや日付変更スタンプを Web ページに追加するためだけに PHP が使用されていた頃は、パスワードについて懸念する必要はなかったのです。

しかし、それから 20 年が経った今、パスワードで保護されたユーザー・アカウントを伴わない Web アプリケーションを作成することなど、ほとんど考えられません。PHP プログラマーが最もセキュアな最新の手段を使ってアカウントのパスワードを保護することは、何よりも重要になっています。このことから PHP 5.5 に追加されたのが、Anthony Ferrara 氏(@ircmaxell) によって作成された新しいパスワード・ハッシュ・ライブラリーです。このライブラリーには、現在のベスト・プラクティスに従った方法でパスワードの一方向暗号化処理を行うために使用できる関数がいくつか用意されています。さらに、コンピューターやハッカーが高度になったときに、悪意のある人たちの一歩先を行くことができるよう、他の機能でも将来的なセキュリティー・ニーズが想定されています。この記事では、パスワード・ハッシュ・ライブラリーの関数について詳しく紹介するとともに、これらの関数を最大限利用する方法も詳しく紹介します。

セキュア・ハッシュの重要性

パスワードは歯ブラシのように扱う必要があります。他の誰にも使わせないようにして、6 ヶ月ごとに新しいものに取り替えてください。

Clifford Stoll

これは言うまでもないことでしょうが (少なくとも、そうであることを願っています)、ユーザーのパスワードを平文で保管するのは御法度です。パスワードは、必ずハッシュ・アルゴリズムなどの一方向暗号化アルゴリズムを使って暗号化したものを保管するようにし、誰かがアカウント・データベースにアクセスしても、ユーザーのパスワードがわからないようにしてください。この忠告は、Web サイトに不正侵入してデータベースにアクセスする人からユーザーを守ることだけを目的としているのではありません。組織内の悪意を持った人からもパスワードを保護する必要があります。

パスワード保護の必要性は大きくなる一方です。残念ながら、多くのユーザーは複数の Web サイトでまったく同じパスワードを使用しているからです。そのため、誰かが e-メール・アドレスにアクセスして、いずれかのユーザーの暗号化されていないパスワードを取得したとすると、他の Web サイトでもそのユーザーのアカウントにアクセスできる可能性が十分にあります。

解析の速さ

すべてのハッシュが一様に作成されるわけではありません。ハッシュを作成するには、さまざまなアルゴリズムを使用することができます。過去によく使われていたのは MD5SHA-1 の 2 つですが、このいずれのアルゴリズムにしても、最近のパワフルなコンピューターであれば、簡単に解析できてしまいます。例えば、単一の GPU で、MD5 に対しては毎秒 36.5 億回、SHA-1 に対しては毎秒 13.6 億回の速度でハッシュを解析するソフトウェアも存在します。これだけの速度であれば、パスワードの複雑さと長さによっては、1 時間もかけずにパスワードを破ることができます。

したがって、計算するのがより複雑なハッシュ・アルゴリズムを使用することが重要です。より長いハッシュにするだけでなく (これによって、ハッシュの競合 (2 つのフレーズから同じハッシュが生成されること) が発生する可能性を小さくします)、ハッシュが生成されるまでの時間をできるだけ長くする必要もあります。それはなぜかと言うと、Web アプリケーションでは、ユーザーがパスワード・ハッシュの生成を待たなければならないのは、ログインする際の 1 回だけです。その待ち時間が 1 秒であれば (あるいは、2、3 秒であっても)、ユーザーは気にすることも、気付くこともないでしょう。しかし、解析の計算速度を毎秒 36 億回から毎秒 1 回にまで遅くすれば、パスワードを破るのは飛躍的に難しくなります。

レインボー・テーブル

レインボー・テーブルに対する自己防衛も必要です。レインボー・テーブルとは、ハッシュの逆引き参照テーブルのことです。その一例として、MD5 のレインボー・テーブルには md5cracker.org でアクセスすることができます。このテーブルの作成者は、ありとあらゆる一般的な単語、語句、単語の変化形、さらにはランダムな文字列に対しても MD5 ハッシュを事前に計算しています。ハッシュを利用した人がそのハッシュをこの参照テーブルに入力すると、そのハッシュを生成するために使われたパスワードを見つけることができます。つまり実質的に、一方向プロセスを逆に行うという仕組みです。このようなレインボー・テーブルを作成できるのは、MD5 ハッシュを解析する処理コストが比較的低いからです。

計算が複雑なアルゴリズムの場合、そのレインボー・テーブルを生成するとなると、かなり長い時間がかかりますが、それでも不可能なことではなく、作成者にとって 1 回限りの苦労でしかありません。レインボー・テーブルに対する適切な対抗策は、ハッシュにソルトを追加することです。このコンテキストでのソルトとは、最初にハッシュを作成する前にパスワードに追加する任意の語句を指します。ソルトを使用すると、レインボー・テーブルは (実際に) 使いものにならなくなります。皆さんのアプリケーションに対して、誰かが固有のレインボー・テーブルを生成した後、複数のパスワードを破ってソルトが何であるかを明らかにすることは、困難かつコストがかかるシナリオです。

古いバージョンの PHP でのパスワード処理方法を改善する

リスト 1 は、PHP で数年前まで有効とされていたパスワード処理方法の一例です。

リスト 1. PHP でかつて有効とされていたパスワード・セキュリティーの例
<?php
// Create a password class to handle management of this:
class Password {
    const SALT = 'MyVoiceIsMyPassport';

    public static function hash($password) {
        return hash('sha512', self::SALT . $password);
    }

    public static function verify($password, $hash) {
        return ($hash == self::hash($password));
    }
}

// Hash the password:
$hash = Password::hash('correct horse battery staple');

// Check against an entered password (This example will fail to verify)
if (Password::verify('Tr0ub4dor&3', $hash)) {
    echo 'Correct Password!\n';
} else {
    echo "Incorrect login attempt!\n";
}

リスト 1 のような例は、いわゆるベスト・プラクティスとして Web に散在しています。長い間ベスト・プラクティスとされてきたこの方法は、確かに MD5 を使用するよりましであり、パスワードを平文で保管することに比べれば圧倒的に望ましい方法です。リスト 1 ではかなり複雑な SHA-512 アルゴリズムを使用し、既製のレインボー・テーブルを無効にするために、すべてのパスワードにソルト処理を適用しますが、この方法にはいくつかの問題が残っています。

ランダム・ソルトに移行する

リスト 1 ではソルトを使用しているものの、すべてのパスワードにまったく同じソルトを使用しています。したがって、誰かがパスワードを 1 つでも破ると (さらに悪いシナリオとして、コード・ベースにアクセスしてソルトを発見した場合)、そのソルトをレインボー・テーブルの各エントリーに追加することで、カスタム・レインボー・テーブルを作成することができます。このようなレインボー・テーブルを無効にするためのソリューションは、各パスワードの作成時にランダム・ソルトを使用し、パスワードを復元できるようにそのソルトをパスワードと一緒に保管することです。

コストをさらに増やす

また、リスト 1 では MD5 や SHA-1 の代わりに SHA-512 を使用しています。SHA-512 は PHP に付属の非常に複雑なアルゴリズムですが、それでも SHA-512 ハッシュは毎秒約 4600 万回の計算速度で解析することができます。MD5 や SHA1 に対する速度よりは遅いとは言え、十分なセキュリティーを施すにはまだ十分な遅さとは言えません。この問題に対するソリューションは、計算するのがさらに難しいアルゴリズムを使用し、それらのアルゴリズムを何度も繰り返して実行することです。例えば、すべてのパスワードを SHA-512 で 100 回連続して処理すると、ハッキングを試行する時間が大幅に長くなります。

ありがたいことに、このソリューションを実装するために独自にコードを作成する必要はありません。PHP 5.5 で新たに追加されたパスワード・ハッシュ・ライブラリーが、まさにこのニーズに対応してくれます。

password_hash() を導入する

パスワード・ハッシュ拡張機能により、計算が複雑でセキュアなパスワード・ハッシュが自動的に作成されます。その裏では、ランダム・ソルトも生成されて処理されています。最も単純な使用ケースでは、ハッシュを生成するパスワードを引数に指定して password_hash() を呼び出します。すると、この拡張機能がすべての処理を引き受けてくれます。2 つ目のパラメーターとして、この拡張機能に使用させるハッシュ・アルゴリズムも指定する必要があります。いくつかの選択肢がありますが、現時点では PASSWORD_DEFAULT 定数を指定するのが最善の方法です (この後、その理由を説明します)。

<?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);

コストを指定する

さらに、ハッシュの生成方法を変更するためのオプション配列を、3 つ目のパラメーターとして指定することもできます。そこにソルトを指定することもできますが、ソルトを指定せず、ランダム・ソルトが自動で生成されるようにするのが最善の方法です。さらに重要なことに、この配列には cost の値を指定することができます。この値 (デフォルト値は 10) によって、アルゴリズムの複雑さが決まります。つまり、ハッシュを生成するための所要時間が決まるということです (この値は、計算時間を長くするために、アルゴリズムを繰り返し実行する回数を変更するための値だと考えてください)。パスワードをよりセキュアにして、ハードウェアでそのパスワードを処理できるようにするには、以下のような呼び出しを実行します。

<?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT, ['cost' => 14]);

私の MacBook Pro をテスト環境として使用した場合、コスト 10 (デフォルト値) で password_hash を生成するのに約 0.085 秒かかります。コストを 14 に増やすと所要時間は 1.394 秒になり、1 回の計算に相当な時間がかかるようになります。

生成されたパスワードを検証する

前の 2 つの例では、パスワードがランダム・ソルト・プロセスによって自動で生成されるため、該当するソルトを直接的に知ることにはなりません。したがって、パスワードを検証する方法として password_hash() をもう一度実行して、生成されたストリングを比較しても、その結果は一致しません。この関数を呼び出すたびに新しいソルトが生成されるため、返されるハッシュは異なります。この拡張機能には、検証プロセスを処理する password_verify() という関数も用意されています。ユーザーが入力したパスワードと保管されているハッシュを渡して password_verify() を呼び出すと、この関数は、パスワードが正しい場合はブール値の TRUE を、正しくない場合は FALSE を返します。

<?php
if (password_verify($password, $hash)) {
    // Correct Password
}

この組み込みパスワード・ハッシュ拡張機能を使用するには、リスト 1 のクラスをリスト 2 のようにリファクタリングすることができます。

リスト 2. リスト 1 の Password クラスのリファクタリング
<?php
class Password {
    public static function hash($password) {
        return password_hash($password, PASSWORD_DEFAULT, ['cost' => 14]);
    }

    public static function verify($password, $hash) {
        return password_verify($password, $hash);
    }
}

変化するセキュリティーのニーズに対処する

新しいパスワード・ハッシュ拡張機能を使用することで、コード・ベースを最新のセキュリティー標準に対応させることができます。しかし、エキスパートたちが SHA-1 をベスト・プラクティスのソリューションと呼んでいたのは、わずか数年前のことです。パスワード暗号化をさらに強化しなければならなくなったとしたら (その時は必ずやってきます)、どうなるのでしょう?幸い、新しい拡張機能には、この起こり得る事態を考慮した機能が組み込まれています。

password_needs_rehash() 関数を使用すると、現在のセキュリティー・ニーズを指定して、保管されているパスワードがそのニーズを満たしているかどうかを (この関数の中で) 調べることができます。このようなことを調べる動機としては、cost パラメーターの値を増やした場合や、PHP の新しいリリースでは内部のハッシュ・アルゴリズムがこれまでとは異なるものに変更されている、といったことが考えられます。これが、アルゴリズムとして PASSWORD_DEFAULT を選択することを推奨する理由です。このオプションを使用すれば、ソフトウェアが常にベスト・プラクティスの最新版を使用することになります。

この機能を使用すると、少し複雑になりますが、複雑になりすぎることはありません。ユーザーのパスワードをチェックする際に (例えば、ユーザーがログインしようとするときに)、もう 1 つ追加の作業を行って password_needs_rehash() を呼び出すだけです。password_needs_rehash() 関数は password_hash() と同様の引数を取り、提供されたパスワード・ハッシュを、新たに要求されたセキュリティー設定に照らしてチェックします。パスワード・ハッシュがこれらのセキュリティー設定に対応していない場合、この関数はその旨をレポートします。

password_needs_rehash() 関数が行うことは、パスワードに再ハッシュの必要があるかどうかを知らせることだけなので、プログラマーによってはここで当惑してしまうかもしれません。その後、パスワードの新しいハッシュ生成して保存するかどうかは、完全にプログラマー次第です。なぜなら、パスワード拡張機能は、パスワードをどのように保管するかについては関知しないからです。

リスト 3 に、これまでに説明したツールを使用した、完全なモックアップ User クラスを記載します。このクラスで、ユーザーのパスワードを安全に処理するとともに、今後変化するセキュリティー・ニーズに対処できるようにします。

リスト 3. パスワード拡張機能を駆使したモック User クラス
<?php
class User {
    // Store password options so that rehash & hash can share them:
    const HASH = PASSWORD_DEFAULT;
    const COST = 14;

    // Internal data storage about the user:
    public $data;

    // Mock constructor:
    public function __construct() {
        // Read data from the database, storing it into $data such as:
        //  $data->passwordHash  and  $data->username
        $this->data = new stdClass();
        $this->data->passwordHash = 'dbd014125a4bad51db85f27279f1040a';
    }

    // Mock save functionality
    public function save() {
        // Store the data from $data back into the database
    }

    // Allow for changing a new password:
    public function setPassword($password) {
        $this->data->passwordHash = password_hash($password, self::HASH, ['cost' => self::COST]);
    }

    // Logic for logging a user in:
    public function login($password) {
        // First see if they gave the right password:
        echo "Login: ", $this->data->passwordHash, "\n";
        if (password_verify($password, $this->data->passwordHash)) {
            // Success - Now see if their password needs rehashed
            if (password_needs_rehash($this->data->passwordHash, self::HASH, ['cost' => self::COST])) {
                // We need to rehash the password, and save it.  Just call setPassword
                $this->setPassword($password);
                $this->save();
            }
            return true; // Or do what you need to mark the user as logged in.
        }
        return false;
    }
}

まとめ

ここまでで、PHP の新しいパスワード・ハッシュ・ライブラリーがどのように機能するか、またセキュリティー侵害からユーザーを保護する上でこのライブラリーがどのように役立つかがわかったことと思います。連載「一新された PHP」の次回の記事では、PHP 言語自体の進化から話題を移し、この言語を中心に進化し始めているエコシステムとツールを取り上げます。最初に取り上げるのは、コミュニティーで爆発的な人気を得ている、Composer という PHP の依存関係マネージャーです。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source, セキュリティ
ArticleID=1008648
ArticleTitle=PHP の新しい側面: 一新された PHP: 最新の PHP でのパスワード・セキュリティー
publish-date=06252015