Do not store password as plain text
Do not try to invent your own password security
Do not ‘encrypt’ passwords
Do not use MD5
Do not use a single site-wide salt
What you should do
Use a cryptographically strong hashing function like bcrypt (see PHP's crypt() function).
Use a random salt for each password.
Use a slow hashing algorithm to make brute force attacks practically impossible.
For bonus points, regenerate the hash every time a users logs in.
$username = 'Admin';
$password = 'gf45_gdf#4hg';
// A higher "cost" is more secure but consumes more processing power
$cost = 10;
// Create a random salt
$salt = strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.');
// Prefix information about the hash so PHP knows how to verify it later.
// "$2a$" Means we're using the Blowfish algorithm. The following two digits are the cost parameter.
$salt = sprintf("$2a$%02d$", $cost) . $salt;
// Value:
// $2a$10$eImiTXuWVxfM37uY4JANjQ==
// Hash the password with the salt
$hash = crypt($password, $salt);
// Value:
// $2a$10$eImiTXuWVxfM37uY4JANjOL.oTxqp7WylW7FCzx2Lc7VLmdJIddZq
In the above example we turned a reasonably strong password into a hash that we can safely store in a database. The next time the user logs in we can validate the password as follows:
$username = 'Admin';
$password = 'gf45_gdf#4hg';
// For brevity, code to establish a database connection has been left out
$sth = $dbh->prepare('
SELECT
hash
FROM users
WHERE
username = :username
LIMIT 1
');
$sth->bindParam(':username', $username);
$sth->execute();
$user = $sth->fetch(PDO::FETCH_OBJ);
// Hashing the password with its hash as the salt returns the same hash
if ( hash_equals($user->hash, crypt($password, $user->hash)) ) {
// Ok!
}
A few additional tips to prevent user accounts from being hacked:
Limit the number of failed login attempts.
Require strong passwords.
Do not limit passwords to a certain length (remember, you're only storing a hash so length doesn't matter).
Allow special characters in passwords, there is no reason not to.
注意:hash_equals (PHP 5 >= 5.6.0) 如果你的php版本 phpversion()不够,可以尝试使用下面的代码
password_compat
This library requires PHP >= 5.3.7 OR a version that has the $2y fix backported into it (such as RedHat provides). Note that Debian's 5.3.3 version is NOT supported.
使用前,用下面代码测试当前域名是否可以用这个password_compat
require "lib/password.php";
echo "Test for functionality of compat library: " . (PasswordCompatbinarycheck() ? "Pass" : "Fail");
echo "n";
Usage
Creating Password Hashes
To create a password hash from a password, simply use the password_hash function.
$hash = password_hash($password, PASSWORD_BCRYPT);
Note that the algorithm that we chose is PASSWORD_BCRYPT. That's the current strongest algorithm supported. This is the BCRYPT crypt algorithm. It produces a 60 character hash as the result.
BCRYPT also allows for you to define a cost parameter in the options array. This allows for you to change the CPU cost of the algorithm:
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));
That's the same as the default. The cost can range from 4 to 31. I would suggest that you use the highest cost that you can, while keeping response time reasonable (I target between 0.1 and 0.5 seconds for a hash, depending on use-case).
Another algorithm name is supported:
PASSWORD_DEFAULT
This will use the strongest algorithm available to PHP at the current time. Presently, this is the same as specifying PASSWORD_BCRYPT. But in future versions of PHP, it may be updated to use a stronger algorithm if one is introduced. It can also be changed if a problem is identified with the BCRYPT algorithm. Note that if you use this option, you are strongly encouraged to store it in a VARCHAR(255) column to avoid truncation issues if a future algorithm increases the length of the generated hash.
It is very important that you should check the return value of password_hash prior to storing it, because a false may be returned if it encountered an error.
Verifying Password Hashes
To verify a hash created by password_hash, simply call:
if (password_verify($password, $hash)) {
/* Valid */
} else {
/* Invalid */
}
That's all there is to it.
Rehashing Passwords
From time to time you may update your hashing parameters (algorithm, cost, etc). So a function to determine if rehashing is necessary is available:
if (password_verify($password, $hash)) {
if (password_needs_rehash($hash, $algorithm, $options)) {
$hash = password_hash($password, $algorithm, $options);
/* Store new hash in db */
}
}