PHP中常见的密码处理方式和建议总结


Posted in PHP onOctober 14, 2018

前言

在使用PHP开发Web应用的中,很多的应用都会要求用户注册,而注册的时候就需要我们对用户的信息进行处理了,最常见的莫过于就是邮箱和密码了,本文意在讨论对密码的处理:也就是对密码的加密处理。

密码安全的重要性我们就不用再去强调,随着在线攻击的增多,如果我们对密码没有进行合适的处理或做防御措施,我们的应用就会肯定会收到来自各方的威胁和攻击。

PHP中常见的密码处理方式和建议总结

所以作为开发者,我们需要对用户的密码做好预防措施。

关于密码我们应该遵守的一些原则

绝对不能知道用户的密码

  • 我们绝对不能知道用户的密码,也不能有获取用户密码的方式。
  • 知道的越少(包括我们开发者自己)越安全。

绝对不去约束用户的密码

  • 最好不要去约束密码的长度、格式等。
  • 如果要求密码符合一个特定的模式,其实对于那些不怀好意的人也提供了攻击的途径。
  • 如果必须要约束的话,建议只限制最小长度。并把常用的密码或基于字典创建的密码加入黑名单,也是一个好主意。

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

对于一个web应用来说,重置或修改密码时,我们应该在邮件里发送用于设定或修改密码的 URL 。而且这个URL中应该会包含一个唯一的令牌,这个令牌只能在设定或修改密码时使用一次。在设定或修改密码之后,我们就应该把这个令牌置为失效。

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

目前,通过大量的审查,最安全的哈希算法是 bcrypt 。

首先,我们明确两个概念,哈希、加密。哈希和加密有什么区别?

加密

加密是双向算法,加密的数据之后通过解密还可以得到。

哈希

哈希是单向算法,哈希后的数据不能再还原成原始值。

哈希算法的用途,

验证数据的完整性(要求算法速度快)

  • 用户提高密码等需要单向验证的数据的安全性(要求安全性高,甚至故意要求时间慢)
  • 一般我们在数据库中保存的应该是计算出来的密码的哈希值。这样即使我们的数据库泄露了,他们也只能看到这些无意义的密码的哈希值。

哈希的算法有很多种,

MD5

MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。

