我们如何在 PHP 中创建一个相当安全的密码哈希?
我一直在阅读有关密码散列的信息,但我阅读的所有论坛都充满了人们在讨论密码散列背后的理论的帖子,而我并不真正理解.
I have been reading up on password hashing, but all the forums I read are full of posts from people debating theory behind it that I don't really understand.
我有一个旧的(并且可能非常弱)密码脚本,如下所示:$hash = sha1($pass1);
I have an old (and presumably extremely weak) password script that reads like this: $hash = sha1($pass1);
function createSalt()
{
$string = md5(uniqid(rand(), true));
return substr($string, 0, 3);
}
$salt = createSalt();
$hash = sha1($salt . $hash);
如果我理解正确的话,salt 越长,黑客必须生成的表就越大以破坏散列.如果我错了,请纠正我.
If I understand correctly, the longer the salt, the larger the table the hacker has to generate in order to break the hash. Please correct me if I am wrong.
我希望编写一个更安全的新脚本,并且我认为这样的事情可以:
I am looking to write a new script that is more secure, and I am thinking that something like this would be okay:
function createSalt()
{
$string = hash('sha256', uniqid(rand(), true));
return $string;
}
$hash = hash('sha256', $password);
$salt = createSalt();
$secret_server_hash = 'ac1d81c5f99fdfc6758f21010be4c673878079fdc8f144394030687374f185ad';
$salt2 = hash('sha256', $salt);
$hash = $salt2 . $hash . $secret_server_hash;
$hash = hash('sha512', $hash );
这样更安全吗?这是否有明显的开销?
Is this more secure? Does this have a noticeable amount of overhead?
最重要的是,有没有更好的方法来确保我的数据库中的密码不能(实际上)通过密码分析来恢复,从而确保安全受到损害的唯一方法是通过我自己的编码错误?
Most importantly, is there some better way to make sure that the passwords in my database cannot be (realistically) recovered by cryptanalysis, thus ensuring that the only way security will be compromised is through my own error in coding?
在阅读了您的所有答案并进一步研究后,我决定继续实施 bcrypt 方法来保护我的密码.话虽如此,出于好奇,如果我要使用上面的代码并在其上放置一个循环,例如 100,000 次迭代,那会完成类似于 bcrypt 的强度/安全性的事情吗?
Upon reading all of your answers and further reasearching, I have decided to go ahead and implement the bcrypt method of protecting my passwords. That being said, for curiosity's sake, if I were to take my above code and put a loop on it for say, 100,000 iterations, would that accomplish something similar to the strength/security of bcrypt?
推荐答案
到目前为止,Salts 只能帮到你.如果您使用的散列算法如此之快以至于生成彩虹表几乎没有成本,那么您的安全性仍然会受到影响.
Salts can only help you so far. If the hashing algorithm you use is so fast that there is little to no cost for generating rainbow tables, your security is still compromised.
几点建议:
- 不对所有密码使用单一盐.每个密码使用随机生成的盐.
- 不重新散列未修改的哈希(冲突问题,看我之前的回答,你需要无限输入来进行哈希).
- 不要不尝试创建自己的散列算法或将匹配算法混合到复杂的操作中.
- 如果遇到损坏/不安全/快速哈希原语,请使用 密钥强化.这增加了攻击者计算彩虹表所需的时间.示例:
- Do NOT use a single salt for all passwords. Use a randomly generated salt per password.
- Do NOT rehash an unmodified hash (collision issue, see my previous answer, you need infinite input for hashing).
- Do NOT attempt to create your own hashing algorithm or mix-matching algorithms into a complex operation.
- If stuck with broken/unsecure/fast hash primitives, use key strengthening. This increases the time required for the attacker to compute a rainbow table. Example:
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;
}
<小时>
与其部署您自己的(固有缺陷)哈希/盐算法,为什么不使用由安全专业人员开发的算法?
Instead of deploying your own (inherently with flaws) hash/salt algorithm, why not use one that was developed by security professionals?
使用 bcrypt.它正是为此而开发的.它的缓慢和多轮确保攻击者必须部署大量资金和硬件才能破解您的密码.再加上每个密码的盐(bcrypt 需要盐),您可以确定如果没有大量资金或硬件,攻击几乎是不可行的.
Use bcrypt. It's been developed exactly for this in mind. It slowness and multiple rounds ensures that an attacker must deploy massive funds and hardware to be able to crack your passwords. Add to that per-password salts (bcrypt REQUIRES salts) and you can be sure that an attack is virtually unfeasible without either ludicrous amount of funds or hardware.
非便携式模式下的便携式PHP散列框架允许您使用bcrypt轻松生成散列.
The Portable PHP Hashing Framework in non-portable mode allows you to generate hashes using bcrypt easily.
您还可以使用 crypt()
函数生成输入的 bcrypt 哈希字符串.如果您沿着这条路线走,请确保为每个哈希生成一个盐.
You can also use crypt()
function to generate bcrypt hashes of input strings. If you go down that route, make sure you generate one salt per hash.
此类可以自动生成盐并根据输入验证现有哈希.
This class can automatically generate salts and verify existing hashes against an input.
class Bcrypt {
private $rounds;
public function __construct($rounds = 12) {
if(CRYPT_BLOWFISH != 1) {
throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
}
$this->rounds = $rounds;
}
public function hash($input) {
$hash = crypt($input, $this->getSalt());
if(strlen($hash) > 13)
return $hash;
return false;
}
public function verify($input, $existingHash) {
$hash = crypt($input, $existingHash);
return $hash === $existingHash;
}
private function getSalt() {
$salt = sprintf('$2a$%02d$', $this->rounds);
$bytes = $this->getRandomBytes(16);
$salt .= $this->encodeBytes($bytes);
return $salt;
}
private $randomState;
private function getRandomBytes($count) {
$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($this->randomState === null) {
$this->randomState = microtime();
if(function_exists('getmypid')) {
$this->randomState .= getmypid();
}
}
for($i = 0; $i < $count; $i += 16) {
$this->randomState = md5(microtime() . $this->randomState);
if (PHP_VERSION >= '5') {
$bytes .= md5($this->randomState, true);
} else {
$bytes .= pack('H*', md5($this->randomState));
}
}
$bytes = substr($bytes, 0, $count);
}
return $bytes;
}
private function encodeBytes($input) {
return strtr(rtrim(base64_encode($input), '='), '+', '.');
}
}
您可以这样使用此代码:
You may use this code as such:
$bcrypt = new Bcrypt(15);
$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);
相关文章