如何将 Argon2 算法与 password_hash 结合使用?

How do I use the Argon2 algorithm with password_hash?

所以听说PHP7.2引入了新的Argon2 algorithm。但是我对如何将它与现有代码一起使用感到困惑。例如,我有这个

$password = password_hash('somepassword', PASSWORD_DEFAULT, ['cost' => 12]);

PASSWORD_DEFAULT 现在使用 Argon2 吗? password_verify 我需要更改什么(如果有的话)? bcrypt 现在被认为是不安全的吗?

Argon2 是什么? bcrypt 现在不好了吗?

在 PHP 7.2 之前,唯一使用的散列算法 password_hash 是 bcrypt。在撰写本文时,bcrypt 仍然被认为是一种强哈希,尤其是与其前身 md5sha1(两者都是 insecure because they are fast). Argon2 is simply a costlier algorithm to brute force

相比)

Argon2i uses data-independent memory access. It is slower because it makes more passes over the memory to protect from trade off attacks. It is highly recommended for password hashing and password-based key derivation.

Bcrypt 仍然是可接受的密码散列。如果您不想切换,则无需切换(从 7.2.0 版本开始)。此外,PASSWORD_DEFAULT 应该只在下一个完整版本(7.3.0 或更高版本)上更改(根据 PHP Internals policy)。如果你想确保只使用 bcrypt 继续,你可以使用 PASSWORD_BCRYPT 代替。然而,这是不必要的,我们将在下面讨论。

你如何使用 Argon2?

首先,我们将 password_hash 的第二个参数切换到其中一个参数

  • PASSWORD_ARGON2I - PHP 7.2.0+
  • PASSWORD_ARGON2ID - PHP 7.3.0+(如果可用则首选,请参阅下面的注释)

然后我们需要更改选项。 bcrypt 使用 cost 作为迭代密码次数的参数(更高的成本 = 更长的散列时间)。然而,有不同的成本因素

password_hash('somepassword', PASSWORD_ARGON2I, ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]);

From the manual 我们看看这些选项的作用

  • memory_cost - 可用于计算 Argon2 哈希(默认 1024)的最大内存(以 kibibytes 为单位)
  • time_cost - 计算 Argon2 哈希值可能需要的最长时间(默认 2)
  • threads - 用于计算 Argon2 哈希的线程数(默认 2)

请注意,在您更改这些之前,此处的更高成本会减慢您的脚本。您需要 运行 在您的服务器上进行测试,以找到最适合您的设置。这通常是通过循环给定成本的多次迭代来实现的。 PHP manual gives an example of this 如果你需要的话。

另请注意,虽然 bcrypt 存储 60 个字符,但 Argon2 可能需要更多。理想情况下,您应该让密码字段存储 255 个字符。

我们在 password_verify 中改变了什么?

答案是……没什么。明白 password_verify 足够聪明,可以弄清楚使用了什么算法并适当地处理它。如上所述,这意味着如果您使用 PASSWORD_DEFAULT,则默认值可以更改并且不会对您产生负面影响(尽管您可能需要调整成本参数)。 password_verify 只需要它支持的算法。如果您从 bcrypt 切换到 Argon2,两者将以相同的方式进行验证,因为所有必要的数据(salt、hash 和 cost)都已为您存储。

//Works for both bcrypt and Argon2
if(password_verify($user_password, $stored_hash)) {
    // password validated
}

如果您想从 bcrypt 升级散列,您可以在用户成功登录时执行此操作(并因此为您提供未散列的密码)。只需检查您的散列是否以 y$(bcrypt 标记)开头。如果是,再次将提供的密码传递给 password_hash,但使用 Argon2 参数,并将其保存到登录用户的密码字段。

Argon2ID 是什么?

Introduced in PHP 7.3, Argon2ID makes some improvements over Argon2I as noted in this Crypto.SE question

The best tradeoff attack on 1-pass Argon2id is the combined low-storage attack (for the first half of the memory) and the ranking attack (for the second half), which bring together the factor of about 2.1.

Argon2ID 使用与 Argon2I 相同的参数。

仅当您使用 PHP 7.3: 我创建了 2 个简单而精巧的函数来使用 Argon2ID 和 PHP:

function argon2idHash($plaintext, $password, $encoding = null) {
    $plaintextsecured = hash_hmac("sha256", $plaintext, $password);
    return $encoding == "hex" ? bin2hex(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : ($encoding == "base64" ? base64_encode(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : password_hash($plaintextsecured, PASSWORD_ARGON2ID));
}

function argon2idHashVerify($plaintext, $password, $hash, $encoding = null) {
    $plaintextsecured = hash_hmac("sha256", $plaintext, $password);
    return password_verify($plaintextsecured, $encoding == "hex" ? hex2bin($hash) : ($encoding == "base64" ? base64_decode($hash) : $hash)) ? true : false;
}

获取散列值使用(最后一个参数是可选的,您可以选择hexbase64或什么都不选)[return => 字符串]:

$salt = "LALALA";
argon2idHash($clearvalue, $salt, "hex"); // with encoding
argon2idHash($clearvalue, $salt); // without encoding

验证散列值使用(参数 $salt 必须与散列时设置的盐相匹配,如果使用,同样的规则也适用于编码)[return => bool]:

$salt = "LALALA";
argon2idHashVerify($clearvalue, $salt, $hashtoverify, "hex") ? "match" : "dont match"; // with encoding
argon2idHashVerify($clearvalue, $salt, $hashtoverify) ? "match" : "dont match"; // without encoding

最后,如果您知道 PHP,您可以根据自己的喜好修改这些函数,但目前这是我所知道的在数据库中安全存储密码的最佳方式。