Mordern PHP 密码(使用哪种哈希密码最安全?)

    xiaoxiao2022-07-07  205

    密码

    随着在线攻击的增多、,密码安全越来越重要。因为重要的零售商被黑,你注销过多少张信用卡? 很多零售商因为没有使用最好的安全措施而沦为恶意黑客的攻击对象,PHP应用一样,如果没有合适的预防措施,也会受到攻击。

    其中一个重要的预防措施是保护密码。作为开发者,我们要担起安全管理、计算哈希和存储用户密码的责任。不管应用是简单的游戏还是绝密商业文件的仓库,都要做到这一 点。用户把他们的信息托付给你,是相信你能使用最好的安全措施保护他们的信息。我见过很多不知道如何安全管理密码的PHP开发者。这怨不得他们,毕竟密码很难安全管理。幸好,PHP内置了一些工具,让保护密码变得十分容易。本节说明如何根据现代的安全措施使用这些工具。

    绝对不能知道用户的密码

    我们绝对不能知道用户的密码,也不能有获取用户密码的方式。如果应用的数据库被黑,你肯定不希望数据库中有纯文本或能解密的密码。一旦密码泄露,用户对你的信任会严重降低,而且你或你的公司要背负大量法律责任。你知道的越少越安全。

    绝对不要约束用户的密码

    如果某个网站要求账户的密码要符合特定的格式,我会心灰意冷。如果限制账户密码的长度不能超过N个字符,我会更生气。为什么?我知道要求密码使用特定格式有时是为了兼容以前的应用或者数据库,可这不是缺乏安全措施的借口。

    绝对不能约束用户的密码如果要求密码符合特定的模式,其实是为不怀好意的人提供 了攻击应用的途径。如果必须约束用户的密码,我建议只限制最小长度。把常用的密码或基于字典创建的密码加入黑名单也是好主意

    绝对不能通过电子邮件发送用户的密码

    绝对不能通过电子邮件发送密码。如果你通过电子邮件给我发送密码,我会知道三件事:你知道我的密码;你使用纯文本或能解密的格式存储了我的密码;你没有对通过互联网发送纯文本的密码感到不安。

    我们应该在电子邮件中发送用于设定或修改密码的URL.Web应用通常会生成一个唯一的令牌,这个令牌只在设定成修改密码时使用一次。例如,我忘了自己在你应用中的账户密码,我单击登录表单中的“忘记密码”链接、然后转到一个表单,我在这个表单中填写我的电子邮件地址,请求重设密码,你的应用生成一个唯一的令牌,并把这个令牌关联到我的电子邮件地址对应的账户上,然后发送一时电子邮件到账户的电子邮件地止。这封电子邮件中有一个URL,其中某个URL片段或查字符串的值是这个唯一的令牌。我访问这个URL、你的应用验证令牌,如果令牌有效、就让我为账户重设一个密吗,我重设密码之后,你的应用把这个令牌设为失效。

    使用 bcrypti计算用户密码的哈希值

    我们应该计算用户密码的哈希值,而不能加密用户的密码。加密和哈希不是一回事。加密是双向算法,加密的数据以后可以解密。而哈希是单向算法,哈希后的数据不能再还原成原始值,而且相同的数据得到的哈希值始终相同。

    在数据库中存储用户的密码时,要先计算密码的哈希值,然后在数据库中存储密码的哈希值。如果黑客攻入了数据库、他们只能看到无意义的密码哈希值,需要花费大量时间和NSA资源才能破解。

    哈希算法有很多种(例如MD5、SHAI、 bcrypt?和 crypt)。有些算法的速度很快,用于 验证数据完整性;有些算法的速度则很慢,旨在提高安全性。生成密码和存储密码时需 要使用速度慢、安全性高的算法。

    目前,经同行車査,最安全的哈希算法是 bcrypt。与MDS和SHA1不同, bcrp是故意设 计得很慢。 bcrypt算法会自动加盐,防止潜在的彩虹表攻击。 bcrypt算法会花费大量时间 (以秒计)反复处理数据,生成特别安全的哈希值。在这个过程中,处理数据的次数叫 工作因子( work factor)。工作因子的值越高,不怀好意的人破解密码哈希值所需的时 间会成指数倍增长。 bcrypt算法永不过时,如果计算机的运算速度变快了,我们只需提 高工作因子的值。

    crypto算法得到了同行的大量审查,很多比我聪明多的人都审查过 bcrypt算法,试图找 出潜在的漏洞,但目前为止一个漏洞都没找到。我们一定要使用经同行市査过的哈希算 法,而不要自己创建。群众的眼睛是雪亮的,而你可能并不是加密专家(如果你是的 话,给布鲁斯・施奈尔译注2带个好)。

    译注2:布音斯・范奈尔是美国若名的密码学学者、信息安全专家与作家,有很多著作、例如 用密码学》

    密码哈希API

    读过前文可以得知,处理用户的密码时要考虑很多事情。安东尼・费拉拉(hrp:blog. ircmuarel/ come)很懂我们的心思,他开发了PHP5.0中原生的密码哈希API(php. net/manual/book.password.php)・PHP原生的密码哈希AP提供了很多易干使用的函 数、大大简化了计算密码哈番值和验证密码的操作。而且,这个密码哈希API使用 bcrypt哈希算法

    注意:安东尼・费拉拉( Twitter号为 @ircmaxel)是谷歌的技术推广员,是PHP性能和安全方面 的权威,安东尼还是PHP密码哈希API的作者。我建议你在 Twitter上关注安东尼、也建议你阅读他的博客( Ar(.//blog ircmarell com),我要特别谢安东尼,他对PHP的贡敏让我们能更轻易地使用最好的安全措施,从而提升PHP应用的安全性。

    开发Web应用时,有两个地方会用到密码哈希API:注册用户和登录用户。下面我们探 讨PHP密码哈希API是如何简化这两个操作的。

    注册用户

    没有用户、Web应用无法生存下去、而用户需要一种注册账户的方式。假设我们虚构的 应用中有个PHP文件、其URL是/register.php。这个PHP文件用来接收URL编码的HTTP POST请求,从中获取电子邮件地址和密码。如果电子邮件地址有效,而且密码至少有 八个字符,我们就创建一个用户账户。下面是一个HTTP POSTI请求示例:

    POST /login.php HTTP/1.1 Content-length: 43 Content-type: application/x-www-form-urlencoded email = johne@exple.com&pass=sekritshhh!

    示例5-7是接收这个 HTTP POSTI请求的 register. php文件。

    示例5-7:注册用户的脚本

    <?php try{ //验证电子邮件地址 $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); if(!email){ throw new Exception("Invalid email"); } //验证密码 $password = filter_input(INPUT_POST, 'password'); if(!password || mb_strlen($password) < 8){ throw new Exception("Password must contain 8+ characters"); } //创建密码的哈希值 $passwordHash = password_hash( $password, PASSWORD_DEFAULT, ['cost' => 12] ); if($passwordHash === false){ throw new Exception('password hash failed'); } //创建用户账户(注意,这是虚构的代码) $user =new User(); $user->email = $email; $user->password_hash = $passwordHash; $user->save(); //重定向到登录页面 header('http:/1.1 302 Redirect'); header('Location: /login.php'); }catch (Exception $e){ // 报告错误 header('HTTP/1.1 400 Bad request'); echo $e->getMessage(); }

    在示例5-7中

    第4-7行验证用户的电子邮件地址。如果电子邮件地址无效,抛出异常

    第10-13行验证从HTP请求主体中获取的纯文本密码。如果纯文本密码少于八个字符,抛出异常。

    第16-23行使用PHP密码哈希API中的 password_hash()函数创建密码的哈希值。 这个函数的第一个参数是纯文本密码;第二个参数是 PASSWORD_DE FAULT常量,告 诉PHP使用 bcrypt哈希算法;第三个参数是一个数组,指定哈希选项。这个数组中 的cost键用于设定 bcrypt 的工作因子。工作因此的默认值是10,不过你应该根据硬 件的具体计算能力提高这个值。计算哈希值一般需要0.1-0-5。如果计算密码的哈 希值失败,抛出异常。

    第26-29行展示了保存虚构的用户账户。这几行是虚构的代码,你应该针对应用的 具体需求修改这些代码。我想强调的是,要把密码的哈希值存储到数据库中,而不 能直接存储从HTTP请求主体中获取的纯文本密码。我们还存储了用于识别和登录 用户账户的电子邮件地址。

    建议:密码的哈希值要存储在 VARCHAR(255)类型的数据库列中。这样便于以后存储比现在的 bcrypt 算法得到的哈希值更长的密码。

    登录用户

    我们建构的应用还有一个URL为/ login.php的PHP文件。这个文件用于接收包含电子邮件 地址和密码的HTTP POST请求,识别、认证,并登录用户。下面是一个HTTP POST请求 示例:

    POST /login.php HTTP/1.1 Content-length: 43 Content-type: application/x-www-form-urlencoded email = john@example.com&password=sekritshhh!

    login. php文件会找到电子邮件地址对应的用户账户,验证提交的密码与用户账户的密吗 哈希值匹配后,登录这个用户账户。示例5-8是 login.php文件的内容。

    示例5-8:登录用户的脚本

    <?php session_start(); try{ //从请求主体中获取电子邮件地址 $email = filter_input(INPUT_POST, 'email'); //从请求主体中获取密码 $password =filter_input(INPUT_POST, 'password'); //使用电子邮件地址査找账户(注意,这是虚构的代码) email), $user=User::findByEmail($email); //验证密码和账户的密码哈希值是否匹配 if (password_verify($password, $user->password_hash) === false){ throw new Exception("Invalid password") } //如果需要,重新计算密码的哈希值(参见下面的说明) $currentHashAlgorithm = PASSWORD_DEFAULT; $scurrentHashOptions =array('cost=> 15'); $passwordNeedsRehash = password_needs_rehash( $user->password_hash, $currentHashAlgorithm, $scurrentHashOptions ); if (passwordNeedsRehash == true) { //保存新计算得到的密码哈希值(注意,这是虚构的代码) $user->password_hash = password_hash( $password, $currentHashAlgorithm, $scurrentHashOptions ); $user->save(); } //把登录状态保存到会话中 $_SESSION['user_logged_in'] = 'yes'; $_SESSION['user_email'] = $email; //重定向到个人资料页面 header('HTTP/1.1 302 Redirect'); header("Location: /user-profile.php"); } catch(Exception $e){ header("http:/1.1 401 Unauthorized"); echo $e->getmessage(); }

    在示例5-8中

    第5行和第8行分别从HTTP请求主体中获取电子邮件地址和密码

    第11行查找HTTP请求主体中提交的电子邮件地址对应的用户记录。我使用的是虚 构的代码,你应该根据应用的具体需求修改这些代码

    第14-16行使用password_verify()函数比较HTTP请求主体中提交的纯文本密码 和用户记录中存储的密码哈希值。如果验证失败,抛出异常。

    第19-34行调用 Password_needs_rehash()函数,确认用户记录中的密码哈希值是 否符合最新的密码算法选项。如果用户记录中的密码哈希值过时了,我们就使用最 新的算法选项重新计算哈希值,然后使用新哈希值更新用户记录。

    验证密码

    我们调用 password_verify()函数对比从HTTP请求主体中获取的纯文本密码和用户记录 中存储的密码哈希值。这个函数有两个参数。第一个参数是纯文本密码,第二个参数是 用户记录中现有的密码哈希值。如果 password_verify()函数返回true,说明纯文本密码 是正确的,那就登录用户否则,说明纯文本密码错误,要中止登录过程。

    重新计算密码的哈希值

    在示例5-8中第17行代码之后、认证已经通过、可以登录用户了・可是,在登录前、一定 要松査用户记录中现有的密码哈希值是否过期。如果过期了,要重新计算密码哈希值。

    为什么要重新计算密码哈希值呢? 假设我们的应用创建于两年前,那时使用的 bcrypt工 作因子是10。而现在我们使用的工作因子是20,因为黑客更聪明了、而且计算机速度更 快了。可是,有些用户账户的密码哈希值仍然是工作因子为10时生成的、因此,登录请 求通过认证之后、要使用 password_needs_rehash()函数检查用户记录中现有的密码哈 希值是否需要更新。这个函数能确保指定的密码哈希值是使用最新的哈希算法选项创建 的 的.如果确实需要重新计算密码的哈希值,我们要使用当前的算法选项计算HTTP请求 主体中纯文本密码的哈希值、然后使用新哈希值更新用户记录。

    建议:在登录用户的脚本中使 password_needs_rehash()函数最简单,因为能同时获取旧密码哈 希值和纯文本密码。

    PHP5.5.0之前的密码哈希API

    如果无法使用PHP5.5.0或以上版本,也不用害怕,可以使用安东尼费拉拉开发的 ircmaxell/password-compat组件(https://packagist.org/packages/ircmaxell/password- compar)。这个组件实现了PHP密码哈希API中的所有函数:

    password hash()password get info()pas sword needs rehash()password verify()

    费拉拉开发的 ircmaxell/password- compat组件可以直接替代现代的PHP密码哈希API, 使用 Composer把这个组件添加到你的应用中就行了。

    文章来源: Modern PHP 第五章 密码

    最新回复(0)