文档章节

密码存储中MD5的安全问题与替代方案

ZYallers
 ZYallers
发布于 2017/07/25 15:21
字数 1581
阅读 2
收藏 0
点赞 0
评论 0

密码存储中MD5的安全问题与替代方案

image

md5安全吗?

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

这儿有个脚本来测试下MD5的速度

<?php
$testRounds = 100;
$testTimes  = 1000000;
$times = [];
$data = 'abcdefgh';
for ($i = 0; $i < $testRounds; $i++){
    $begin = microtime(true);
    for ($j = 0; $j < $testTimes; $j++){
        $hash = md5($data);
    }
    $times[] = microtime(true) - $begin;
}
print_r([
    'rounds' => $testRounds,
    'times of a round' => $testTimes,
    'avg' => array_sum($times) / count($times),
    'max' => max($times),
    'min' => min($times),
]);

测试结果:

[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.

在这个名叫“密码散列算法”的核心扩展中提供了一系列简洁明了的对密码存储封装的函数。简单介绍下:

password_hash是对密码进行加密(hash),目前默认用(也只能用)bcrypt算法,相当于一个加强版的md5函数

password_verify是一个验证密码的函数,内部采用的安全的字符串比较算法,可以预防基于时间的攻击, 相当于 $hashedPassword === md5($inputPassword)

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

比如下面是笔者的一次测试

<?php
echo sprintf("%10s %10s\n", 'cost', 'time');
for ($cost = 8; $cost < 20; $cost++) {
    $begin = microtime(true);
    password_hash('test1234', PASSWORD_BCRYPT, ['cost' => $cost]);
    $delta = microtime(true) - $begin;
    echo sprintf("%10d %10.6f\n", $cost, $delta);
}

测试结果(个人弱机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攻击。

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

总上所述,一个安全地存储密码的方案应该是这样子的:

<?php
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 的值即可无缝升级,让存放的密码更安全。

© 著作权归作者所有

共有 人打赏支持
ZYallers
粉丝 1
博文 59
码字总数 19100
作品 0
佛山
程序员
如何安全的存储密码 - hash、salt 以及更多

最近爆出的网站安全问题越来越多,甚至有部分网站的密码泄露,这是一篇简单的教程,教你如何更加安全的保存密码。我这里说的“安全”是指当密码泄露以后,对方需要相当一段时间来破解以获得明...

虫虫 ⋅ 2012/06/11 ⋅ 10

常用的加密算法

概述 加密算法分为单向加密和双向加密,明文数据通过加密后传输,以确保传输和存储安全 单向加密包括 MD5、SHA、BCrypt 加密算法等,它是不可逆的,也就是无法将加密后的数据恢复成原始数据,...

林塬 ⋅ 2017/12/25 ⋅ 0

PHP中“==”运算符的安全问题

原文出处:digitalloft 译文出处:安全客 前言 PHP是一种通用的开源脚本语言,它的语法混合了C,Java,以及Perl等优秀语言的语法。除此之外,它还提供了大量的函数库可供开发人员使用。但是,...

digitalloft ⋅ 2016/08/05 ⋅ 0

浅谈OpenStack平台的安全问题及措施

OpenStack的优势与劣势 Openstack具有三大特点:免费开源、强大的兼客性以及开放性。 Openstack 本身是一个开源、免费的软件,同商业软件相比它给了客户足够的自由度,可以在任何场合使用,O...

凌洛 ⋅ 05/12 ⋅ 0

用户登陆的业务流程架构设计

一、目的 对用户登陆业务流程进行一个梳理,试图从应用层角度来解决业务上曾经遇到的一些坑,提高业务的安全性,但是不可避免,流程上复杂了一些,同时业务处理时间上会有一些损耗。 二、主要...

saintatgod ⋅ 2015/12/01 ⋅ 9

被 MD5 加密坑了?雅虎大规模泄密是个咋样的悲剧

据路透社报道,2013 年夏天,雅虎推行一个新项目来保障用户密码,同时宣布放弃旧有的安全性不佳的 MD5 加密方案。希望通过这一举措更好地保障用户密码安全。然而为时已晚,一切来得太迟了。同...

局长 ⋅ 2016/12/21 ⋅ 57

HTTP认证方式

HTTP请求报头: Authorization HTTP响应报头: WWW-Authenticate HTTP认证是基于质询/回应(challenge/response)的认证模式。 1 基本认证 basic authentication(HTTP1.0提出的认证方法) 基本...

ericSM ⋅ 2015/11/26 ⋅ 0

如何安全设定和检测你的密码安全性?

自去年CSDN网站、天涯、开心网、人人网等网站被曝大量用户的密码被泄漏以来,密码的安全问题已引起用户和业界的极大关注和重视。你的密码安全吗?上互联网站还会不会泄漏密码?......这些问题...

samsunglinux ⋅ 2012/04/28 ⋅ 0

谷歌:安全问题机制并不如你想象中安全

腾讯科技讯 5月25日,如今的你或许已经对许多网站所使用的“安全问题机制”习以为常了,但你真的认为包括“你第一个宠物的名字是什么?”这些问题能够保障你的帐户安全吗? 根据谷歌(微博)安...

问题终结者 ⋅ 昨天 ⋅ 0

VC++网络安全编程范例(7)-实现哈希摘要算法

哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母...

junwong ⋅ 2012/03/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

645. Set Mismatch - LeetCode

Question 645. Set Mismatch Solution 思路: 遍历每个数字,然后将其应该出现的位置上的数字变为其相反数,这样如果我们再变为其相反数之前已经成负数了,说明该数字是重复数,将其将入结果r...

yysue ⋅ 5分钟前 ⋅ 0

Confluence 6 从生产环境中恢复一个测试实例

请参考 Restoring a Test Instance from Production 页面中的内容获得更多完整的说明。 很多 Confluence 的管理员将会使用生产实例运行完整数据和服务的 Confluence 服务器,同时还会设置一个...

honeymose ⋅ 10分钟前 ⋅ 0

Python这么强?红包杀手、消息撤回也可以无视,手机App辅助!

论述 标题也许有点不好理解,其实就是一款利用Python实现的可以监控微信APP内的红包与消息撤回的助手。不得不说,这确实是一款大家钟意的神器。 消息撤回是一件很让人恶心的事,毕竟人都是有...

Python燕大侠 ⋅ 21分钟前 ⋅ 0

压缩打包介绍、gzip压缩工具、bzip2压缩工具、xz压缩工具

压缩打包介绍 压缩的好处不仅能节省磁盘空间而且在传输的时候节省传输时间和网络带宽 windows系统下文件带有 .rar .zip .7z 后缀的就是压缩文件 linux系统下则是 .zip, .gz, .bz2, .xz, ...

黄昏残影 ⋅ 26分钟前 ⋅ 0

观察者模式

1.利用java原生类进行操作 package observer;import java.util.Observable;import java.util.Observer;/** * @author shadow * @Date 2016年8月12日下午7:29:31 * @Fun 观察目标 **/......

Cobbage ⋅ 29分钟前 ⋅ 0

Ubuntu打印服务器配置

参考:https://blog.csdn.net/gsls200808/article/details/50950586 https://blog.csdn.net/jiay2/article/details/80252369 https://wiki.gentoo.org/wiki/HPLIP 由于媳妇儿要大量打印资料,......

大熊猫 ⋅ 34分钟前 ⋅ 0

面试的角度诠释Java工程师(二)

原文出处: locality 续言: 相信每一位简书的作者,都会有我这样的思考:怎么写好一篇文章?或者怎么写好一篇技术类的文章?我就先说说我的感悟吧,写文章其实和写程序是一样的。为什么我会...

颖伙虫 ⋅ 37分钟前 ⋅ 0

github中SSH的Key

https://help.github.com/articles/connecting-to-github-with-ssh/ https://help.github.com/articles/testing-your-ssh-connection/ https://help.github.com/articles/adding-a-new-ssh-k......

whoisliang ⋅ 38分钟前 ⋅ 0

only_full_group_by

我的mysql是在CentOS7.1下面的5.7.17 在 /etc/my.cnf 文件里加上如下: sql_mode='NO_ENGINE_SUBSTITUTION' 然后,重启Mysql服务 systemctl restart mysqld...

SunHacker ⋅ 今天 ⋅ 0

实际项目(SpringBoot项目)中集成Druid

参考网页 https://blog.csdn.net/liuchuanhong1/article/details/55050131 https://blog.csdn.net/CoffeeAndIce/article/details/78707819 https://www.pocketdigi.com/20170530/1577.html 为......

karma123 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部