如何进行项目安全设计

数据库数据安全

在这里我们讨论如何对用户输入的数据(密码)进行加密,避免成为明文存入数据库中

加密解密在实际开发中应用比较广泛,常用加解密分为:“对称式”、“非对称式”和”数字签名“。

对称式:对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。具体算法主要有DES算法3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

**非对称加密(公钥加密)**:指加密和解密使用不同密钥的加密算法,也称为公私钥加密。具体算法主要有RSAElgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

数字签名:数字签名是非对称密钥加密技术数字摘要技术的应用。如MD5、SHA1、HMAC等, 主要用于验证,防止信息被修改, 如:文件校验、数字签名、鉴权协议;

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,

hmac算法是加密的hash算法,它需要一个hash算法(比如sha256md5等)和一个密匙key,在hash计算的过程中将密匙key混入,产生一个和原来hash算法相同位数的hash值。
在大多数情况下,我们甚至可以将hamc算法看做是加盐的hash算法(加盐是将一个随机字符串放在需要加密的密文前面或者后面,然后对这个拼接后的密文进行加密得到hash值)。
但它们的加密原理肯定不一样,虽然达到的效果是一样的,都是对密文混入一个第三方值,然后得到一个hash值。

1
2
3
4
5
6
7
8
9
10
11
//key随意设置 data 要加密数据
func Hmac(key, data string) string {
hash:= hmac.New(md5.New, []byte(key)) // 创建对应的md5哈希加密算法
hash.Write([]byte(data))
return hex.EncodeToString(hash.Sum([]byte("")))
}
func HmacSha256(key, data string) string {
hash:= hmac.New(sha256.New, []byte(key)) //创建对应的sha256哈希加密算法
hash.Write([]byte(data))
return hex.EncodeToString(hash.Sum([]byte("")))
}

只要是hash函数(sha1,sha256,sha512,md5等算法,都可以hmac加密哈希算法)。

  • 再来看hmac包的另一个方法:
1
func Equal(mac1, mac2 []byte) bool

/*
比较两个hash值是否相同,而不会泄露对比时间信息。
(以避免时间侧信道攻击:指通过计算比较hash值花费的时间的长短来获取密码的信息,用于密码破解),
这个方法只要传入的数据长短相同,那么比较的时间就是一样的。
举个栗子:比较[1,2,3],[1,2,2]与[1,2,3],[3,4,5],这个方法执行的时间都是一样的,
比较的时候不会因为后面一组数据第一个值不同就立即返回而使得后面一组数据的比较时间比前面一组的比较时间段。
这样做是为了防止黑客用暴力破解的方式不断收集每个hash与正确hash的比较时间,从而来逐步确定正确hash的值,从而达到破解hash密文的目的。
*/

  • 下面是用hmac包比较密文与hash是否相同的标准操作
1
2
3
4
5
6
func CheckMAC(msg, msgMAC, key []byte) bool {
mac := hmac.New(sha256.New,key) // 创建hash加密算法
mac.Write(msg) // 写入数据
expectedMAC := mac.Sum(nil) //获取加密后的hash
return hmac.Equal(expectedMAC,msgMAC) // 比较预期的hash和实际的hash
}

越权问题

①水平越权问题

由于此次项目只存在用户这一种权限级别,不存在管理员等其他级别,因此不存在水平越权问题

②垂直越权问题.

客户端访问,只会有登录未登录两种状态,这两种情况统一由token提供安全保障就好了

Token鉴权

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

原先的jwt-go仓库已经不再维护,迁移到了github.com/golang-jwt/jwt/v4

1
go get github.com/golang-jwt/jwt/v4
  • 由三部分组成

    • Header 头部

      • alg:签名的算法(algorithm),默认是HMAC SHA256(写成 HS256
    • typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

    • PayLoad 有效载荷(这也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。)

      • iss (issuer):签发人
      • exp (expiration time):过期时间
      • sub (subject):主题
      • aud (audience):受众
      • nbf (Not Before):生效时间
      • iat (Issued At):签发时间
      • jti (JWT ID):编号
    • Signature 签名( 对前两部分的签名,防止数据篡改 )

      • 首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定

        的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名

1
2
3
HMACSHA256(
base64UrlEncode(header) + "." +base64UrlEncode(payload),
secret)
  • 使用是会连接上面的3部分

    • “Token” : Header+”.”+PayLoad+”.”+Signature

Token加密解密

定义claimsserect

1
2
3
4
5
6
type MyClaims struct {
Phone string `json:"phone"`
jwt.RegisteredClaims // 注意!这是jwt-go的v4版本新增的,原先是jwt.StandardClaims
}

var MySecret = []byte("手写的从前") // 定义secret,后面会用到

生成token

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这里传入的是手机号,因为我项目登陆用的是手机号和密码
func MakeToken(phone string) (tokenString string, err error) {
claim := MyClaims{
Phone: phone,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Hour * time.Duration(1))), // 过期时间3小时
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间
}}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) // 使用HS256算法
tokenString, err = token.SignedString(MySecret)
return tokenString, err
}

解析token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func Secret() jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
return []byte("手写的从前"), nil // 这是我的secret
}
}

func ParseToken(tokenss string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenss, &MyClaims{}, Secret())
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, errors.New("that's not even a token")
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, errors.New("token is expired")
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, errors.New("token not active yet")
} else {
return nil, errors.New("couldn't handle this token")
}
}
}
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("couldn't handle this token")
}

参考资料

[golang标准库-crypto/hmac(加密哈希算法][https://itpika.com/2019/06/28/go/library-crypto-hmac/]

[Go语言使用golang-jwt/jwt/v4进行JWT鉴权][http://t.csdn.cn/f3C3f]