PHP 5.6.0beta1 released

安全なパスワードハッシュ

この節では、なぜハッシュ関数を使ってパスワードを守るのかについての理由と、 ハッシュ処理を効率的に行う方法について説明します。

なぜ、アプリケーションのユーザーが登録したパスワードをハッシュしなければならないのですか?

パスワードのハッシュは、最も基本的なセキュリティ要件のひとつです。 ユーザーからパスワードを受け取るアプリケーションを設計するときには必ず考慮しなければなりません。 ハッシュしなければ、パスワードを格納したデータベースが攻撃を受けたときにパスワードを盗まれてしまいます。 それは即時にアプリケーションが乗っ取られることにつながるし、 もしそのユーザーが他のサービスでも同じアカウント・同じパスワードを使っていればさらに被害が大きくなります。

ユーザーのパスワードにハッシュアルゴリズムを適用してからデータベースに格納しておくと、 攻撃者が元のパスワードを知ることが難しくなります。 とはいえ、パスワードのハッシュ結果との比較は可能です。

しかし、ここで注意すべき点は、パスワードのハッシュ処理はあくまでもデータベースへの不正アクセスからの保護にすぎず、 アプリケーション自体に不正なコードを注入される攻撃からは守れないということです。

よく使われるハッシュ関数である md5()sha1() は、なぜパスワードのハッシュに適していないのですか?

MD5 や SHA1 そして SHA256 といったハッシュアルゴリズムは、 高速かつ効率的なハッシュ処理のために設計されたものです。 最近のテクノロジーやハードウェア性能をもってすれば、 これらのアルゴリズムの出力をブルートフォースで(力ずくで)調べて元の入力を得るのはたやすいことです。

最近のコンピュータではハッシュアルゴリズムを高速に「逆算」できるので、 セキュリティ技術者の多くはこれらの関数をパスワードのハッシュに使わないよう強く推奨しています。

よく使われるハッシュ関数では不適切だというのなら、 パスワードをどうやってハッシュすればいいのですか?

パスワードをハッシュするときに検討すべき重要な二点は、 その計算量とソルトです。 ハッシュアルゴリズムの計算コストが増えれば増えるほど、 ブルートフォースによる出力の解析に時間を要するようになります。

PHP 5.5 以降には ネイティブのパスワードハッシュ API が用意されており、これを使えば ハッシュの計算パスワードの検証 を安全に行えます。 » PHP だけで書かれた同じ機能のライブラリ もあって、これは PHP 5.3.7 以降で使えます。

それ以外には、crypt() 関数を使う方法もあります。 この関数は PHP 5.3 以降で使えるもので、いくつかのハッシュアルゴリズムに対応しています。 この関数を使うときには、指定したアルゴリズムが使えるかどうかを気にする必要はありません。 各アルゴリズムが PHP の内部でネイティブに実装されているので、 ご利用の OS でサポートしていないアルゴリズムでも使うことができます。

パスワードをハッシュするときのおすすめのアルゴリズムは Blowfish です。 パスワードハッシュ API でも、このアルゴリズムをデフォルトで使っています。 というのも、このアルゴリズムは MD5 や SHA1 と比較して計算コストが高いにもかかわらず、スケーラブルだからです。

crypt() でパスワードを検証する場合は、タイミング攻撃に注意が必要です。 タイミング攻撃を避けるため、処理時間が一定な文字列比較処理を使うようにしましょう。 PHP の == 演算子や === 演算子 も、そして strcmp() も、文字列比較の処理時間が一定ではありません。 その点 password_verify() はこの問題を気にしなくて済むので、 可能な限り ネイティブのパスワードハッシュ API を使うようにしましょう。

ソルトとは?

暗号理論におけるソルトとは、ハッシュ処理の際に追加するデータのことです。 事前に計算済みのハッシュとその元入力の対応表 (レインボーテーブル) で出力を解析される可能性を減らすために利用します。

端的に言うと、ソルトとはちょっとした追加データです。 これをつけるだけで、ハッシュをクラックするのが劇的に難しくなります。 事前に計算済みのハッシュとその元入力を大量にまとめた表が、オンラインで多数公開されています。 ソルトを使えば、そのハッシュ値がこれらの表に含まれている可能性を大きく減らすことができます。

password_hash() は、ソルトを指定しなかった場合にはランダムなソルトを作ります。 一般に、これがいちばんお手軽で安全なアプローチでしょう。 approach.

ソルトはどのように保存すればいいのですか?

password_hash()crypt() を使った場合、戻り値であるパスワードハッシュの中にソフトが含まれています。 このソルトは、そのままの形式でデータベースに格納する必要があります。 というのも、利用したハッシュ関数の情報がそこに含まれており、それを直接 password_verify()crypt() に渡せばパスワードの検証ができるからです。

