前言
PHP的
MD5
,SHA1
以及SHA256
这样的散列算法是面向快速、高效 进行散列处理而设计的。随着技术进步和计算机硬件的提升, 破解者可以使用“暴力”方式来寻找散列码 所对应的原始数据。
因为现代化计算机可以快速的“反转”上述散列算法的散列值, 所以很多安全专家都强烈建议 不要在密码散列中使用这些散列算法**。
如何对密码进行散列处理?
当进行密码散列处理的时候,有两个必须考虑的因素: 计算量以及“盐”。 散列算法的计算量越大, 暴力破解所需的时间就越长。
PHP 5.5
提供了 一个原生密码散列 API, 它提供一种安全的方式来完成密码 散列和 验证。 PHP 5.3.7
及后续版本中都提供了一个 » 纯 PHP 的兼容库。
password_hash()
是 crypt() 的一个简单封装,采用 Blowfish算法 ,并且完全与现有的密码哈希兼容。推荐使用 password_hash()
。
- 时序攻击
如果使用 crypt()
函数来进行密码验证, 那么你需要选择一种耗时恒定的字符串比较算法来避免时序攻击。 (译注:就是说,字符串比较所消耗的时间恒定, 不随输入数据的多少变化而变化) PHP 中的 ==
和 ===
操作符 和 strcmp()
函数都不是耗时恒定的字符串比较,但是 password_verify()
可以帮你完成这项工作。
- 加盐
“加盐”是指在进行散列处理的过程中 加入的一些数据,用来避免从已计算的散列值表 (被称作“彩虹表”)中 对比输出数据从而获取明文密码的风险。
如果不提供“盐”,password_hash()
函数会随机生成“盐”。 非常简单,行之有效。
- 验证
PHP当使用 password_hash()
或者 crypt()
函数时, “盐”会被作为生成的散列值的一部分返回。 你可以直接把完整的返回值存储到数据库中, 因为这个返回值中已经包含了足够的信息, 可以直接用在 password_verify()
或 crypt()
函数来进行密码验证。
下图展示了 crypt()
或 password_hash()
函数返回值的结构。 如你所见,算法的信息以及“盐”都已经包含在返回值中, 在后续的密码验证中将会用到这些信息。
原生密码散列 API
密码散列算法函数
password_hash
— 创建密码的哈希(hash)password_needs_rehash
— Checks if the given hash matches the given optionspassword_verify
— 验证密码是否和哈希匹配
password_hash()
使用了一个强的哈希算法,来产生足够强的盐值,并且会自动进行合适的轮次。
Note: 由于crypt()
使用的是单向算法,因此不存在decrypt
函数。
password_hash
(PHP 5 >= 5.5.0, PHP 7)
password_hash — 创建密码的哈希(hash)string password_hash ( string $password , integer $algo [, array $options ] )
$password
:
用户的密码。
注意: 使用PASSWORD_BCRYPT
做算法,将使password
参数最长为72个字符,超过会被截断。
$algo
:
支持的算法 :
PASSWORD_DEFAULT
- 使用bcrypt 算法
(PHP 5.5.0
默认)。
注意,该常量会随着 PHP 加入更新更高强度的算法而改变。所以,使用此常量生成结果的长度将在未来有变化。 因此,数据库里储存结果的列可超过60
个字符(最好是255
个字符)。PASSWORD_BCRYPT
- 使用CRYPT_BLOWFISH
算法创建哈希。
这会产生兼容使用"$2y$"
的crypt()
。 结果将会是60
个字符的字符串, 或者在失败时返回FALSE
。默认算法。
$options:
支持的选项:
salt
- 在散列密码时加的盐(干扰字符串)。
省略此值后,password_hash()
会为每个密码哈希自动生成随机的盐值。强烈建议不要自己为这个函数生成盐值(salt
)。只要不设置,它会自动创建安全的盐值。
盐值(salt
)选项从 PHP7.0.0
开始被废弃(deprecated)了。 现在最好选择简单的使用默认产生的盐值。cost
- 用来指明算法递归的层数。默认值是 10。
password_verify
(PHP 5 >= 5.5.0, PHP 7)
password_verify — 验证密码是否和哈希匹配boolean password_verify ( string $password , string $hash )
$password:
用户的密码。使用hash算法加密前的原始密码。
$hash
一个由
password_hash()
创建的散列值。
其他
password_needs_rehash
加强安全性,如何在验证密码后更新hash密码?。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$password = 'rasmuslerdorf';
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS';
// The cost parameter can change over time as hardware improves
$options = array('cost' => 11);
// Verify stored hash against plain-text password
if (password_verify($password, $hash)) {
// Check if a newer hashing algorithm is available
// or the cost has changed
if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
// If so, create a new hash, and replace the old one
$newHash = password_hash($password, PASSWORD_DEFAULT, $options);
// todo: store newHash to database
}
// Log user in
}
纯 PHP 的兼容库
1 | /** |
合适的 cost 值
在交互的系统上,推荐在自己的服务器上测试此函数,调整 cost 参数直至函数时间开销小于 100 毫秒(milliseconds)
1 |
|
手动设置盐值
1 | /** |