php 如何安全地存储用户的密码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1581610/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 03:10:18  来源:igfitidea点击:

How can I store my users' passwords safely?

phpsecuritypasswordssaltpassword-hash

提问by Rebar

How much more safe is this than plain MD5? I've just started looking into password security. I'm pretty new to PHP.

这比普通的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]!";
}

回答by Jacco

The easiest way to get your password storage scheme secure is by using a standard library.

确保密码存储方案安全的最简单方法是使用标准库

Because security tends to be a lot more complicated and with more invisible screw up possibilities than most programmers could tackle alone, using a standard library is almost always easiest and most secure (if not the only) available option.

因为安全性往往比大多数程序员单独解决的要复杂得多,而且有更多不可见的搞砸可能性,所以使用标准库几乎总是最简单和最安全(如果不是唯一的话)的可用选项。



The new PHP password API (5.5.0+)

新的 PHP 密码 API (5.5.0+)

If you are using PHP version 5.5.0 or newer, you can use the new simplified password hashing API

如果您使用的是 PHP 5.5.0 或更高版本,则可以使用新的简化密码散列 API

Example of code using PHP's password 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';
}

(In case you are still using legacy 5.3.7 or newer you can install ircmaxell/password_compatto have access to the build-in functions)

(如果您仍在使用旧版 5.3.7 或更新版本,您可以安装ircmaxell/password_compat以访问内置函数)



Improving upon salted hashes: add pepper

改进盐渍哈希:加入胡椒粉

If you want extra security, the security folks now (2017) recommend adding a 'pepper' to the (automatically) salted password hashes.

如果您想要额外的安全性,安全人员现在 (2017) 建议在(自动)加盐密码哈希中添加“胡椒”。

There is a simple, drop in class that securely implements this pattern, I recommend: Netsilik/PepperedPasswords(github).
It comes with a MIT License, so you can use it however you want, even in proprietary projects.

有一个简单的类可以安全地实现这种模式,我推荐: Netsilik/PepperedPasswords( github)。
它带有 MIT 许可证,因此您可以随心所欲地使用它,即使在专有项目中也是如此。

Example of code using Netsilik/PepperedPasswords:

使用的代码示例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';
}



The OLD standard library

旧标准库

Please note:you should not be needing this anymore! This is only here for historical purposes.

请注意:您不应该再需要这个了!这只是为了历史目的。

Take a look at: Portable PHP password hashing framework: phpassand make sure you use the CRYPT_BLOWFISHalgorithm if at all possible.

看一看:Portable PHP password hashing framework: phpass并确保CRYPT_BLOWFISH尽可能使用该算法。

Example of code using phpass (v0.2):

使用 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 has been implemented in some quite well known projects:

PHPass 已经在一些非常著名的项目中实现:

  • phpBB3
  • WordPress 2.5+ as well as bbPress
  • the Drupal 7 release, (module available for Drupal 5 & 6)
  • others
  • phpBB3
  • WordPress 2.5+ 以及 bbPress
  • Drupal 7 版本,(可用于 Drupal 5 和 6 的模块)
  • 其他

The good thing is that you do not need to worry about the details, those details have been programmed by people with experience and reviewed by many folks on the internet.

好处是你不需要担心细节,这些细节是由有经验的人编写的,并被互联网上的许多人过。

