md5安全吗?有多么地不安全?如何才能安全地存储密码?...

md5安全吗?

经过各种安全事件后,很多系统在存放密码的时候不会直接存放明文密码了,大都改成了存放了 md5 加密(hash)后的密码,可是这样真的安全吗?

这儿有个脚本来测试下MD5的速度, 测试结果:

[root@f4d5945f1d7c tools]# php speed-of-md5.php
Array
(
[rounds] => 100
[times of a round] => 1000000
[avg] => 0.23415904045105
[max] => 0.28906106948853
[min] => 0.21188998222351
)

有没有发现一个问题:MD5速度太快了,导致很容易进行暴力破解.

简单计算一下:

> Math.pow(10, 6) / 1000000 * 0.234
0.234
> Math.pow(36, 6) / 1000000 * 0.234 / 60
8.489451110400001
> Math.pow(62, 6) / 1000000 * 0.234 / 60 / 60
3.69201531296
  1. 使用6位纯数字密码,破解只要0.234秒!
  2. 使用6位数字+小写字母密码,破解只要8.49分钟!
  3. 使用6位数字+大小写混合字母密码,破解只要3.69个小时!

当然,使用长一点的密码会显著提高破解难度:

> Math.pow(10, 8) / 1000000 * 0.234
23.400000000000002
> Math.pow(36, 8) / 1000000 * 0.234 / 60 / 60 / 24
7.640505999359999
> Math.pow(62, 8) / 1000000 * 0.234 / 60 / 60 / 24 / 365
1.6201035231755982
  1. 使用8位纯数字密码,破解要23.4秒!
  2. 使用8位数字+小写字母密码,破解要7.64小时!
  3. 使用8位数字+大小写混合字母密码,破解要1.62年!

但是,别忘了,这个速度只是用PHP这个解释型语言在笔者的弱鸡个人电脑(i5-4460 CPU 3.20GHz)上跑出来的,还只是利用了一个线程一个CPU核心。若是放到最新的 Xeon E7 v4系列CPU的服务器上跑,充分利用其48个线程,并使用C语言来重写下测试代码,很容易就能提升个几百上千倍速度。那么即使用8位数字+大小写混合字母密码,破解也只要14小时!

更何况,很多人的密码都是采用比较有规律的字母或数字,更能降低暴力破解的难度... 如果没有加盐或加固定的盐,那么彩虹表破解就更easy了...

那么如何提升密码存储的安全性呢?bcrypt!

提升安全性就是提升密码的破解难度,至少让暴力破解难度提升到攻击者无法负担的地步。(当然用户密码的长度当然也很重要,建议至少8位,越长越安全)

这里不得不插播一句:PHP果然是世界上最好的语言 -- 标准库里面已经给出了解决方案。

PHP 5.5 的版本中加入了 password_xxx 系列函数, 而对之前的版本,也有兼容库可以用:password_compat.
在这个名叫“密码散列算法”的核心扩展中提供了一系列简洁明了的对密码存储封装的函数。简单介绍下:

  1. password_hash 是对密码进行加密(hash),目前默认用(也只能用)bcrypt算法,相当于一个加强版的md5函数
  2. password_verify 是一个验证密码的函数,内部采用的安全的字符串比较算法,可以预防基于时间的攻击, 相当于 $hashedPassword === md5($inputPassword)
  3. password_needs_rehash 是判断是否需要升级的一个函数,这个函数厉害了,下面再来详细讲

password_hash 需要传入一个算法,现在默认和可以使用的都只有bcrypt算法,这个算法是怎么样的一个算法呢?为什么PHP标准库里面会选择bcrypt呢?

bcrypt是基于 Blowfish 算法的一种专门用于密码哈希的算法,由 Niels Provos 和 David Mazieres 设计的。这个算法的特别之处在于,别的算法都是追求快,这个算法中有一个至关重要的参数:cost. 正如其名,这个值越大,耗费的时间越长,而且是指数级增长 -- 其加密流程中有一部分是这样的:

EksBlowfishSetup(cost, salt, key)
state <- InitState()
state <- ExpandKey(state, salt, key)
repeat (2^cost) // "^"表示指数关系
state <- ExpandKey(state, 0, key)
state <- ExpandKey(state, 0, salt)
return state

比如下面是笔者的一次测试结果(个人弱机PC, i5-4460 CPU 3.20GHz) :

      cost       time
8 0.021307
9 0.037150
10 0.079283
11 0.175612
12 0.317375
13 0.663080
14 1.330451
15 2.245152
16 4.291169
17 8.318790
18 16.472902
19 35.146999

附:测试代码

这个速度与md5相比简直是蜗牛与猎豹的差别 -- 即使按照cost=8, 一个8位的大小写字母+数字的密码也要14万年才能暴力破解掉,更何况一般服务器都会至少设置为10或更大的值(那就需要54万年或更久了)。

显然,cost不是越大越好,越大的话会越占用服务器的CPU,反而容易引起DOS攻击。建议根据服务器的配置和业务的需求设置为10~12即可。最好同时对同一IP同一用户的登录尝试次数做限制,预防DOS攻击。

一个安全地存储密码的方案

总上所述,一个安全地存储密码的方案应该是这样子的:(直接放代码吧)

