ユーザーのパスワードを安全に保存するにはどうすればよいですか?


169

これは単純なMD5よりもはるかに安全ですか?パスワードのセキュリティについて調べ始めたところです。PHPは初めてです。

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}

php 5.4+にはこれが組み込まれていることに注意してください
Benjamin

OpenwallのPHPパスワードハッシュフレームワーク(PHPass)もご覧ください。その移植性があり、ユーザーパスワードに対する多くの一般的な攻撃に対して強化されています。
jww 2014年

1
今日のこの問題に出くわした人々のために、義務的な「文字列補間の代わりにPDOを使用する」。
モニカの訴訟に資金を提供

回答:


270

パスワードストレージスキームを安全にする最も簡単な方法は、標準ライブラリを使用することです。

セキュリティは、ほとんどのプログラマが単独で取り組むよりもはるかに複雑で目に見えない混乱の可能性がある傾向があるため、標準ライブラリを使用することは、ほとんど常に最も簡単で最も安全な(唯一ではないにせよ)利用可能なオプションです。


新しいPHPパスワードAPI(5.5.0以降)

PHPバージョン5.5.0以降を使用している場合は、新しい簡略化されたパスワードハッシュAPIを使用できます

PHPのパスワードAPIを使用したコードの例:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(引き続きレガシー5.3.7以降を使用している場合は、ircmaxell / password_compatをインストールして、組み込み関数にアクセスできます)


塩漬けのハッシュを改善する:コショウを加える

追加のセキュリティが必要な場合、セキュリティ担当者は(2017年)、(自動的に)ソルトされたパスワードハッシュに' pepper 'を追加することを推奨しています。

このパターンを安全に実装する単純なドロップインクラスがあります。Netsilik/ PepperedPasswordsgithub)をお勧めします 。
MITライセンスが付属しているため、プロプライエタリプロジェクトでも、好きなように使用できます。

を使用したコードの例Netsilik/PepperedPasswords

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


OLD標準ライブラリ

注意:これはもう必要ありません!これは歴史的な目的のためだけにあります。

見てください:ポータブルなPHPパスワードハッシュフレームワークphpassCRYPT_BLOWFISH。可能な場合は、アルゴリズムを使用してください。

phpass(v0.2)を使用したコードの例:

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPassはいくつかの非常によく知られたプロジェクトに実装されています:

  • phpBB3
  • WordPress 2.5以降およびbbPress
  • Drupal 7リリース(Drupal 5および6で利用可能なモジュール)
  • その他

良い点は、詳細について心配する必要がないことです。これらの詳細は、経験を持つ人々によってプログラムされ、インターネット上の多くの人々によってレビューされています。

パスワードの保存方法の詳細については、Jeffのブログ投稿を読んでください:パスワードを誤って保存している可能性があります

私は自分でやるよ、ありがとう」というアプローチをとるなら何をするにMD5せよ、SHA1もう使わないください。これらは優れたハッシュアルゴリズムですが、セキュリティ上の理由から壊れていると考えられています

現在、CRYPT_BLOWFISHでcryptを使用することがベストプラクティスです。
PHPのCRYPT_BLOWFISHは、Bcryptハッシュの実装です。BcryptはBlowfishブロック暗号に基づいており、アルゴリズムの速度を落とすために高価なキー設定を利用しています。


29

SQLステートメントを連結する代わりにパラメータ化されたクエリを使用すると、ユーザーははるかに安全になります。また、ソルトはユーザーごとに一意である必要があり、パスワードハッシュとともに保存する必要があります。


1
Nettuts +にはPHPのセキュリティに関する優れた記事があり、パスワードのソルティングについても説明されています。たぶん、あなたは見てとる必要があります。 net.tutsplus.com/tutorials/php/...を
ファビオ・アントゥネス

3
Nettuts +は、モデルとして使用するには非常に悪い記事です。MD5の使用が含まれているため、ソルトを使用しても非常に簡単にブルートフォースすることができます。:代わりに、ちょうどすなわち、はるかに良いチュートリアルサイト上で見つけることが任意のコードよりも、この答え遠いPHPassライブラリを使用stackoverflow.com/questions/1581610/...
RichVel

