怎么知道 网站是否直接明文保存密码_谈谈密码安全:服务端密码保存

现在有越来越多的网络服务,基本都需要注册才能使用,注册需填写账号和密码。账号通常是邮箱或手机号,普通人基本上只有一两个邮箱和手机号,而普通人记得的密码也只是一两个。因此很多人会很自然地在不同的网络服务中使用相同的账号和密码。

这样就出现安全隐患。一旦某个服务的账号密码被泄露,其它的服务安全就受到牵连。

密码安全,应该分两部分讨论:

  1. 作为账号系统的设计者,如何对用户负责,更安全地保存密码。
  2. 作为普通个人,应该怎么管理自己的密码。

本文只讨论第一点。

安全性

服务端肯定在某个地方保存了用户信息,这样才能验证用户登录。一旦信息被保存下来,就有可能被泄露。

安全性总是会被忽视,都是出事之后才会受到关注。而假如做得很好的,反而会淡化自身的存在,默默无闻。古人也说,善战者无功名,无勇功。反而是那些出问题比较多,自己搞出些意外,再拼命修改的,存在感会更强。但这种出问题再修正的,就算修好也会有损害的。

密码安全是一步步受到重视的。开发者设计好系统,之后系统受到攻击;开发者想方法去改进,之后攻击者又想出新的攻击方法;之后开发者再次改进,之后又出现新的攻击方式。就这样循环。有时需要先理解出了什么些问题,才能更好理解为什么需要这样做,

下面回顾密码保存的方法。

明文密码

很久前安全意识还很淡薄,不少服务器密码是明文保存的。

用户 A 注册的时候,需要向服务器发送账号 A(a) 和密码 P(a)。服务端就直接保存下用户的账号 A(a) 和密码 P(a)。当用户登录的时候,再次发送账号 A(a) 和密码 P(a)。服务器比较各个账号,当某个账号 A(n) == A(a) 并且密码 P(n) == P(a),用户登录成功。

现在的观点看来,这样的密码管理实在是太脆弱了。一旦攻击者成功入侵,获取到数据库,就直接取得账号和密码。

有些人可能会说,数据库本身也会加密啊,就算攻击者获取到数据库也打不开。但假如数据库很有价值,比如有 1 亿用户。这个数据库被破解也只是时间问题。

有些明文保存将账号密码写在文件上面,也不加密。攻击者连破解数据库的步骤也省了。

加密密码

随着时间发展,开发者认识到密码明文保存的脆弱性,采用加密保存密码。

账号基本上还是明文,因为需要找回密码之类的功能,不知道原始账号就做不到这一点。而密码通常会使用 MD5,SHA 之类的算法加密保存。

这里说加密算法其实是不准确的,更准确的是哈希算法(或摘要算法)。平常说的加密会有相应的解密算法。加密从 E(P) 得到 P', 就会有一种解密算法 D(P') 还原得到 P。MD5,SHA 之类的哈希算法是单向的,从 H(P) 得到 P', 之后没有办法还原出 P。

本文中,在不引起歧义的情况下,将加密算法和哈希算法等同,只是为了描述方便。但大家要清楚意识到,MD5、SHA 严格来说并非是加密算法。

用户 A 注册的时候,也向服务器发送账号 A(a) 和密码 P(a)。服务端计算出加密后的密码,P'(a)。之后保存下,用户的账号 A(a),和加密密码 P'(a)。当用户登录的时候,再次发送账号 A(a) 和密码 P(a),服务器再次计算出 P'(a), 之后保存的信息比较:

A (1), A (2), A (3), A (4)      A (n)
P'(1), P'(2), P'(3), P'(4) .... P'(n)

当某个账号 A(n) == A(a) 并且密码 P'(n) == P'(a),用户登录成功。因为不可以通过 P',逆向还原出 P。就算泄露了加密后的用户密码,攻击者也不知道原始密码。这种系统,粗略看来似乎很安全了。

攻击者角度

对于攻击者来说,既然不可以逆向还原密码,但还是可以可以正向计算加密密码。

攻击者将常用的密码,比如常用单词、数字组合、生日等用计算机暴力扫描计算生成一个大表。这个表叫彩虹表。这样的表可以包含了几十万亿、几百万亿个密码,基本上涵盖了常用的密码了。常见流行的加密算法,比如 MD5,SHA 之类,都已经有预先计算好的彩虹表。这样的表,原密码和加密密码的一一对应。

