为什么写这篇文章
近期, Github 被爆出, 在内部日志中记录了明文密码.
虽然据说影响面很小(因为日志外部访问不到), 但是网络和数据安全问题又一次被放到台面上. 大多数用户的常用密码就那么几个, 一旦被黑客拿到, 去其他网站 "撞库", 可能会造成用户的财产损失.
本篇文章主要介绍如何加密传输和存储用户密码, 并讲解相关原理.
加密传输
加密主要有两种方式: 对称加密和非对称加密.
对称加密
对称加密算法在加密和解密时使用的是同一个秘钥.
对称加密的模式是:
- 甲方选择某一种加密规则, 对信息进行加密
- 乙方使用同一种规则, 对信息进行解密
客户端和服务端进行通信, 采用对称加密, 如果只使用一个秘钥, 很容易破解; 如果每次用不同的秘钥, 海量秘钥的管理和传输成本又会比较高.
非对称加密
非对称加密算法需要两个密钥来进行加密和解密, 这两个秘钥是公开密钥 (public key, 简称公钥) 和私有密钥(private key, 简称私钥).
非对称加密的模式则是:
- 乙方生成两把密钥(公钥和私钥). 公钥是公开的, 任何人都可以获得, 私钥则是保密的
- 甲方获取乙方的公钥, 然后用它对信息加密
- 乙方得到加密后的信息, 用私钥解密.
即使黑客拿到了公钥, 没有私钥也是没有办法解密, 不考虑彩虹表的情况, 完全可以长期使用一对秘钥.
RSA 算法
最经典的非对称加密算法是 RSA 算法.
RSA 公钥加密算法是 1977 年由罗纳德. 李维斯特 (Ron Rivest), 阿迪. 萨莫尔(Adi Shamir) 和伦纳德. 阿德曼 (Leonard Adleman) 一起提出的. 公钥私钥成对出现, 用其中一个加密只能用另一个解密, 通常用公钥加密私钥解密.
为什么 RSA 能够做到非对称加密呢?
互质关系: 如果两个正整数, 除了 1 以外, 没有其他公因子, 我们就称这两个数是互质关系
简单来说, RSA 利用的原理是, 如果两个互质关系的正整数的乘积足够大, 是极难进行因式分解的(目前被破解的最长 RSA 密钥是 768 个二进制位, 而正常使用的至少是 1024 位的密钥).
通过一定的运算, 把某计算结果和乘积作为公钥, 另一个计算结果和乘积作为私钥, 即可以实现, 利用公钥进行加密, 并利用私钥进行解密. 具体的数学公式推导和证明可以参考 RSA 算法原理.
github 的登录方式
传输层面的加密解密原理讲的差不多了, 我们来看看 github 是如何传输账号密码的.
抓包看一下登录请求的 request, 赫然发现, 密码是通过明文传输的......
那么, 这种传输方式安全嘛? 还可以, 因为使用了 https, 但还不够安全.
http 和 https
常规的 http 请求, 所有信息明文传播, 只要中间人在链路中的任意阶段进行劫持, 就会带来三大风险:
窃听风险(eavesdropping): 第三方可以获知通信内容.
篡改风险(tampering): 第三方可以修改通信内容.
冒充风险(pretending): 第三方可以冒充他人身份参与通信.
怎么解决这些问题? 用 https.
https 可以认为是 http + TLS,TLS 是传输层加密协议, 它的前身是 SSL 协议.
SSL/TLS 协议是为了解决 http 的三大风险而设计的, 希望达到:
- 内容加密. 所有信息都是加密传播, 第三方无法窃听.
- 身份认证. 配备身份证书, 防止身份被冒充. 即使被 DNS 劫持到了第三方站点, 也会提醒用户有可能被劫持
- 数据完整性. 防止内容被第三方冒充或者篡改. 具有校验机制, 一旦被篡改, 通信双方会立刻发现.
说了这么多, https 到底做了什么? 结合以下流程图, 讲解一次 https 请求都发生了什么:
1, 客户端发起 https 请求
2, 服务端的配置
一般需要向权威机构申请一个证书(也可以自己制作, 这个会在之后的中间人攻击中讲到, 区别就是自己颁发的证书需要客户端验证通过, 才可以继续访问, 而使用受信任的公司申请的证书则不会提示), 证书会生成 RSA 加密使用的一对公钥 A 和私钥 B.
3, 传送证书
这个证书主要内容是公钥 A, 也包含了其他信息, 如证书的颁发机构, 过期时间等等.
4, 客户端解析证书
由客户端的 TLS 来完成的, 主要是验证公钥 A 是否有效, 比如颁发机构, 过期时间等等, 如果发现异常, 则会弹出一个警告框, 提示证书存在问题. 如果证书没有问题, 那么就生成一个随机值. 之后就进入了不对称加密 (RSA) 的过程, 用证书对该随机值进行加密, 生成私钥 C.
5, 传送加密信息
这部分传送的是用证书加密后的随机值(即私钥 C), 目的就是让服务端得到这个私钥, 后续所有的数据都可以用私钥 C, 进行对称加密和解密.
6, 服务端解密信息
服务端用私钥 B 解密后, 得到了客户端传过来的私钥 C, 到此 RSA 非对称加密的过程结束了.
7, 传输加密后的信息
服务端用私钥 C 加密信息.
8, 客户端解密信息
客户端用之前生成的私钥 C 解密服务端传过来的信息, 于是获取了解密后的内容.
中间人攻击(MITM)
上面的过程, 看起来似乎无懈可击? 并不是, 因为 "人" 才是安全系统中最脆弱的环节.
https 信息的安全, 完全建立在证书可信的基础上, 如果中间人伪造证书怎么办?
黑客自己伪造的证书需要客户端验证通过, 才可以继续访问, 只要客户端验证通过, 那么公钥 A, 私钥 B 和私钥 C 对黑客来说都是透明的, 也有没有数据安全可言了, 所以黑客只要诱导用户安装自己伪造的证书即可, 例如使用各种钓鱼的不可描述网站.
所以即使使用 https 传输明文密码, 也不是绝对安全的. 那怎么样才能保证密码安全呢?
百度的登录方式
抓包看一下百度的登录请求发现, 密码是加密过的:
怎么加密的? 我们发现, 有这么一个关键请求:
response 里有 pubkey, 意味着, 密码使用 RSA 进行加密和解密处理的, 这个请求传输的是公钥. 那么流程是什么样的呢?
查了一下 github 中, 关键词 RSA,star 数最多的 JavaScript 库 jsencrypt, 惊喜的发现, 百度登录的加密方式和使用的函数名都和这个库一致, 那我们是不是可以大胆假设百度整套登录请求时的流程和这个开源库基本一致呢, 那 jsencrypt 的流程又是什么样的呢, 如下图:
加密存储
到这里, 加密传输的过程已经完结了, 现在服务端已经收到了用户真实的密码(解密后的), 那怎么存储这个密码呢?
千万不要用明文存储密码
如果用明文存储密码(不管是存在数据库还是日志中), 一旦数据泄露, 所有用户的密码就毫无保留地暴露在黑客的面前, 开头提到的风险就可能发生, 那我们费半天劲加密传输密码也失去了意义.
用哈希算法加密密码
单向加密算法: 只能从明文生成一个对应的哈希值, 不能反过来根据哈希值得到对应的明文.
常用的给密码加密的算法是几种单向的哈希算法.
经常被大家用来加密的算法有 MD5 和 SHA 系列(如 SHA1,SHA256,SHA384,SHA512 等).
虽然用哈希算法能提高密码存储的安全性, 但还是不够安全.
通常黑客在侵入保存密码的数据库之后, 他会随机猜测一个密码, 生成一个哈希值. 如果该哈希值在数据库中存在, 那么他就猜对了一个用户的密码. 如果没有猜中也没有关系, 他可以再次随机猜测下一个密码进行尝试.
事实上黑客为了提高破解密码的效率, 他们会事先计算大量密码对应的各种哈希算法的哈希值, 并把密码及对应的哈希值存入一个表格中(这种表格通常被称为彩虹表), 在破解密码时只需要到事先准备的彩虹表里匹配即可. 因此现在黑客们破解仅仅只用哈希算法加密过的密码事实上已是不费吹灰之力.
加 "盐" 提高安全性
盐: 一个随机的字符串, 往明文密码里加盐就是把明文密码和一个随机的字符串拼接在一起.
为了应对黑客们用彩虹表破解密码, 我们可以先往明文密码加盐, 然后再对加盐之后的密码用哈希算法加密. 由于盐在密码校验的时候还要用到, 因此通常盐和密码的哈希值是存储在一起的.
采用加盐的哈希算法对密码加密, 要确保要往每个密码里添加随机的唯一的盐, 而不是让所有密码共享一样的盐.
虽然加盐的算法能有效应对彩虹表的破解法, 但它的安全级别并不高, 因为计算哈希值耗时极短, 黑客仍然可以用穷举法来破解, 只是增加了一些耗时.
用 BCrypt 或者 PBKDF2 增加破解的难度
为了应对暴力破解法, 我们需要非常耗时的而不是非常高效的哈希算法. BCrypt 和 PBKDF2 算法应运而生.
这两个算法最大的特点是我们可以通过参数设置重复计算的次数, 重复计算的次数越多耗时越长. 如果计算一个哈希值需要耗时 1 秒甚至更多, 那么黑客们采用暴利法破解密码将几乎不再可能. 破解一个 6 位纯数字密码需要耗时 11.5 天, 更不要说高安全级别的密码了.
总结
如果我们想要尽可能保证用户的信息安全, 我们需要做以下的工作
- 使用 https 请求
- 利用 RSA 加密密码并传输数据
- 用 BCrypt 或者 PBKDF2 单向加密, 并存储
来源: http://stor.51cto.com/art/201805/574936.htm