11

より良い方法は、各ユーザーが独自のソルトを持つことです。

ソルトを使用する利点は、攻撃者がすべての辞書単語のMD5署名を事前に生成することが困難になることです。ただし、固定ソルトがあることを攻撃者が知った場合、固定ソルトが前に付いているすべての辞書の単語のMD5署名を事前に生成する可能性があります。

より良い方法は、ユーザーがパスワードを変更するたびに、システムがランダムなソルトを生成し、そのソルトをユーザーレコードと一緒に保存することです。(MD5署名を生成する前にソルトを検索する必要があるため)パスワードのチェックは少しコストがかかりますが、攻撃者がMD5を事前に生成することははるかに困難になります。


3
ソルトは通常、パスワードハッシュ(たとえば、crypt()関数の出力)と共に保存されます。とにかくパスワードハッシュを取得する必要があるため、ユーザー固有のソルトを使用しても、手順のコストは高くなりません。(または、新しいランダムソルトを生成するのはコストがかかると思いましたか?私は本当にそうは思いません。)そうでなければ+1。
Inshallah

セキュリティ上の理由から、ストアドプロシージャを介してのみテーブルへのアクセスを提供し、ハッシュが返されないようにすることができます。代わりに、クライアントはハッシュと考えているものを渡し、成功または失敗のフラグを取得します。これは試みをログに記録するストアドプロシージャを可能にする、セッションを作成、など
スティーブンSudit

@Inshallah-すべてのユーザーが同じソルトを持っている場合、user1で使用した辞書攻撃をuser2に対して再利用できます。ただし、各ユーザーが一意のソルトを持っている場合は、攻撃するユーザーごとに新しい辞書を生成する必要があります。
R Samuel Klatchko

@Rサミュエル-それがまさに私があなたの回答に投票した理由です。なぜなら、そのような攻撃を回避するためのベストプラクティス戦略を推奨しているからです。私のコメントは、ユーザーごとのソルトの追加コストに関してあなたが言ったことについて私の困惑を表現するためのものでしたが、私にはまったくわかりませんでした。(「塩は通常パスワードハッシュと共に保存されるため」、ユーザーごとのソルトの追加のストレージおよびCPU要件は非常に微視的であるため、言及する必要さえありません...)
Inshallah

@Inshallah-ハッシュされたパスワードが大丈夫かどうかデータベースをチェックする場合を考えていました(ソルトを取得するための1つのdb検索と、ハッシュされたパスワードをチェックするための2番目のdbアクセスがあります)。一度の検索でソルト/ハッシュされたパスワードをダウンロードして、クライアントで比較を行う場合は正しいです。混乱させて申し訳ありません。
R Samuel Klatchko

11

PHP 5.5で(私が説明するものであっても、以前のバージョンに提供されています、下記参照)私は内蔵のソリューション、その新しいを使用することをお勧めしたいのですが、コーナーの周り:password_hash()password_verify()。必要なパスワードセキュリティのレベルを達成するためにいくつかのオプションを提供します(たとえば、$options配列を介して「コスト」パラメーターを指定することにより)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

戻ります

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

ご覧のとおり、文字列にはソルトとオプションで指定されたコストが含まれています。また、使用されるアルゴリズムも含まれています。

したがって、パスワードをチェックするとき(たとえば、ユーザーがログインするとき)、補足password_verify()機能を使用するときに、必要な暗号パラメーターをパスワードハッシュ自体から抽出します。

ソルトを指定しない場合password_hash()、ソルトはランダムに生成されるため、生成されるパスワードハッシュはの呼び出しごとに異なります。したがって、正しいパスワードであっても、以前のハッシュと新しく生成されたハッシュを比較すると失敗します。

確認は次のように機能します。

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

これらの組み込み関数を提供することで、プログラマーが適切な実装を行わなければならないという考えの量が減るので、データ盗難の場合にすぐにパスワードのセキュリティが向上することを願っています。

