首页 存档 技术 查看内容

对抗拖库Web前端慢加密

2018-3-30 13:00 |来自: 互联网 357 0

摘要:  0x00 前言   天下武功,唯快不破。但密码加密不同。算法越快,越容易破。  0x01 暴力破解   密码破解,就是把加密后的密码还原成明文密码。似乎有不少方法,但最终都得走一条路:暴力穷举。   也许你会说 ...

 0x00 前言

  天下武功,唯快不破。但密码加密不同。算法越快,越容易破。

 0x01 暴力破解

  密码破解,就是把加密后的密码还原成明文密码。似乎有不少方法,但最终都得走一条路:暴力穷举。

  也许你会说还可以查表,瞬间就出结果。虽然查表不用穷举,但表的制造过程仍然需要。查表只是将穷举提前了而已。

  密码加密,用的都是单向散列计算。既然单向,那就是不可逆,那只能穷举。

  穷举的原理很简单。只要知道密文是用什么算法加密的,我们也用相同的算法,把常用的词组跑一遍。若有结果和密文一样,那就猜中了。

  穷举的速度有多快?这和加密算法有关。加密一次有多快,猜一次也这么快。

  例如 MD5 加密是非常快的。加密一次耗费 1 微秒,那破解时随便猜一个词组,也只需 1 微秒(假设机器性能一样,词组长度也差不多)。攻击者一秒钟就可以猜 100 万个,而且这还只是单线程的速度。

  所以,加密算法越快,破解起来就越容易。

 0x02 慢加密

  如果能提高加密时间,显然也能增加破解时间。

  如果加密一次提高到 10 毫秒,那么攻击者每秒只能猜 100 个,破解速度就慢了一万倍。

  怎样才能让加密变慢?最简单的,就是对加密后的结果再加密,重复多次。

  例如,原本 1 微秒的加密,重复一万次,就慢一万倍了:

function slow_md5(x)
    for i = 0 ~ 10000
        x = md5(x)
    return x
end

  攻击者破解时,也必须用这套算法跑字典。于是,破解时间就大幅增加了。

  事实上,这样的「慢加密」算法早已存在,例如 bcrypt、PBKDF2 等等。它们都有一个难度系数因子,可以控制加密时间,想多慢就多慢。

  加密越慢,破解时间越长。

 0x03 慢加密应用

  最需要慢加密的场合,就是网站数据库里的密码。

  近几年,经常能听到网站被「拖库」的新闻。用户资料都是明文存储,泄露了也无法挽回。唯独密码,还可以和攻击者对抗一下。

  然而不少网站,使用的都是快速加密算法,因此轻易就能破解出一堆弱口令账号。

  当然,有时只想破解某个特定人物的账号。只要不是特别复杂的词汇,跑上几天,很可能就破出来。

  但网站用了慢加密,结果可能就不一样了。如果把加密时间提高 100 倍,破解时间就得长达数月,变得难以接受。

  即使数据泄露,也能保障「密码」这最后一道隐私。

 0x04 慢加密缺点

  不过,慢加密也有明显的缺点:消耗大量计算资源。

  使用慢加密的网站,如果同时来了多个用户,服务器 CPU 可能就不够用了。要是遇到恶意用户,发起大量的登录请求,甚至造成资源被耗尽。

  性能和安全总是难以兼得。所以,一般也不会使用太高的强度。

  一些大型网站,甚至为此投入集群,用来处理大量的加密计算。但这需要不少的成本。

  有没有什么方法,可以让我们使用算力强劲、同时又免费的计算资源?

 0x05 前端加密

  在过去,个人电脑和服务器的速度,还是有较大差距的。但如今,随着硬件发展进入瓶颈,这个差距正缩小。在单线任务处理上,甚至不相上下。

  客户端拥有强大的算力,能不能分担一些服务器的工作?

  尤其像「慢加密」这种算法开源、但计算沉重的任务,为何不交给客户端来完成?

  过去,提交的是明文密码;现在,提交的则是明文密码的「慢加密结果」。无论是注册,还是登陆。

  而服务端,无需任何改动。将收到的「慢加密结果」,当做原来的明文密码 就行。以前是怎么保存的,现在还是怎么保存。

  这样就算被拖库,攻击者破解出来的也只是「慢加密结果」,还需再破解一次,才能还原出「明文密码」。

  事实上,「慢加密结果」这个中间值,是不可能破解出来的!

  因为它是一个散列值 毫无规律的随机串,例如 32 位十六进制字符串,而字典都是有意义的词组,几乎不可能跑到它!

  除非字节逐个穷举。但这有 16^32 种组合,是个天文数字。

  所以「慢加密结果」是无法通过数据库里泄露的密文「逆推」出来的。

或许你在想,即使不知道明文密码,也可以直接用「慢加密结果」来登录。事实上后端储存时再次加密,就无法逆推出这个散列值了。

  当然,不能逆推,但可以顺推。把字典里的词组,用前后端的算法依次执行一次:

back_fast_hash( front_slow_hash(password) )

  然后对比密文,即可判断有没有猜中。这样就可以用跑字典来破解。

  但是有 front_slow_hash 这个障碍,破解速度就大幅降低了。

 0x06 对抗预先计算

  不过,前端的一切都是公开的。所以 front_slow_hash 的算法大家都知道。

  攻击者可以用这套算法,把常用词组的「慢加密结果」提前算出来,制作成一个「新字典」。将来拖库后,就可以直接跑这个新字典了。

  对抗这种方法,还得用经典的手段:加盐。最简单的,将用户名作为盐值:

front_slow_hash(password   username)

  这样,即使相同的密码,对于不同的用户,「慢加密结果」也不一样了。

  也许你会说,这个盐值不合理,因为用户名是公开的。攻击者可以对某个重要人物的账号,单独为他建立一个字典。

  那么,是否可以提供一个隐蔽的盐值?答案是:不可以。

  因为这是在前端。用户还没登录,那返回谁的盐值?登陆前就能获得账号的盐值,这不还是公开的吗。

  所以,前端加密的盐值无法隐藏,只能公开。

  当然,即使公开,单独提供一个盐值参数,也比用户名要好。因为用户名永远不变,而独立的盐值可以定期更换。


  盐值可以由前端生成。例如注册时:

# 前端生成盐值
salt = rand()
password = front_slow_hash(password   salt)

# 提交时带上盐值
submit(..., password, salt)

  后端将用户的盐值也储存起来。

  登录时,输完用户名,就可以开始查询用户对应的盐值:

  盐值的更换,也非常简单,甚至可以自动完成:

  前端在加密当前密码时,同时开启一个新线程,计算新盐值和新密码。提交时,将它们全都带上。

  如果「当前密码」验证成功,则用「新密码」和「新盐值」覆盖旧的。

  这样更换盐值,还是只用到前端的算力。

  这一切都是自动的,相当于 在用户无感知的情况下,定期帮他更换密码!

  密文变了,针对「特定盐值」制作的字典,也就失效了。攻击者得重新制作一次。

 0x07 强度策略

  密码学上的问题到此结束,下面讨论实现上的问题。

  现实中,用户的算力是不均衡的。有人用的是神级配置,也有的是古董机。这样,加密强度就很难设定。

  如果古董机用户登录会卡上几十秒,那肯定是不行的。对于这种情况,只有以下选择:

  • 强度固定

  • 强度可变

  1.强度固定

  根据大众的配置,制定一个适中的强度,绝大多数用户都可接受。

  但如果超过规定时间还没完成,就把算到一半的 Hash 和步数提交上来,剩余部分让服务器来完成。

[前端] 完成 70% ----
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部