扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
  • Go语言Web编程

    • 课程介绍
  • Web基础

  • 表单

  • 访问数据库

  • session和数据存储

  • 文本处理

  • Web服务

  • 安全与加密

    • 第1节:安全与加密
    • 第2节:预防CSRF攻击
    • 第3节:确保输入过滤
    • 第4节:避免 XSS 攻击
    • 第5节:避免 SQL 注入
    • 第6节:存储密码
    • 第7节:加密和解密数据
    • 第8节:小结
  • 国际化和本地化

  • 错误处理,调试和测试

  • 部署与维护

  • 如何设计一个Web框架

  • 扩展Web框架

扫码订阅《 》或入驻星球,即可阅读文章!

第6节:存储密码


ASTA谢

过去一段时间以来, 许多的网站遭遇用户密码数据泄露事件, 这其中包括顶级的互联网企业 – Linkedin, 国内诸如 CSDN,该事件横扫整个国内互联网,随后又爆出多玩游戏 800 万用户资料被泄露,另有传言人人网、开心网、天涯社区、世纪佳缘、百合网等社区都有可能成为黑客下一个目标。层出不穷的类似事件给用户的网上生活造成巨大的影响,人人自危,因为人们往往习惯在不同网站使用相同的密码,所以一家“暴库”,全部遭殃。

那么我们作为一个 Web 应用开发者,在选择密码存储方案时, 容易掉入哪些陷阱, 以及如何避免这些陷阱?

# 普通方案

目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要 (digest) 恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括 SHA-256, SHA-1, MD5 等。

Go 语言对这三种加密算法的实现如下所示:


// import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))

// import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))

// import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

单向哈希有两个特性:

  • 1)同一个密码进行单向哈希,得到的总是唯一确定的摘要。
  • 2)计算速度快。随着技术进步,一秒钟能够完成数十亿次单向哈希计算。

结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为 rainbow table。

因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。

# 进阶方案

通过上面介绍我们知道黑客可以用 rainbow table 来破解哈希后的密码,很大程度上是因为加密时使用的哈希算法是公开的。如果黑客不知道加密的哈希算法是什么,那他也就无从下手了。

一个直接的解决办法是,自己设计一个哈希算法。然而,一个好的哈希算法是很难设计的——既要避免碰撞,又不能有明显的规律,做到这两点要比想象中的要困难很多。因此实际应用中更多的是利用已有的哈希算法进行多次哈希。

但是单纯的多次哈希,依然阻挡不住黑客。两次 MD5、三次 MD5之类的方法,我们能想到,黑客自然也能想到。特别是对于一些开源代码,这样哈希更是相当于直接把算法告诉了黑客。

没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是,先将用户输入的密码进行一次 MD5(或其它哈希算法)加密;将得到的 MD5 值前后加上一些只有管理员自己知道的随机串,再进行一次 MD5 加密。这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。


// import "crypto/md5"
// 假设用户名 abc,密码 123456
h := md5.New()
io.WriteString(h, "需要加密的密码")

// pwmd5 等于 e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))

// 指定两个 salt: salt1 = @#$%   salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"

// salt1 + 用户名 + salt2 + MD5 拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)

last :=fmt.Sprintf("%x", h.Sum(nil))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在两个 salt 没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。

# 专家方案

上面的进阶方案在几年前也许是足够安全的方案,因为攻击者没有足够的资源建立这么多的 rainbow table。 但是,时至今日,因为并行计算能力的提升,这种攻击已经完全可行。

怎么解决这个问题呢?只要时间与资源允许,没有破译不了的密码,所以方案是:故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的 rainbow table。

这类方案有一个特点,算法中都有个因子,用于指明计算密码摘要所需要的资源和时间,也就是计算强度。计算强度越大,攻击者建立 rainbow table 越困难,以至于不可继续。

这里推荐 scrypt 方案,scrypt 是由著名的 FreeBSD 黑客 Colin Percival 为他的备份服务 Tarsnap 开发的。

目前 Go 语言里面支持的库 http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt (无法使用)


dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) (scrypt无法使用)
1
2

通过上面的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。

# 总结

看到这里,如果你产生了危机感,那么就行动起来:

  • 1)如果你是普通用户,那么我们建议使用 LastPass 进行密码存储和生成,对不同的网站使用不同的密码;
  • 2)如果你是开发人员, 那么我们强烈建议你采用专家方案进行密码存储。
  • 普通方案
  • 进阶方案
  • 专家方案
  • 总结