P(1) -> P(1)'
P(2) -> P(2)'
P(3) -> P(3)'
 .....
P(n) -> P(n)'

这样当取得用户密码 P', 就再这表中查找,假如 P' == P(n)', 就表示原来密码 P == P(n)。有了彩虹表之后,对于常见的密码,一查表就可以还原出来了。

注意到,彩虹表不可能覆盖全部的密码,因为可能的密码实在太庞大了。但普通人取密码为方便容易记忆,都不可能是完全随机的。倾向于取容易记忆的单词、生日、电话号码之类,考虑到这些因素,彩虹表可以覆盖绝大部分日常密码。

安全性前提

这里先暂停一下,为更好讨论,需要承认安全性的两个大前提:

  1. 预计最坏的情况,攻击者已经知道除了用户原始密码的任何信息。包括全部程序、加密算法、数据库等等。
  2. 世上没有绝对的安全,但是假如当破解密码需要的成本超过获得的收益,就是相对安全的。

前提 1 下,妄想隐藏某个信息期望攻击者不知道,只会获得虚假的安全感。一旦将信息保存在某个地方,就可能泄露,就需要假设攻击者已经知道了这信息。至于信息泄露已经是另外的话题,或者攻击者攻击服务器拿到全部信息,或者内部有人受贿出卖资料,但到底是怎么泄露的已经和这里的讨论无关。

前提 2 下,攻击是需要成本的(时间成本、机器成本等),一但攻击成本大于收益,作为一个理性的攻击者,就不值得去攻击了。

开发者角度

为避开彩虹表覆盖,开发者想出不少方法。

比如先用 MD5 加密,在用 SHA 加密,之后反复几次。或者土法炼钢,想出奇奇怪怪的加密算法。

他们的理论就是,我这样做之后,我的加密算法就是不标准的。攻击者不知道我的算法,就不能利用彩虹表来来还原密码了。

这样的有点天真。在安全性前提 1 下,攻击者可以知道任何你想隐藏起来的信息。攻击者既然已经可以拿到数据库了,那些程序自然也可以拿到,那些加密方式无论怎么不标准,也自然可以知道。使用不标准的加密方法,虽然攻击者预先准备好的彩虹表失效了,但攻击者可以针对特定加密算法将彩虹表再次计算出来。

比如数据库有 1 亿用户,数据库很有价值。攻击者重新计算出彩虹表用了 5 天,之后情况又跟使用标准的加密算法一样了,没有本质区别。

另外的一种想法是,MD5 等算法计算速度实在太快了。攻击者可以很快重新计算出彩虹表,既然这样,我就用慢点的算法,让攻击者计算起来更慢,就可以增大攻击成本。这样的算法有 bcrypt,比如计算一次密码用 0.3 秒。用这样的算法后,之前 5 天就可以算好彩虹表,现在需要 365 年。这样在安全性前提 2 下,成本过大,从而攻击者就放弃攻击了。

bcrypt 的精髓在于,其计算代价是可调节。当出现更快的计算机时,可将计算代价调大,让攻击的速度仍然很慢。

单纯拖慢速度的方法可以作为很好的辅助手段,但不能完全依赖这个。下面会介绍加密 + Salt 的想法,但先来了解攻击方式。

无差别攻击和针对性攻击

再次重申安全性前提:

  1. 攻击者已经知道除了用户原始密码的任何信息。
  2. 世上没有绝对的安全,但是假如当破解密码需要的成本超过获得的收益,就是相对安全的。

既然不能隐藏信息。我们的思考切入点就是如何增大攻击者成本。上面说的故意拖慢计算速度,是其中的方法。

回顾上面的攻击手段,假如数据库有 1 亿用户,攻击者就算没有预先准备好彩虹表,需要重新计算一次,一旦计算好,就可以针对于 1 亿人使用。假如有 2 亿用户,也只重新计算一次,就可以直接针对 2 亿用户使用。这样攻击成本是固定的,用户数量越大,分摊到每个人上面的攻击成本就越小。

假如攻击成本需要 5000 元,攻击成功之后,每个用户可以获得 0.1 元。这样用户数量一旦超过 50000,攻击就是值得的。

这种一次针对所有人的攻击,就是无差别攻击。攻击者的考虑角度和用户是不同的,作为用户只关心自己的个人账号是否安全,至于其它人是否安全是不关心的,用户更关系个人。作为攻击者,关心是否有足够多的人被破解,具体破解到什么人是不关心的。用户是鱼,攻击者去网鱼,攻击者只关心一网下去捞到多少条鱼,但不会关心具体捞到哪一条鱼。

