PHP Manual
/
安全问题

加密字符串和密码

11. 09. 2019

Obsah článku

散列过程(与加密相反)从输入中产生一个输出,从该输出中不能再得出原始字符串。

因此,它非常适用于保护敏感字符串、密码和校验。

散列函数的另一个很好的特点是,它们总是产生相同长度的输出,输入的一个小变化总是完全改变整个输出。

哈希函数

在 PHP 中有许多哈希函数,重要的有:。

  • Bcrypt: password_hash() - 最安全的密码散列,计算速度很慢,使用内部盐和迭代散列。
  • md5() - 非常快的函数,适合于文件散列。输出始终为32个字符。
  • sha1() - 用于文件散列的快速散列函数,由Git内部使用,用于提交散列。输出总是40个字符。

洗练

$password = '秘密密码';
echo password_hash($password); // Bcrypt
echo md5($password);
echo sha1($password);

警告: md5()sha1()都不适合用于密码散列,因为在计算上很容易发现原始密码,或者至少可以预先计算出密码。使用 "bcrypt "要好得多,它是为密码散列而开发的。

网站md5cracker.com包含一个校验和(哈希值)的数据库,尝试搜索哈希值:79c2b46ce2594ecbcb5b73e928345492,你可以看到,所以纯粹的md5()对于普通单词和密码来说并不是那么安全。

唯一正确的解决方案:Bcrypt + salt

如何不在目标平面上捣乱的演讲中,David Grudl谈到了正确哈希和存储密码的方法。

唯一正确的解决方案是:Bcrypt + salt

具体来说就是。

$password = '散列';
// 生成一个安全的哈希值
echo password_hash($password, PASSWORD_BCRYPT);
//可选择更高的复杂性(默认为10)。
echo password_hash($password, PASSWORD_BCRYPT, ['费用' => 12]);

Bcryp的优势主要体现在其速度和自动加盐上。

事实上,它需要**长的时间来生成,例如100毫秒,这使得攻击者测试许多密码的成本非常高。

此外,输出的哈希值会自动用**随机盐处理,这意味着当同一密码被反复哈希时,输出的总是不同的哈希值。因此,攻击者将无法使用预先计算的哈希表。

因此,我们将无法通过重复散列来验证密码的正确性,而是需要调用一个专门的函数。

if (password_verify($password, $hash)) {
// 密码是正确的
} else {
// 密码不正确
}

密码加盐

为了增加哈希破解的难度,在原始输入中插入一些额外的字符串是一个好主意。最好是一个随机的。这个过程被称为密码加盐

安全性的基础是,攻击者将无法使用预先计算好的密码和哈希值表,因为他不知道盐,必须单独破解密码。

比如说。

$password = '秘密护照';
$salt = 'fghjgtzjhg';
$hash = md5($password . $salt);
echo $password; //打印出原始密码
echo $hash; //打印包括盐的密码哈希值

复合哈希函数

你可能会想,反复执行哈希函数是个好主意,从而提高破解的复杂性,因为原始密码需要反复哈希。

比如说。

$password = '密码';
for ($i = 0; $i <= 1000; $i++) {
$password = md5($password);
}
echo $password; //通过md5()进行1000倍散列

矛盾的是,突破的难度降低或几乎保持不变。

原因是md5()函数的速度非常快,在普通计算机上每秒可以计算超过一百万个哈希值,所以一个一个地尝试密码并不会降低速度。

第二个原因更多的是一种理论,即有可能碰到所谓的碰撞。如果我们反复散列一个密码,随着时间的推移,可能会发生我们击中一个攻击者已经知道的散列,这将使他能够使用数据库散列密码。

因此,最好使用一个缓慢的安全散列函数,并且只进行一次散列,同时仍然对最终输出进行加盐处理。

对两个哈希值/字符串进行极为安全的比较

你知道吗,在密码验证中,===运算符不是最安全的哈希比较选择?

当比较字符串时,两个字符串被逐个字符遍历,直到到达终点(成功,它们是相同的)或存在差异(字符串是不同的)。

而这一点可以在攻击中被利用。如果你测量的时间足够准确,你可以估计还需要添加多少个字符才能获得完全匹配并达到终点,或者你可以在比较字符串时估计字符串已经走了多远。

解决办法是在比较字符串的地方使用hash_equals()函数,如果攻击者能找出比较失败的位置,那就很重要了。

那么这个函数是如何做到这一点的呢?它确保任何2个字符串的比较总是需要相同的时间,所以你无法通过测量时间来判断哪里出现了差异。我发现有些类型的攻击真的非常不可能,而且很难实施。这就是其中之一。

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
5.
Status:
All systems normal.
2024