crypt()password_hash() の戻り値の書式を次の図に示します。 このように、使ったアルゴリズムや検証時に必要なソフトに関する情報もすべて含まれています。


        password_hash や crypt が戻す値。
        使ったアルゴリズム、そのアルゴリズムのオプション、使ったソルト、そしてハッシュしたパスワードが続く。

add a note add a note

User Contributed Notes 6 notes

up
10
sgbeal at googlemail dot com
1 year ago
sha1 in conjunction with one or more salt values need not be as insecure as the above makes it out to be. e.g. the Fossil SCM creates an sha1 password hash based on a user's clear-text password combined with the user's name and a shared secret known only to the specific source repository for which the user is set up.
up
10
fluffy at beesbuzz dot biz
1 year ago
The security issue with simple hashing (md5 et al) isn't really the speed, so much as the fact that it's idempotent; two different people with the same password will have the same hash, and so if one person's hash is brute-forced, the other one will as well.  This facilitates rainbow attacks.  Simply slowing the hash down isn't a very useful tactic for improving security.  It doesn't matter how slow and cumbersome your hash algorithm is - as soon as someone has a weak password that's in a dictionary, EVERYONE with that weak password is vulnerable.

Also, hash algorithms such as md5 are for the purpose of generating a digest and checking if two things are probably the same as each other; they are not intended to be impossible to generate a collision for.  Even if an underlying password itself requires a lot of brute forcing to determine, that doesn't mean it will be impossible to find some other bit pattern that generates the same hash in a trivial amount of time.

As such: please, please, PLEASE only use salted hashes for password storage.  There is no reason to implement your own salted hash mechanism, either, as crypt() already does an excellent job of this.
up
2
alf dot henrik at ascdevel dot com
1 month ago
I feel like I should comment some of the clams being posted as replies here.

For starters, speed IS an issue with MD5 in particular and also SHA1. I've written my own MD5 bruteforce application just for the fun of it, and using only my CPU I can easily check a hash against about 200mill. hash per second. The main reason for this speed is that you for most attempts can bypass 19 out of 64 steps in the algorithm. For longer input (> 16 characters) it won't apply, but I'm sure there's some ways around it.

If you search online you'll see people claiming to be able to check against billions of hashes per second using GPUs. I wouldn't be surprised if it's possible to reach 100 billion per second on a single computer alone these days, and it's only going to get worse. It would require a watt monster with 4 dual high-end GPUs or something, but still possible.

Here's why 100 billion per second is an issue:
Assume most passwords contain a selection of 96 characters. A password with 8 characters would then have 96^8 = 7,21389578984e+15 combinations.
With 100 billion per second it would then take 7,21389578984e+15 / 3600 = ~20 hours to figure out what it actually says. Keep in mind that you'll need to add the numbers for 1-7 characters as well. 20 hours is not a lot if you want to target a single user.

So on essence:
There's a reason why newer hash algorithms are specifically designed not to be easily implemented on GPUs.

Oh, and I can see there's someone mentioning MD5 and rainbow tables. If you read the numbers here, I hope you realize how incredibly stupid and useless rainbow tables have become in terms of MD5. Unless the input to MD5 is really huge, you're just not going to be able to compete with GPUs here. By the time a storage media is able to produce far beyond 3TB/s, the CPUs and GPUs will have reached much higher speeds.

As for SHA1, my belief is that it's about a third slower than MD5. I can't verify this myself, but it seems to be the case judging the numbers presented for MD5 and SHA1. The issue with speeds is basically very much the same here as well.

The moral here:
Please do as told. Don't every use MD5 and SHA1 for hasing passwords ever again. We all know passwords aren't going to be that long for most people, and that's a major disadvantage. Adding long salts will help for sure, but unless you want to add some hundred bytes of salt, there's going to be fast bruteforce applications out there ready to reverse engineer your passwords or your users' passwords.
up
2
loveun1x at yahooc dot om
5 months ago
The section "Why are common hashing functions such as md5() and sha1() unsuitable for passwords?" is completely wrong. No one says MD5 and SHA-1 aren't suitable for password hashing due to their speed. (MD5 is broken for digital signatures but not yet for password hashing, but not because it is fast.) A single round of them is unsuitable, but that is not a problem with the underlying algorithms themselves.

Always use a salt, preferably a big (e.g. at least 16 characters) salt. Always run through multiple rounds of of the hashing. Most implementations today use several tens of thousands or hundreds of thousands of rounds, for example 7-zip's encrypted format uses 262,144 rounds of SHA-256.
up
-3
user at wample dot com
29 days ago
password hashing may be better with the use of "phpass"
up
-21
owlstead
8 months ago
Clearly you should by now use PBKDF2, bcrypt or scrypt to store secure "hashes" of passwords. Unfortunately the PHP crypto implementations and documentation keeps propagating insecure protocols and algorithms. Isn't there a single PHP programmer that could clean up the cryptographic functions and libraries?
To Top