SHA1

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准 (Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。 SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。

bcrypt

bcrypt是专门为密码存储而设计的算法,基于Blowfish加密算法变形而来,由Niels Provos和David Mazières发表于1999年的USENIX。 bcrypt最大的好处是有一个参数(work factor),可用于调整计算强度,而且work factor是包括在输出的摘要中的。随着攻击者计算能力的提高,使用者可以逐步增大work factor,而且不会影响已有用户的登陆。 bcrypt经过了很多安全专家的仔细分析,使用在以安全著称的OpenBSD中,一般认为它比PBKDF2更能承受随着计算能力加强而带来的风险。bcrypt也有广泛的函数库支持,因此我们建议使用这种方式存储密码。

scrypt

scrypt不仅计算所需时间长,而且占用的内存也多,使得并行计算多个摘要异常困难,因此利用rainbow table进行暴力攻击更加困难。scrypt没有在生产环境中大规模应用,并且缺乏仔细的审察和广泛的函数库支持 。但是,scrypt在算法层面只要没有破绽,它的安全性应该高于PBKDF2和bcrypt。

目前,通过大量的审查,最安全的哈希算法是 bcrypt 。与 MD5 和 SHA1 不同, bcrypt 算法会自动加盐,来防止潜在的彩虹表攻击。 bcrypt 算法会花费大量的时间反复处理数据,来生成安全的哈希值。在这个过程中,处理数据的次数叫工作因子(work factor)。工作因子的值越高,破解密码哈希值的时间会成指数倍增长。

bcrypt 算法永不过时,如果计算机的运算速度变快了,我们只需要提高工作因子即可。

顺带说一下,任何情况下尽可能的不要使用 md5 算法,至少也要使用 SHA 系列的哈希算法。因为md5算法以目前计算机的计算能力来说显得比较简单,而 md5 的性能优势现在也已经完全可以忽略不计了。

密码哈希API

上面我们说到 bcrypt 算法最安全,最适合对我们的密码进行哈希。 PHP 在 PHP5.5.0+ 的版本中提供了原生的密码哈希API供我们使用,这个密码哈希API默认使用的就是 bcrypt 哈希算法,从而大大简化了我们计算密码哈希值和验证密码的操作。

PHP原生密码哈希API

密码哈希函数:

  • password_get_info
    返回指定的哈希值的相关信息
  • password_hash
    创建密码的哈希(hash)
  • password_needs_rehash
    检查给定的哈希是否与给定的选项匹配
  • password_verify
    验证密码是否和哈希匹配

password_get_info

说明

array password_get_info ( string $hash )

参数

hash, 一个由 password_hash() 创建的散列值。

示例,

<?php
var_dump(password_get_info($hash));
// Example
array(3) {
 ["algo"]=>
 int(1)
 ["algoName"]=>
 string(6) "bcrypt"
 ["options"]=>
 array(1) {
 ["cost"]=>
 int(10)
 }
}
?>

password_hash

说明

string password_hash ( string $password , integer $algo [, array $options ] )

password_hash() 使用足够强度的单向散列算法创建密码的哈希(hash)。 password_hash() 兼容 crypt()。 所以, crypt() 创建的密码哈希也可用于 password_hash()。

当前支持的算法:

  • PASSWORD_DEFAULT
    使用 bcrypt 算法 (PHP 5.5.0 默认)。 注意,该常量会随着 PHP 加入更新更高强度的算法而改变。 所以,使用此常量生成结果的长度将在未来有变化。 因此,数据库里储存结果的列可超过60个字符(最好是255个字符)。
  • PASSWORD_BCRYPT
    使用 CRYPT_BLOWFISH 算法创建哈希。 这会产生兼容使用 “$2y$“ 的 crypt()。 结果将会是 60 个字符的字符串, 或者在失败时返回 FALSE。

支持的选项:

  • salt - 手动提供哈希密码的盐值(salt)。这将避免自动生成盐值(salt)。
    省略此值后,password_hash() 会为每个密码哈希自动生成随机的盐值。这种操作是有意的模式。
    Warning 盐值(salt)选项从 PHP 7.0.0 开始被废弃(deprecated)了。 现在最好选择简单的使用默认产生的盐值。
  • cost - 代表算法使用的 cost。crypt() 页面上有 cost 值的例子。
    省略时,默认值是 10。 这个 cost 是个不错的底线,但也许可以根据自己硬件的情况,加大这个值。

参数

  • password, 用户的密码。
    使用 PASSWORD_BCRYPT 做算法,将使 password 参数最长为72个字符,超过会被截断。
  • algo, 一个用来在散列密码时指示算法的密码算法常量。
  • options, 一个包含有选项的关联数组。目前支持两个选项:
    salt,在散列密码时加的盐(干扰字符串),
    cost,用来指明算法递归的层数。这两个值的例子可在 crypt() 页面找到。
    省略后,将使用随机盐值与默认 cost。

示例

示例1,使用默认算法哈希密码

<?php
/**
 * 我们想要使用默认算法哈希密码
 * 当前是 BCRYPT,并会产生 60 个字符的结果。
 *
 * 请注意,随时间推移,默认算法可能会有变化,
 * 所以需要储存的空间能够超过 60 字(255字不错)
 */
echo password_hash("rasmuslerdorf", PASSWORD_DEFAULT)."\n";
?>
// 输出类似于:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

示例2,手动设置 cost

<?php
/**
 * 在这个案例里,我们为 BCRYPT 增加 cost 到 12。
 * 注意,我们已经切换到了,将始终产生 60 个字符。
 */
$options = [
 'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>
// 输出类似于:
// $2y$12$QjSH496pcT5CEbzjD/vtVeH03tfHKFy36d4J0Ltp3lRtee9HDxY3K

示例3,如何选择一个适合当前服务器的 cost

<?php
/**
 * 这个例子对服务器做了基准测试(benchmark),检测服务器能承受多高的 cost
 * 在不明显拖慢服务器的情况下可以设置最高的值
 * 8-10 是个不错的底线,在服务器够快的情况下,越高越好。
 * 以下代码目标为 ≤ 50 毫秒(milliseconds),
 * 适合系统处理交互登录。
 */
$timeTarget = 0.05; // 50 毫秒(milliseconds)

$cost = 8;
do {
 $cost++;
 $start = microtime(true);
 password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
 $end = microtime(true);
} while (($end - $start) < $timeTarget);

echo "Appropriate Cost Found: " . $cost . "\n";
?>

输出类似于:

Appropriate Cost Found: 10

password_needs_rehash

说明

boolean password_needs_rehash ( string $hash , integer $algo [, array $options ] )

参数

  • hash, 一个由 password_hash() 创建的散列值。
  • algo, 一个用来在散列密码时指示算法的密码算法常量。
  • options, 一个包含有选项的关联数组。目前支持两个选项:
    salt,在散列密码时加的盐(干扰字符串),
    cost,用来指明算法递归的层数。这两个值的例子可在 crypt() 页面找到。

示例,

<?php
$password = 'rasmuslerdorf';
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS';

// cost 参数可随硬件的提升也不断提升
$options = array('cost' => 11);

// 使用纯文本密码 验证存储的散列
if (password_verify($password, $hash)) {
 // 检查是否有更新的散列算法可用或 cost 是否已经改变
 if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
  // 如果是,请创建一个新的哈希值,并替换旧的哈希值
  $newHash = password_hash($password, PASSWORD_DEFAULT, $options);
 }

 // 用户登录验证完成
 // ...
}
?>

password_verify

说明

boolean password_verify ( string $password , string $hash )

注意 password_hash() 返回的哈希包含了算法、 cost 和盐值。 因此,所有需要的信息都包含内。使得验证函数不需要储存额外盐值等信息即可验证哈希。

参数

  • password, 用户的密码。
  • hash, 一个由 password_hash() 创建的散列值。

示例,

<?php
// 想知道以下字符从哪里来,可参见 password_hash() 的例子
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
 echo 'Password is valid!';
} else {
 echo 'Invalid password.';
}
?>

