经作者授权转载,原文链接,作者:Roronoa Zoro
本文主要讲常见场景的数据加密方案,以及对未来加密技术的展望,先看几条新闻:
Facebook 明文存储用户密码:
Hundreds of millions of Facebook users had their account passwords stored in plain text and searchable by thousands of Facebook employees — in some cases going back to 2012, KrebsOnSecurity has learned. Facebook says an ongoing investigation has so far found no indication that employees have abused access to this data.
早在 2012 年,Facebook 明文存储数亿用户的账户密码,成千上万的 Facebook 的员工可以随意进行搜索......
原文:Facebook Stored Hundreds of Millions of User Passwords in Plain Text for Years
CSDN 600 万用户账号密码泄露:
北京时间 12 月 21 日晚间消息,中国开发者技术在线社区 CSDN 今晚发表声明,就“600 万用户账号密码泄露”一事公开道歉,承认部分用户账号面临风险,将临时关闭用户登录,并要求“2009 年 4 月以前注册的帐号,且 2010 年 9 月之后没有修改过密码”的用户立即修改密码。
很多新手程序员都是这样存储密码的:
username | phone | password |
---|---|---|
小明 | 18888888888 | asd123456 |
大明 | 17777777777 | 123abc!@# |
为什么这样做是不安全的?
首先,如果遇到数据泄露事件,明文密码直接将用户隐私暴露在空中,任何人可以登陆暴露密码的账号,随意更改。其次,即使不会泄露,内部员工也可以轻易访问用户的明文密码,当公司上了规模,你无法保证公司内部没有坏人,他们是否会搜索某些用户的密码,侵犯用户隐私。所以明文存储密码是绝对不安全的。
即使你用了这样的密码:ppnn13%dkstFeb.1st(娉娉袅袅十三余,豆蔻梢头二月初),明文存储,安全性也是木有的。
再复杂的密码,也敌不过 CSDN 的明文
来自知乎用户:Right Here
题外话:历史上最有名的电脑密码是什么?
密码 | 含义 |
---|---|
FLZX3000cY4yhx9day | 飞流直下三千尺,疑似银河下九天 |
hanshansi.location()!∈[gusucity] | 姑苏城外寒山寺 |
hold?fish:palm | 鱼和熊掌不可兼得 |
Tree_0f0=sprintf("2_Bird_ff0/a") | 两个黄鹂鸣翠柳 |
csbt34.ydhl12s | 池上碧苔三四点,叶底黄鹂一两声 |
for_\$n(@ RenSheng)_\$n+="die" | 人生自古谁无死 |
while(1)Ape1Cry&&Ape2Cry | 两岸猿声啼不住 |
doWhile(1){LeavesFly();YangtzeRiverFlows()}; | 无边落木萧萧下,不尽长江滚滚来 |
dig?F*ckDang5 | 锄禾日当午 |
既然密码不能明文存储,那怎么存储才是安全的?我如何检查用户输入的密码是正确的?
存储相关信息用于校验是必须的,有没有一种机制能够只保存密码的部分信息,也能用于密码校验?这样即使数据库泄露,攻击者也无法通过这些信息反推用户的密码,进而保护用户账号安全。
哈希函数(hash function)可以解决这个问题。
哈希函数是单向不可逆的,从上图很好理解,经过 hash 函数都会被丢弃一部分信息,就如同这个算法:
算法:存储用户名时丢弃用户姓氏然后随机打乱顺序,输入赵日天,输出天日。
即使知道这个算法和天日这个数据,也无法推断出赵日天这个名字,因为部分信息丢失了。
$$ h = hash(p) $$
h 为最终存储到数据库的值,p 为用户原始密码,在用户登陆时,输入密码 p1,我们通过计算 $h_1 = hash(p_1)$,判断 h1 是否与数据库中的记录 h 相同,确定用户输入的密码是否正确。
所有哈希函数都有一个性质:如果两个 h 值是不一致的,那么输入 p 值也不一样(单向散列函数),但另一方面输入和输出并非一一对应的关系,比如存在不同的 h 值,使得经过 hash 函数计算后的 p 值是一样的。
没有,由于上述哈希函数的性质,如果两个用户都用了 123456abc,这样的密码,那么数据库存储的 h 值都是一样的,而且不同密码可能计算出来的 h 值是一样的(碰撞攻击),那么攻击者就可以根据 hash 函数,暴力计算所有可能性,做成一张表格,这样拿到 h 值的时候就可以推断出密码是 123456abc 了。这种做法叫彩虹表攻击。
比如以前常用的哈希函数 MD5,由于计算力逐步增强,现在已经是不安全的了:
MD5 1996 年后被证实存在弱点,可以被加以破解,对于需要高度安全性的资料,专家一般建议改用其他算法,如 SHA-2。2004 年,证实 MD5 算法无法防止碰撞攻击,因此不适用于安全性认证,如 SSL 公开密钥认证或是数字签名等用途
对付彩虹表可以用 Sated Hash 的方式,比如对于每个用户密码存储时都记录一个随机的 salt 值(盐值),用这个盐值和密码 p,计算出 h 值:
$$ h = hash(salt, p) $$
数据库同样存储了 salt 值和 h 值,这样攻击者想获取一个用户的密码,就得建立一个对应的彩虹表,增大攻击者的成本。
但即使这样,使用 SHA-2 再加盐也不是安全的,因为计算力逐年提升,攻击成本下降,有财力的攻击集团还是能够建立这些彩虹表,进而窃取用户密码。
加盐的方式只是多了独立的彩虹表,如果我们可以利用硬件控制每次 hash 计算的时间比如 1 秒,且无论用什么机器,什么高性能的 CPU 计算,每次都要 1 秒的时间,攻击者要计算这个彩虹表,1000 万个组合就得 115 天(hash 空间远远超过 1000 万),那这种方式就很难破解了。
bcrypt 是一个由 Niels Provos 以及 David Mazières 根据 Blowfish 加密算法所设计的密码散列函数,于 1999 年在 USENIX 中展示 [1]。实现中 bcrypt 会使用一个加盐的流程以防御彩虹表攻击,同时 bcrypt 还是适应性函数,它可以借由增加迭代之次数来抵御日益增进的电脑运算能力透过暴力法破解。
除了对您的数据进行加密,默认情况下,bcrypt 在删除数据之前将使用随机数据三次覆盖原始输入文件,以阻挠可能会获得您的计算机数据的人恢复数据的尝试。如果您不想使用此功能,可设置禁用此功能。
除了 bcrypt 这种调整计算强度,抵御日益增长的 CPU 计算力带来攻击风险的算法,scrypt 算法还利用了内存空间,每次计算都要占用一定内容,不过 bcrypt 算法由于有成熟的实现,实际使用较多,spring boot security 的密码加密就用的这个算法。
比如某个密码经过 bcrypt 加密后变成这样:
\$2a\$07\$woshiyigesaltzhi\$\$\$\$\$.lrU488y7E1Xw.JA4uizIu.PBSSe7t4y
2a 表示 bcrypt 算法的版本,07 代表迭代次数,次数越高,每次计算所需的时间越长,后面的 woshiyigesaltzhi$$$$$ 代表加密用的 salt 值,数据库可以直接存储这个字段,比如:
name | phone | pwd_hash |
---|---|---|
小明 | 1234 | \$2a\$07\$woshiyigesaltzhi\$\$\$\$\$.lrU488y7E1Xw.JA4uizIu.PBSSe7t4y |
这种也是本文推荐的密码存储方式,hash + salt + 计算强度,可以较好保护用户密码安全,由于登陆不是频繁的操作,每次登陆时用户等待 1 秒也没有太大关系。
密码信息可以 hash,达到不可逆的目的,但是一些用户数据是可逆的,而且要求加密,怎么办?比如用户在线文档,通过用户自定义的密码加解密。
很容易想到的就是 AES256 之类的对称加密技术:
$$ e = AES256(salt, text) $$
通过拿用户的密码作为 salt 值加解密文档,服务端存储用户密码。
还是前面的问题,明文存储密码是不安全的行为,这里可以用双重 hash 的方法保证一定的安全性(这里的双重 hash 不是连续计算两次 hash 的意思):
实现方案:
用户密码存储仍然采用前面提到的方案,可以用 bcrypt 算法,这里记存储的值为 h1,当用户请求加密数据时,提供了加密密码,我们通过 h1 检验用户密码的正确性,同时用用户的密码计算出另一个 hash 值,记为 h2,h2 的计算方式与 h1 不同,只是简单的 hash,不能加盐,这时我们使用 h2 加解密用户文档:
$$ e = AES256(h2, text) $$
记住,h2 的值是不能存储的(不能保存于数据库中),用于加解密数据。由于 h2 的值用完就丢掉。h2 的 hash 函数可以是私有的,进一步保障安全性。
为什么需要 h2 这个参数?首先用户密码长度不一致,像 AES 之类的对称加密算法需要有一个固定长度的加密参数,其次经过 hash 之后能够进一步保障数据安全性,如果数据库泄露,攻击者即使知道用户密码,不知道私有 hash 函数也无法进行解密。
上述提到的加密方案是用户密码控制的,数据安全性非常高(只有用户知道密码,且密码丢失后数据不可恢复),但是这种做法很多场景不适合,比如常规的用户数据:手机号、社交账号、地址、姓名,高频访问的数据不适合用密码加密,效率太低,那如何保护用户这类数据的安全性呢?
理解这个问题,需要知道用户数据是怎样传输的:
用户在客户端软件上产生数据(比如浏览器上),经过 https 加密传输到后端服务器,有服务端软件处理(比如 java),之后经过调用数据库接口通过数据库软件(比如 mysql)存储到存储设备中(比如硬盘)。
有 4 个阶段可以进行数据加密,加密的过程越靠近用户越安全(客户端加密后面说):
加密方式 | 防止内部泄露 | 防止数据库泄露 | 防止物理机丢失泄露 |
---|---|---|---|
服务端软件加密 | √(大部分场景) | √ | √ |
数据库软件加密 | √ | √ | |
存储端落盘加密 | √ |
前两者加密方式都可以保证即使是数据库管理员也无法查看用户数据,最后一种通常意义不大,但一些国家、地区的法律要求,或者用户要求有硬盘加密措施,仍然需要使用。数据库软件加密仍然存在内部泄露的风险,比如 mysql 的 binlog,即使你使用了 AES256,数据同步时密钥也会存储在 binlog 中,存在泄露的途径。
如果相关用户数据没有搜索的需求(只有常规读写需求),可以使用服务端加密或者数据库加密的手段保护用户数据。但如果相关数据需要支持搜索功能,这个问题就很棘手了。
这篇论文 Practical techniques for searches on encrypted data 发表于 2000 年,开创了一个新的研究方向 Searchable Encryption,提出了第一个实用的可搜索加密方案 SWP,实现思路大概是:通过加密每个单词,然后将一个 hash 值嵌入密文中,服务器通过提取改 hash 值,检查密文中是否有类似的特殊格式,确认是否匹配搜索。
上文想法很理想,但是落地时会有很多困难,比如必须使用固定大小的单词,但搜索系统最重要的就是其分词引擎,如何对多种语言进行良好的分词直接决定了搜索的效果,不少搜索系统任然采用如下结构:
将 mysql 的数据单向同步到 elasticsearch 中,完成相关搜索功能的支持。20 多年过去了,今天,可搜索加密技术依然无法落地使用,甚至可以这么说,如果软件商提供了搜索功能,该数据存储的时候就是没有加密的(硬盘加密对软件层不可见),已加密的数据是无法支持搜索功能的。
假如我是一个坏人,要实现某个阴谋,很重要的前提就是操作足够简单,知道的人足够少,当复杂度太大或者需要的人很多的时候就不可能实现这个阴谋。
知道这一点我们可以很轻松地判断:“美国登月是假的”,这个阴谋猜想是错的。因为登月工程涉及的人很多、工程复杂,无法实现这个阴谋。
这个结论是下文内容的基础。
上述提到的可搜索加密技术无法落地,一些软件厂商(包括一些大厂)提供了搜索功能,仍然声称他们对用户数据进行了加密,很安全,他们到底在说什么?
第一,他们可能说的是硬盘加密,而不是软件层的加密(只能防御硬盘盗窃风险,不能防御数据泄露风险、内部风险);第二,他们可能说的是传输过程的加密,比如 https 或私有通讯加密协议;第三,他们可能有完善内部管理流程,控制内外部风险。
利用上面阴谋论的前提,只要增加一些流程,公开透明出来,提高使坏的成本,在技术不可实现的情况下,也能保障用户数据的安全,比如:
拥有完善的流程也能让用户放心存储隐私数据。
前面没有提到的客户端加密放这里说。加密点越靠近用户,就越安全,如果用户发出来的数据就是经过加密的(非 https 类传输加密),并且自行控制密码,这样服务提供商也无需进一步做加密处理了,标题提到的不用加密指的就是这个,服务商不用再花费大量精力、成本在数据安全上面,将数据所有权交给用户。
上面客户端加密所需的一个技术就是:全同态加密,这是密码学领域的一个重要课题,在 2009 年 9 月,IBM 的博士克雷格·金特里发表了一篇论文:Fully Homomorphic Encryption Using Ideal Lattices,提出了一个可行方法,自此解决了密码学的一大难题。全同态加密可以简单理解如下:
$$ f(data) = DE(\ f(\ E(data)\ )) $$
其中 f 是任意操作函数,E 是加密函数,DE 是解密函数,也就是说对密文的任意计算操作等同于对明文做同样的操作。
这就很牛逼了!!!!!!!!!!!比如我手上有一些财务数据,需要第三方机构进行统计分析,但是我又不想直接给他们数据,他们告诉我可以提供全同态加密服务,那么我可以在给他们数据之前将数据加密一遍,他们给我统计计算后的结果,我解密相关结果即可,真实的数据只有我看得到。
全同态加密的这些特性能够很好解决数据安全,信任的问题,克雷格·金特里给出了一个实现,之后很多密码学者也给出其他实现方法,但目前的角度来说该技术还不成熟,比如一个密钥就得 100 MB 大小,对于当下网络环境来说无法实际使用。
没有绝对的安全,只有相对的安全。期待全同态加密技术在商用领域取得突破性进展。