Dawn's Blogs

分享技术 记录成长

0%

凤凰架构笔记 (6) 安全之授权和加密

授权

授权的过程就是解决这样一个问题:谁拥有什么权限去操作哪些资源。

访问控制模型

可以通过访问控制模型来解决授权问题,有常见的三种访问控制模型:DAC(自主访问控制)、MAC(强制访问控制)、RBAC(基于角色的访问控制)。

DAC

自主访问控制就是资源的所有者,规定谁有权限访问它们

Linux 文件的权限就是 DAC 的一种实现方式。

MAC

主体和客体都有一个固定属性,系统用该安全属性来决定一个主体是否可以访问某个客体。

保护敏感信息一般用 MAC,需要用户提供灵活的保护,更多的考虑共享信息时,使用 DAC。

RBAC

将权限从用户身上剥离,将权限分配在角色上,再给用户分配角色,这是最常用的访问控制模型。

RBAC 有以下特点:

  • 通过角色实现用户和权限的解耦,具有很高的灵活性
  • 同时还天然的满足最小分配原则。在 RBAC 模型中,角色拥有许可的数量是根据完成该角色工作职责所需的最小权限来赋予的。
  • 不同的角色之间可以有继承性,也可以有互斥性(角色的互斥约束可限制同一用户只能分配到一组互斥角色集合中至多一个角色)。

OAuth2

OAuth2 是面向于解决第三方应用的认证授权协议,以 token 代替密码访问资源服务器。整个授权的流程如下图所示:

image-20230609155251566

加密

加密分为端上的加密和传输链路的加密。

链路上的加密

链路上的加密一般的通用方法是:使用非对称加密交换对称密钥,使用对称密钥对通信内容进行加密。

加密保证了信息的机密性,还需要保证信息的完整性。此时需要数字签名,使用私钥对消息摘要进行加密,就可以保证信息的完整性,同时也认证了消息发送者的身份。

但是,不论是加密还是签名时使用公钥,都不能保证公钥在网络传输过程中被中间人篡改。也就是说公钥是不可信任的。

数字证书

所以引入了 CA(数字证书认证中心),CA 作为受信任的第三方,承担公钥体系中公钥的合法性检验的责任。公钥存在于数字证书中,而数字证书由 CA 进行签发和验证。

又有新的问题出现了,因为向 CA 确认认证证书时使用网络,网络上中间人攻击依然会使得 CA 不可靠,那么如何使得 CA 是可靠的呢?

因为 CA 是有限的,所以解决方法就是浏览器中已经预置了权威(根) CA 中心的证书。使得我们能够在不依靠网络的前提下,使用权威 CA 证书的公钥信息对其签发的数字证书进行确认。

TLS

HTTPS 是基于 TLS 进行的安全 HTTP 协议,下面介绍 TLS 协议。

TLS 在传输数据之前会进行握手,TLS 握手的作用就是交换证书(公钥)、对称密钥等信息。

  1. 客户端请求:会给服务器提供以下信息,支持 TLS 的版本、客户端生成的随机数、支持的加密算法等。
  2. 服务器响应:如果服务器接受客户端的请求,则向客户端回复响应。服务器同样会给客户端提供以下信息:确认使用的 TLS 版本、服务器生成的随机数、服务器选定的加密算法等。如果协商出的加密算法需要证书,则也会发送自己的证书。
  3. 客户端确认:此时通信采用协商出的加密方式进行,客户端向服务器发送的信息包括:客户端的证书(可选)、以服务器公钥加密的对称密钥(对称密钥通过两个随机数计算出)。
  4. 服务器确认:服务器通过私钥解密得到对称密钥,此后所有的通信通过对称密钥以对称加密的形式传输。

TLS 握手完成后,安全连接已经建立,之后的通信采用协商出的对称加密算法进行传输。因为 TLS 位于 TCP 和 HTTP 之间所以上层协议 HTTP 是对其无感知的。

端上的加密

当信息内容中有密码等敏感信息时,需要采用端上加密。端上的加密分为客户端加密和服务器端的加密:

  • 客户端的加密主要防止密码在传输过程中泄漏
  • 服务器端的加密主要是为了安全的存储密码

采用 HTTPS 时需要采用客户端加密吗?

需要。因为 HTTPS 也不一定是安全的,如果客户端信任了中间人的证书,那么中间人就能与客户端、服务器之间建立两个 HTTPS 连接,直接的看到 HTTP 传输明文内容。

普通方案:哈希

目前利用最多的方案就是对明文密码进行哈希之后,进行存储。常用的单向哈希算法包括 SHA-256, SHA-1, MD5 等。

缺点:考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合(彩虹表),然后与数据库中的摘要进行比对即可获得对应的密码。

进阶方案:哈希+盐

可以采用加盐的方式来存储密码,常用的方式:

  • 对用户的明文密码进行一次哈希运算
  • 将得到的摘要加上随机串(盐),这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。
  • 再进行一次哈希运算后,放入数据库中存储起来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 假设用户名 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))

专家方案:慢哈希

慢哈希是指执行时间是可以调节的哈希函数,通常是以控制调用次数来实现的。

故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的 rainbow table

由于慢哈希算法占用大量处理器资源,不建议在服务器端采用

可以在客户端采用慢哈希服务器端采用哈希+盐的方式。

慢哈希算法包括但不限于 BCrypt 和 Scrypt,分别在 Golang 的 golang.org/x/crypto/bcryptgolang.org/x/crypto/scrypt 已经支持。