以上例程会输出:

Password is valid!

PHP5.50 之前的密码哈希 API

安东尼·费拉拉(PHP原生密码哈希 API的开发者)为PHP5.5.0 以下的版本也提供了 ircmaxell/password-compat组件(https://packagist.org/packages/ircmaxell/password-compat)。

这个组件也实现了PHP密码哈希API中的所有函数,

  • password_get_info
  • password_hash
  • password_needs_rehash
  • password_verify

我们可以直接使用 Composer 把这个组件添加到我们的应用中就行了。例如,

composer require ircmaxell/password-compat

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
PHP的FTP学习(三)
Oct 09 PHP
php session和cookie使用说明
Apr 07 PHP
php 图片加水印与上传图片加水印php类
May 12 PHP
php登陆页的密码处理方式分享
Oct 14 PHP
ThinkPHP字符串函数及常用函数汇总
Jul 18 PHP
destoon调用discuz论坛中带图片帖子的实现方法
Aug 21 PHP
浅析php适配器模式(Adapter)
Nov 25 PHP
php使用gzip压缩传输js和css文件的方法
Jul 29 PHP
PHP的运行机制与原理(底层)
Nov 16 PHP
PHP处理数组和XML之间的互相转换
Jun 02 PHP
php使用GD2绘制几何图形示例
Feb 15 PHP
微信公众号实现扫码获取微信用户信息(网页授权)
Apr 09 PHP
PHP如何根据文件头检测文件类型实例代码
Oct 14 #PHP
PHP5.0 TIDY_PARSE_FILE缓冲区溢出漏洞的解决方案
Oct 14 #PHP
PHP按一定比例压缩图片的方法
Oct 12 #PHP
PHP实现图片压缩
Sep 09 #PHP
PHP获取数据库表中的数据插入新的表再原删除数据方法
Oct 12 #PHP
thinkphp5.0整合phpsocketio完整攻略(绕坑)
Oct 12 #PHP
PHP解析url并得到url参数方法总结
Oct 11 #PHP
You might like
php flv视频时间获取函数
2010/06/29 PHP
php遍历所有文件及文件夹的方法深入解析
2013/06/08 PHP
php判断表是否存在的方法
2015/06/18 PHP
PHP邮箱验证示例教程
2016/06/01 PHP
PHP编程中的Session阻塞问题与解决方法分析
2017/08/07 PHP
Yii框架布局文件的动态切换操作示例
2019/11/11 PHP
Ext JS添加子组件的误区探讨
2013/06/28 Javascript
javascript中new关键字详解
2015/12/14 Javascript
Boostrap入门准备之border box
2016/05/09 Javascript
js改变css样式的三种方法推荐
2016/06/28 Javascript
概述如何实现一个简单的浏览器端js模块加载器
2016/12/07 Javascript
使用 Node.js 模拟滑动拼图验证码操作的示例代码
2017/11/02 Javascript
使用vue2.6实现抖音【时间轮盘】屏保效果附源码
2019/04/24 Javascript
vue使用recorder.js实现录音功能
2019/11/22 Javascript
详解React 条件渲染
2020/07/08 Javascript
一篇文章看懂JavaScript中的回调
2021/01/05 Javascript
python基础教程之对象和类的实际运用
2014/08/29 Python
python使用KNN算法手写体识别
2018/02/01 Python
Python实现的微信好友数据分析功能示例
2018/06/21 Python
django 自定义过滤器(filter)处理较为复杂的变量方法
2019/08/12 Python
在pycharm中使用matplotlib.pyplot 绘图时报错的解决
2020/06/01 Python
Python tkinter界面实现历史天气查询的示例代码
2020/08/23 Python
西班牙英格列斯百货英国官网:El Corte Inglés英国
2017/10/30 全球购物
同步和异步有何异同,在什么情况下分别使用他们?举例说明
2014/02/27 面试题
linux面试题参考答案(7)
2012/10/29 面试题
群众路线教育实践活动方案
2014/02/02 职场文书
行政求职信
2014/07/04 职场文书
学习党的群众路线教育实践活动剖析材料
2014/10/13 职场文书
少先队辅导员事迹材料
2014/12/24 职场文书
三年级学生期末评语
2014/12/26 职场文书
关于颐和园的导游词
2015/01/30 职场文书
2015年仓库管理员工作总结
2015/04/21 职场文书
2015入党自传书范文
2015/06/26 职场文书
JS如何实现基于websocket的多端桥接平台
2021/05/14 Javascript
pandas求平均数和中位数的方法实例
2021/08/04 Python
【海涛教你打DOTA】虚空假面第一视角骨弓3房29杀
2022/04/01 DOTA