class User extends BaseModel
{
const PASSWORD_COST = 11; // 这里配置bcrypt算法的代价,根据需要来随时升级
const PASSWORD_ALGO = PASSWORD_BCRYPT; // 默认使用(现在也只能用)bcrypt /**
* 验证密码是否正确
*
* @param string $plainPassword 用户密码的明文
* @param bool $autoRehash 是否自动重新计算下密码的hash值(如果有必要的话)
* @return bool
*/
public function verifyPassword($plainPassword, $autoRehash = true)
{
if (password_verify($plainPassword, $this->password)) {
if ($autoRehash && password_needs_rehash($this->password, self::PASSWORD_ALGO, ['cost' => self::PASSWORD_COST])) {
$this->updatePassword($plainPassword);
} return true;
} return false;
} /**
* 更新密码
*
* @param string $newPlainPassword
*/
public function updatePassword($newPlainPassword)
{
$this->password = password_hash($newPlainPassword, self::PASSWORD_ALGO, ['cost' => self::PASSWORD_COST]);
$this->save();
}
}

  

这样子,在用户注册或修改密码的时候就调用 $user->updatePassword() 来设置密码,而登录的时候就调用 $user->verifyPassword() 来验证下密码是否正确。
当硬件性能提升到一定程度,而cost=11无法满足安全需求的时候,则修改下 PASSWORD_COST 的值即可无缝升级,让存放的密码更安全。

密码存储中MD5的安全问题与替代方案的更多相关文章

  1. php中的md5()的安全问题

    汇总下php中md5()的安全问题 安全问题1: 1.x=任意字符串  md5('x')=0e*** 2.y=任意字符串  md5('y')=0e*** 如果x==y,php会返回true,在有些时候 ...

  2. iOS中MD5加密字符串实现

    1.MD5加密 Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护.该算法的文件号为RFC 1321 ...

  3. PHP 开发者如何做好密码保护 & Laravel 底层密码存储和验证实现

    随着在线攻击的增多,密码安全越来越重要.作为开发者我们要担负起安全管理.计算哈希和存储用户密码的责任,不管应用是简单的游戏还是绝密商业文件的仓库,都要做到这一点.PHP内置了一些工具,让保护密码变得更 ...

  4. PHP开发中常见的安全问题详解和解决方法(如Sql注入、CSRF、Xss、CC等

    页面导航: 首页 → 网络编程 → PHP编程 → php技巧 → 正文内容 PHP安全 PHP开发中常见的安全问题详解和解决方法(如Sql注入.CSRF.Xss.CC等) 作者: 字体:[增加 减小 ...

  5. Android简易实战教程--第十五话《在外部存储中读写文件》

    第七话里面介绍了在内部存储读写文件 点击打开链接. 这样有一个比较打的问题,假设系统内存不够用,杀本应用无法执行,或者本应用被用户卸载重新安装后.以前保存的用户名和密码都不会得到回显.所以,有必要注意 ...

  6. 浅谈利用同步机制解决Java中的线程安全问题

    我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...

  7. 又拍云叶靖:OpenResty 在又拍云存储中的应用

    2019 年 7 月 6 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·上海站,又拍云平台开发部负责人叶靖在活动上做了<OpenRest ...

  8. PHP中md5()函数绕过

    PHP md5()函数的简单绕过方法,该篇作为学习笔记简单记录一下.   例题   例题链接: http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php   ...

  9. html5 webDatabase 存储中sql语句执行可嵌套使用

    html5 webDatabase 存储中sql语句执行可嵌套使用,代码如下: *); data.transaction(function(tx){ tx.executeSql("creat ...

随机推荐

  1. $bzoj1019-SHOI2008$ 汉诺塔 $dp$

    题面描述 汉诺塔由三根柱子(分别用\(A\ B\ C\)表示)和\(n\)个大小互不相同的空心盘子组成.一开始\(n\)个盘子都摞在柱子\(A\)上,大的在下面,小的在上面,形成了一个塔状的锥形体. ...

  2. python pandas使用chunksize异步拆分固定行数的文件

    import pandas as pd import asyncio from collections import defaultdict collect = defaultdict(list) # ...

  3. cuda9.0 中不存在libnppi.so

    编译一个caffe版本,报错找不到 -lnppi 发现使用打是cuda9.0, 但是cuda9.0 中不存在libnppi.so. 只好换成cuda8.0.

  4. [转]Hive 数据类型

    Hive的内置数据类型可以分为两大类:(1).基础数据类型:(2).复杂数据类型.其中,基础数据类型包括:TINYINT,SMALLINT,INT,BIGINT,BOOLEAN,FLOAT,DOUBL ...

  5. JWT(Json Web Token)初探与实践

    前言什么是JWT?为什么使用JWT?什么时候使用JWT?JWT的基本结构HeaderPayloadSignature将他们放在一起项目实践JWT后端前端关于安全性总结参考 协议标准:https://t ...

  6. ViewData与ViewBag的区别

    本文导读:在asp.net mvc程序设计中,传递数据常常会用到viewdata.viewbag.ViewData是一个字典集合,通过key值读取对应的value:ViewBag是动态类型,作用和Vi ...

  7. datepicker97切换年月日再连续点击下拉中日期的bug出现问题

    解决办法: function wdateOption(fmt){ if(fmt===undefined){fmt="yyyy-MM-dd"} return{ dateFmt:fmt ...

  8. C 六度空间理论的实现

    “六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论.这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够 ...

  9. CentOS6.4 安装sftp

    1.打开命令终端窗口,按以下步骤操作.使用ssh -V 命令来查看openssh的版本,版本必须大于4.8p1,低于的这个版本需要升级. [root@ecs-3c46 ~]# ssh -v OpenS ...

  10. Hibernate一对多关系操作

    1.创建两个实体类. 一个实体类是商品类,另一个实体类是商品的分类类. 在一对多关系的两个实体中,在编写实体类时必须要遵循以下规则: (1)在一的那一方的实体中,必须要有一个私有的多那一方的实体对象属 ...