更新后的 PHP 现代 PHP 中的密码安全性(3)
 
- UID
- 1066743
|

更新后的 PHP 现代 PHP 中的密码安全性(3)
介绍 password_hash() 这个密码哈希扩展为您创建在计算上复杂的安全的密码哈希值,包括在幕后生成和处理随机的 SALT。在对您想要计算哈希值的密码调用 password_hash() 的最简单用例中,该扩展会为您处理所有事情。您还需要提供第二个参数:您想要扩展使用的哈希算法。您有两种选择,但在目前,指定 PASSWORD_DEFAULT 常量是最佳选择(我稍后会解释其中的原因):
1
2
| <?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
|
指定 cost 参数 也可提供第三个参数,这是一个更改哈希值生成方式的选项数组。您可以在这里指定 SALT,但最好是不指定 SALT,并允许为您生成随机 SALT。更重要的是,在这个数组中,您可以指定一个 cost 值。此值(默认值为 10)可以确定该算法应多复杂,进而确定生成哈希值将花费多长时间。(将此值视为更改算法本身重新运行的次数,以减缓计算。)如果想要一个更安全的密码,而且您的计算机能够处理它,您可以像这样执行调用:
1
2
| <?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT, ['cost' => 14]);
|
使用我自己的 MacBook Pro 作为测试环境,生成一个 cost 为 10(默认值)的 password_hash 大约会花 0.085 秒的时间。将该 cost 调高到 14,会将该时间更改为每次计算 1.394 秒。
验证生成的密码 因为前面两个示例中的密码是使用一个随机 SALT 过程生成的,所以我无法直接知道相关的 SALT。因此,如果我尝试再次运行 password_hash() 并比较字符串,以此作为验证密码的方式,结果将会不匹配。每次您调用该函数,都会生成一个新 SALT,返回的哈希值也不同。所以该扩展提供了第二个函数 password_verify(),它为您处理验证过程。您调用 password_verify(),传入用户所提供的密码和存储的哈希值,如果密码是正确的,该函数返回一个布尔值 TRUE,否则返回 FALSE:
1
2
3
4
| <?php
if (password_verify($password, $hash)) {
// Correct Password
}
|
现在我可以重构 中的类,使用内置的密码哈希扩展,如清单 2 所示。
清单 2. 重构清单 1 的 Password 类1
2
3
4
5
6
7
8
9
10
| <?php
class Password {
public static function hash($password) {
return password_hash($password, PASSWORD_DEFAULT, ['cost' => 14]);
}
public static function verify($password, $hash) {
return password_verify($password, $hash);
}
}
|
处理不断变化的安全需求 通过使用新的密码哈希扩展,您可以将您的代码库提升到如今的安全标准水平。但仅在几年前,专家曾说过,SHA-1 是一种最佳实践解决方案。那么,如果它不是最佳实践,当 密码加密需要更强时,会发生什么?幸运的是,新扩展有一个内置的功能考虑了这一可能性。
可以使用 password_needs_rehash() 函数(在幕后)检测存储的密码是否与您指定的当前安全需求相匹配。如果不匹配,原因可能是,您增加了 cost 参数,或者一个新的 PHP 版本在幕后更改为一种不同的哈希算法。正因如此,PASSWORD_DEFAULT 应当是您首选的算法;此选项始终会使您的软件使用当前的最佳实践版本。
此功能的使用更加复杂,但不是过于复杂。核对用户的密码时(比如用户尝试登录时),您需要执行一个额外的任务:调用 password_needs_rehash(),它接受与 password_hash() 类似的参数。password_needs_rehash() 函数针对新请求的安全设置来检查所提供的密码哈希值。如果密码哈希值与这些设置不匹配,那么该函数会向您报告这一事实。
一些程序员在这里难以理解,因为 password_needs_rehash() 函数所做的一切都是为了让您知道密码是否需要重新计算哈希值。然后是否生成密码的新哈希值并保存它,这完全取决于您,因为密码扩展不知道您需要如何存储密码。
在清单 3 中,我提供了一个完整的模拟的 User 类,在这个类中,通过使用我讨论的工具,既能安全地处理用户的密码,又能支持未来不断变化的安全需求。
清单 3. 这个模拟的 User 类显示了密码扩展的完整用途1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| <?php
class User {
// Store password options so that rehash & hash can share them:
const HASH = PASSWORD_DEFAULT;
const COST = 14;
// Internal data storage about the user:
public $data;
// Mock constructor:
public function __construct() {
// Read data from the database, storing it into $data such as:
// $data->passwordHash and $data->username
$this->data = new stdClass();
$this->data->passwordHash = 'dbd014125a4bad51db85f27279f1040a';
}
// Mock save functionality
public function save() {
// Store the data from $data back into the database
}
// Allow for changing a new password:
public function setPassword($password) {
$this->data->passwordHash = password_hash($password, self::HASH, ['cost' => self::COST]);
}
// Logic for logging a user in:
public function login($password) {
// First see if they gave the right password:
echo "Login: ", $this->data->passwordHash, "\n";
if (password_verify($password, $this->data->passwordHash)) {
// Success - Now see if their password needs rehashed
if (password_needs_rehash($this->data->passwordHash, self::HASH, ['cost' => self::COST])) {
// We need to rehash the password, and save it. Just call setPassword
$this->setPassword($password);
$this->save();
}
return true; // Or do what you need to mark the user as logged in.
}
return false;
}
}
|
|
|
|
|
|
|