首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

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

更新后的 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;
    }
}

返回列表