攻击者最喜欢这种一网下去、一窝端的无差别攻击了。假如一次只能攻击其中特定某一个人,一次只去捞特定的一条鱼,就是针对性攻击。针对性攻击的成本会很大。

加密 + Salt

来到这里,我们可以讨论一种常用的保存密码方法,就是加密+Salt。Salt 在英文中是盐的意思,就是为原始密码加些其它信息。

加 Salt 的目的是将一窝端的无差别攻击,转成针对性攻击。

Salt 是一个服务器随机生成的字符串。

用户 A 注册的时候,需要向服务器发送账号 A(a) 和密码 P(a)。这时服务端为用户 A 生成一个 随机字符串 S(a),再和用户原始密码组合起来,用前面讨论的 MD5,RSA 等方式计算出密文 P'(a)。这里原始密码和 Salt 到底怎么组合,直接串起来、做 xor,、各个字符做减法,其实没有本质区别。

H[S(a) + P(a)] -> P'(a)

之后服务器将 S(a) 和 P'(a) 保存下来,随机字符串 S(a) 可以一直保持不变。当另一用户 B 注册的时候,再生成另外的随机字符串 S(b),重复上面注册过程保存下来。

这里的关键在于,每个用户的 Salt 都是不同的。

一旦加上 Salt,就算原始密码很简单,组合起来也足够随机。这样就算采用标准的算法,随机的密码也不可能出现在彩虹表中,攻击者预先准备好的彩虹表就失效了。

这里攻击者是完全知道保存下来的信息的。这样每个用户的加密密码 P' 和 Salt 也会知道。

这样攻击者攻击用户 A 的时候,就需要针对 A,采用跟 A 相同的 Salt 和加密算法, 重新计算,产生下面对应关系:

S(a) + P(1) -> P'(1)
S(a) + P(2) -> P'(2)
S(a) + P(3) -> P'(3)
....
S(a) + P(n) -> P'(n)

一旦 P'(n) == P', 就表示用户密码为 P(n)。到这里似乎跟之前的方法没有差别。

但攻击者去攻击用户 B 的时候,因为用户 B 的 Salt 跟用户 A 不同。这样之前针对用户 A 的计算,对于用户 B 无效。就需要重新计算用户 B 的密码。每一个不同的 Salt, 对于攻击者来说,就相当于不同的加密算法,都需要重新计算一次。

这样的话,本来一次计算可以适用于全部用户,现在就失效了。一窝端的无差别攻击,只能是转成针对性攻击。

加 Salt 后,需要每个用户分别计算。假如每次计算成本不变,还是 5000 元,每个用户收益还是不变,获得 0.1 元。这样的攻击就很不值得了。

一个笑话:每个用户加相同的 Salt

对于 加密 + Salt, 有个误解,就是以为每个用户都加相同 Salt。这种所谓的加相同 Salt 就是一个笑话。当每个用户都使用相同的 Salt 时,针对用户 A 的计算结果,就可以应用到用户 B 上。这样还是无差别攻击,跟上面说的想出一种非标准加密方法没有本质区别。

那些人可能会觉得 Salt 是随机生成的,嵌在代码中不保存到数据库,攻击者不可能知道。假如为每个用户加不同的 Salt,保存到数据库中。攻击者就知道每个用户的 Salt 了,这样更不安全。

这样的想法有点天真,这种想法跟那些去想各种各样不标准的算法,没有本质区别。上面都说了,攻击者既然可将数据库拿出来,那些程序代码也可以随手拿走。Salt 保存在代码还是保存到数据库当中是一样的。Salt 是随机生成的,但需要参与计算,就一定会被保存到某个地方当中,一但保存下来,参照前提 1,就需要假设攻击者就已经知道了。

再次强调,密码安全不能依赖隐藏信息来获得虚假的安全。需要假设攻击者已经取得全部信息,这种前提下自己发明各种土制算法也是个笑话。

现在服务端保持密码的一种比较好的方法是使用一种慢哈希算法 + Salt。比如 bcrypt 就算专门为这种应用场合设计的,其算法中可直接集成 Salt 原理。只要使用,就不用自己来操心了。基本想法是

  • 计算速度相对较慢。而选择 cost,可调节计算速度,。
  • 使用 salt, 将无差别攻击转化为针对性攻击。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值