password_hashPHP 5.3.7以降でPHP 5.5を提供する小さなライブラリ(1つのPHPファイル)があります:https : //github.com/ircmaxell/password_compat


2
ほとんどの場合、saltパラメータを省略した方がよいでしょう。この関数はオペレーティングシステムのランダムソースからソルトを作成します。自分でより良いソルトを提供できる可能性はほとんどありません。
martinstoeckli 2013

1
それは私が書いたものですよね?「ソルトが指定されていない場合、ランダムに生成されます。そのため、ソルトを指定しないことをお
勧めし

ほとんどの例は、ソルトを追加することが推奨されていない場合でも、両方のパラメーターを追加する方法を示しているので、なぜでしょうか?正直なところ、コードの後ろのコメントだけを読み、次の行は読みません。とにかく、例が関数を最もよく使用する方法を示すとき、それはより良いのではないでしょうか?
martinstoeckli 2013

あなたは正しい、私は同意する。私はそれに応じて私の答えを変更し、行をコメントアウトしました。ありがとう
akirk 2013

保存したパスワードと入力したパスワードが同じかどうかを確認するにはどうすればよいですか。使用password_hash()してpassword_verifyいるパスワード(正しいかどうかに関係なく)を使用しています。最終的に正しいパスワードが
返され

0

それで結構です。Atwood氏は、レインボーテーブルに対するMD5の強さについて書いています。基本的には、長い塩を使ってきれいに座っています(いくつかのランダムな句読点/数字は、改善される可能性があります)。

最近人気が高まっていると思われるSHA-1もご覧ください。


6
Atwood氏の投稿(赤字)の下部にあるメモは、MD5、SHA1、およびパスワードを格納するためのその他の高速ハッシュを使用すると述べているセキュリティ専門家の別の投稿にリンクしています。
sipwiz、2009年

2
@Matthew Scharley:高価なパスワードハッシュアルゴリズムによって課される追加の労力が誤ったセキュリティであることには同意しません。簡単に推測できるパスワードの総当たりを防ぐためです。ログイン試行を制限している場合は、同じことから保護しています(少し効果的です)。しかし、攻撃者がDBに格納されたハッシュにアクセスできる場合、そのような(簡単に推測できる)パスワードをかなり簡単に(簡単に推測できる程度に応じて)総当たりすることができます。SHA-256暗号化アルゴリズムのデフォルトは10000ラウンドなので、10000倍難しくなります。
Inshallah

3
遅いハッシュは、実際には速いハッシュを非常に多数回繰り返し、各反復の間にデータをシャッフルすることによって作成されます。目標は、悪意のあるユーザーがパスワードハッシュのコピーを取得した場合でも、ハッシュに対して辞書をテストするためにかなりの量のCPU時間を消費する必要があることを確認することです。
カフェ

4
@caf:bcryptアルゴリズムは、パラメーター化可能なEksblowfishキースケジューリングの高価さを利用していると思います。これがどのように機能するかは完全にはわかりませんが、暗号化が行われる前に、暗号コンテキストオブジェクトの初期化中にキースケジューリングが非常にコストのかかる操作になることがよくあります。
Inshallah

3
Inshallah:これは真実です-bcryptアルゴリズムは別の設計であり、基礎となる暗号プリミティブはハッシュ関数ではなくブロック暗号です。私はPHKのMD5 crypt()のようなハッシュ関数に基づくスキームを参照していました。
カフェ、

0

追加したい:

  • ユーザーパスワードを長さで制限しない

古いシステムとの互換性のために、パスワードの最大長に制限を設けることがよくあります。これは悪いセキュリティポリシーです。制限を設定する場合は、パスワードの最小の長さに対してのみ設定してください。

  • ユーザーのパスワードをメールで送信しない

忘れたパスワードを回復するには、ユーザーがパスワードを変更できるアドレスを送信する必要があります。

  • ユーザーパスワードのハッシュを更新する

パスワードハッシュが古くなっている可能性があります(アルゴリズムのパラメーターが更新されている可能性があります)。関数password_needs_rehash()を使用して、それをチェックアウトできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.