For more information on password storage schemes, read Jeff`s blog post: You're Probably Storing Passwords Incorrectly

有关密码存储方案的更多信息,请阅读Jeff的博客文章:您可能错误地存储密码

Whatever you do if you go for the 'I'll do it myself, thank you' approach, do not use MD5or SHA1anymore. They are nice hashing algorithm, but considered broken for security purposes.

无论你做什么,如果你采用“我自己做,谢谢”的方法,不要使用MD5SHA1不再使用。它们是不错的散列算法,但出于安全目的被认为已损坏

Currently, using crypt, with CRYPT_BLOWFISH is the best practice.
CRYPT_BLOWFISH in PHP is an implementation of the Bcrypt hash. Bcrypt is based on the Blowfish block cipher, making use of it's expensive key setup to slow the algorithm down.

目前,使用crypt和 CRYPT_BLOWFISH 是最佳实践。
PHP 中的 CRYPT_BLOWFISH 是 Bcrypt 哈希的实现。Bcrypt 基于 Blowfish 分组密码,利用其昂贵的密钥设置来减慢算法速度。

回答by Anton Gogolev

Your users will be much safer if you used parameterized queries instead of concatenating SQL statements. And the saltshould be unique for each user and should be stored along with the password hash.

如果您使用参数化查询而不是串联 SQL 语句,您的用户将更加安全。并且对于每个用户应该是唯一的,并且应该与密码哈希一起存储。

回答by akirk

With PHP 5.5 (what I describe is available to even earlier versions, see below) around the corner I'd like to suggest to use its new, built-in solution: password_hash()and password_verify(). It provides several options in order to achieve the level of password security you need (for example by specifying a "cost" parameter through the $optionsarray)

随着 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));
?>

will return

将返回

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

As you might see, the string contains the salt as well as the cost that was specified in the options. It also contains the algorithm used.

如您所见,该字符串包含盐以及选项中指定的成本。它还包含所使用的算法。

Therefore, when checking the password (for example when the user logs in), when using the complimentary password_verify()function it will extract the necessary crypto parameters from the password hash itself.

因此,在检查密码时(例如当用户登录时),当使用免费password_verify()功能时,它将从密码哈希本身中提取必要的加密参数。

When not specifying a salt, the generated password hash will be different upon every call of password_hash()because the salt is generated randomly. Therefore comparing a previous hash with a newly generated one will fail, even for a correct password.

当不指定盐时,每次调用时生成的密码哈希都会不同,password_hash()因为盐是随机生成的。因此,即使密码正确,将先前的哈希值与新生成的哈希值进行比较也会失败。

Verifying works like this:

验证工作如下:

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

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

I hope that providing these built-in functions will soon provide better password security in case of data theft, as it reduces the amount of thought the programmer has to put into a proper implementation.

我希望提供这些内置函数将很快在数据被盗的情况下提供更好的密码安全性,因为它减少了程序员必须投入正确实现的思想量。

There is a small library (one PHP file) that will give you PHP 5.5's password_hashin PHP 5.3.7+: https://github.com/ircmaxell/password_compat

有一个小型库(一个 PHP 文件)可以password_hash在 PHP 5.3.7+ 中为您提供 PHP 5.5 :https: //github.com/ircmaxell/password_compat

回答by R Samuel Klatchko

A better way would be for each user to have a unique salt.

更好的方法是让每个用户都有一个独特的盐。

The benefit of having a salt is that it makes it harder for an attacker to pre-generate the MD5 signature of every dictionary word. But if an attacker learns that you have a fixed salt, they could then pre-generate the MD5 signature of every dictionary word prefixed by your fixed salt.

使用 salt 的好处是它使攻击者更难预先生成每个字典单词的 MD5 签名。但是,如果攻击者得知您有固定盐分,他们就可以预先生成每个以您的固定盐分为前缀的字典单词的 MD5 签名。

A better way is each time a user changes their password, your system generate a random salt and store that salt along with the user record. It makes it a bit more expensive to check the password (since you need to look up the salt before you can generate the MD5 signature) but it makes it much more difficult for an attacker to pre-generate MD5's.

更好的方法是每次用户更改密码时,您的系统都会生成一个随机盐并将该盐与用户记录一起存储。这使得检查密码的成本更高一些(因为您需要在生成 MD5 签名之前查找 salt),但它使攻击者预生成 MD5 变得更加困难。

回答by R Samuel Klatchko

I want to add:

我想补充:

  • Don't limit users passwords by length
  • 不要按长度限制用户密码

For compatibility with old systems often set a limit for the maximum length of the password. This is a bad security policy: if you set restriction, set it only for the minimum length of passwords.

为了与旧系统兼容,通常会设置密码的最大长度限制。这是一个糟糕的安全策略:如果您设置了限制,请仅针对密码的最小长度进行设置。

  • Don't send user passwords via email
  • 不要通过电子邮件发送用户密码

For recovering a forgotten password you should send the address by which user can change the password.

要恢复忘记的密码,您应该发送用户可以更改密码的地址。

  • Update the hashes of users passwords
  • 更新用户密码的哈希值

The password hash may be out of date (parameters of the algorithm may be updated). By using the function password_needs_rehash()you can check it out.

密码哈希可能已过时(算法的参数可能会更新)。通过使用该功能,password_needs_rehash()您可以查看它。

回答by nickf

That's fine with me. Mr Atwood wrote about the strength of MD5 against rainbow tables, and basically with a long salt like that you're sitting pretty (though some random punctuation/numbers, it could improve it).

这对我来说没问题。阿特伍德先生写了关于MD5 对抗彩虹桌的强度,基本上用长盐就像你坐得很漂亮(虽然一些随机的标点符号/数字,它可以改善它)。

You could also look at SHA-1, which seems to be getting more popular these days.

您还可以查看 SHA-1,它最近似乎越来越流行。