すべてのパスワードで使用される秘密の文字列ではなく、パスワードごとにソルトを生成する必要があります。ソルトを再利用すると、攻撃者は、パスワードごとに 1 つではなく、パスワードごとに 1 つのレインボー テーブルを作成するだけで済みます。
安全なハッシュについて書いた以前の回答を読むことをお勧めします。ルールは簡単です:
- すべてのパスワードに単一のソルトを使用しないでください。パスワードごとにランダムに生成されたソルトを使用します。
- 変更されていないハッシュを再ハッシュしないでください (衝突の問題、以前の回答を参照してください。ハッシュには無限の入力が必要です)。
- 独自のハッシュ アルゴリズムを作成したり、マッチング アルゴリズムを組み合わせて複雑な操作を行ったりしないでください。
- 壊れた/安全でない/高速なハッシュ プリミティブで立ち往生している場合は、キー強化を使用します。これにより、攻撃者がレインボー テーブルを計算するのに必要な時間が増加します。例:
function strong_hash($input, $salt = null, $algo = 'sha512', $rounds = 20000) {
if($salt === null) {
$salt = crypto_random_bytes(16);
} else {
$salt = pack('H*', substr($salt, 0, 32));
}
$hash = hash($algo, $salt . $input);
for($i = 0; $i < $rounds; $i++) {
// $input is appended to $hash in order to create
// infinite input.
$hash = hash($algo, $hash . $input);
}
// Return salt and hash. To verify, simply
// passed stored hash as second parameter.
return bin2hex($salt) . $hash;
}
function crypto_random_bytes($count) {
static $randomState = null;
$bytes = '';
if(function_exists('openssl_random_pseudo_bytes') &&
(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
$bytes = openssl_random_pseudo_bytes($count);
}
if($bytes === '' && is_readable('/dev/urandom') &&
($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
$bytes = fread($hRand, $count);
fclose($hRand);
}
if(strlen($bytes) < $count) {
$bytes = '';
if($randomState === null) {
$randomState = microtime();
if(function_exists('getmypid')) {
$randomState .= getmypid();
}
}
for($i = 0; $i < $count; $i += 16) {
$randomState = md5(microtime() . $randomState);
if (PHP_VERSION >= '5') {
$bytes .= md5($randomState, true);
} else {
$bytes .= pack('H*', md5($randomState));
}
}
$bytes = substr($bytes, 0, $count);
}
return $bytes;
}
ただし、将来的に適応可能なbcryptを使用する必要があります。繰り返しになりますが、より詳細な例については、以前の回答をご覧ください。