【ginny 系列】 基于Go的web后台开发,加密和令牌

加密和令牌

现在我们回头讨论业务方面的问题,关于敏感信息加密的问题。

什么是敏感信息呢?举个例子,密码就是十分敏感的信息,而用户名则没有那么重要。密码对于一个账户而言是至关重要的,是获取其他敏感信息的“钥匙”。

在我们之前撰写的简单接口中,并未对密码有过任何的加密处理,全部都是明文传输,这是相当危险的行为!所以从现在开始我们开始进行对于信息的加密。

5.1 对称加密、非对称加密和散列

对称加密

       秘钥
     ------>
信息          密文
     <------

对称加密,顾名思义,可以通过一个秘钥将生成的密文通过一定的规则还原成原文。

常见的对称加密算法:

DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6和AES

非对称加密

       秘钥1
     ------>
信息          密文
     <------
       秘钥2

非对称加密在知道对称加密的情况下就很好理解了,非对称加密就是加密过程和解密过程使用两套秘钥的加密。

对称加密和非对称加密都是加密,也就是可以完成从密文到原文的转换,而散列则是只能从原文到密文,却无法从密文完成到明文的转换

对称加密和非对称加密

散列

散列原意是为了完成一种压缩映射(事实上散列并不是加密,但是我会使用加密这个词语来表述加密的过程),一般压缩空间比原空间要小得多,因此会出现冲突的情况(也就是两个值的散列值是相同的)

但是我们在使用散列时并不是为了创建映射,而是为了让原数据失去本身的特征,这样就可以使敏感信息无法通过密文复原。但是却又可以用来验证原文的信息。

散列:

     散列算法
     ------>
信息          密文
     <--X---

我们就是通过使用存储密码的散列值来达到对于敏感信息的加密的。

5.2 盐值

我们之前提到过,散列一定会存在冲突,举个很简单的例子:我们对一个字符串的散列方式是将它们的ASCII码值都加起来。(这个过程一般用一个函数来描述,称为哈希函数HashFunction)

func HashFunc(s string) (value int) {
    for _, r := range s {
        value += int(r)
    }
    return
}

然后我们使用最简单的样例去进行测试: “CCC”, “BCD”, “ACE”

很显然,可预见范围内这三次调用的结果都是201,这就是冲突,三个明明完全不同的值,散列后的结果确实一样的。

这种冲突的优化一般有三种,这里不再赘述,但是这催生出了一些著名的散列算法:

MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1

这些散列算法很好,能够保证冲突的尽可能少的发生。但是也存在一个问题:这些算法是公开的,这就有一个很大的问题存在了。

既然这个算法保证了低冲突率,那么就有人直接把所有的字符串按照一种散列算法直接暴力列出结果,到时候再用结果倒推来源,人称彩虹表,这种表动辄上百G,因此如果单纯使用一种散列算法依然无法保证我们服务的安全性。

所以我们要在我们的信息中再加一点不稳定因素:盐值,我们在记录散列值之前,在原数据中撒点盐,这样就变成了和原数据不同的数据,然后我们再记录散列值和盐值,这样我们可以校验我们数据,外界又无法使用彩虹表这种暴力破解手段。

在Go语言中有crypto包,包内是一些常见的加密和散列方法,我们的例子就使用sha256

// ./util/hash.go

package util

import (
    "io"
    "crypto/sha256"
    "crypto/rand"
    "encoding/hex"
)

func PasswordHash(password string) (string, string, error) {
    h := sha256.New()
    h.Write([]byte(password))
    salt, err := generateSalt(6)
    if err != nil {
        return "", "", err
    }
    h.Write(salt)
    passwordHash := hex.EncodeToString(h.Sum(nil))
    return passwordHash, hex.EncodeToString(salt), nil
}

func generateSalt(length int) ([]byte, error) {
    salt := make([]byte, length)
    _, err := io.ReadFull(rand.Reader, salt)
    if err != nil {
        return []byte{}, err
    }
    return salt, nil
}

然后让我们测试一下:

passwordHash, salt, _ := util.PasswordHash("testPassword6")
fmt.Println(passwordHash, salt)
// a07505c3f37b147580c94080edadb39503114b18301fbadd298761aa72b3b73c 
// b2de001c7382
// <nil>

看上去得到了不错的结果,然后我们试试能不能用这个散列值和盐值去验证密码呢?

func CheckPassword(password, passwordHash, salt string) bool {
    h := sha256.New()
    h.Write([]byte(password))
    saltBytes, err := hex.DecodeString(salt)
    if err != nil {
        return false
    }
    h.Write(saltBytes)
    return hex.EncodeToString(h.Sum(nil)) == passwordHash
}

然后我们再继续做一个小测试:

fmt.Println(util.CheckPassword("testPassword6", passwordHash, salt))
// true

这样的话,我们就完成了对于密码的加盐散列。

5.3 改变数据库的字段

我们以前的用户表的结构是这样的:

CREATE TABLE `users` (
    `id`       int unsigned   NOT NULL AUTO_INCREMENT,
    `username` varchar(64)    NOT NULL,
    `password` varchar(64)    NOT NULL,
  
    PRIMARY KEY (`id`)
)

现在我们要改成这样:

CREATE TABLE `users` (
    `id`            int unsigned   NOT NULL AUTO_INCREMENT,
    `username`      varchar(64)    NOT NULL,
    `password_hash` varchar(128)   NOT NULL,
    `salt`          varchar(32)    NOT NULL,
  
    PRIMARY KEY (`id`)
)

可以直接DROP掉整个表然后新建,也可以使用ALTER语句来更改结构。

提示:可以在本教程的附属仓库 https://github.com/ShiinaOrez/ginny.git 中通过标签``v0.2.4``来查看这个示例。

5.4 令牌

我们现在实现了一个很小的用户系统,它可以注册,登录,可以忘记和修改密码。但是我们的目标不仅如此,我们要开发一个博客网站。这就带来了很多问题:我们的接口如何知道请求接口的是用户本人?

举个例子:现在有一个删除博客的接口,我们显然不能每次都发送密码来确认身份,但是接口是会被爬取的。如果所有人都可以调用这个接口,那么只需要写一个简单的爬虫,就可以删除整个网站上所有的博客。

所以我们需要身份验证!特定的接口有特定的验证机制,所以我们往往会多接受一个参数用来判断调用这个接口的人的身份,而这个参数叫做令牌(token)。

令牌相当于一个人进入房间之前的凭证,这个凭证上面不需要写明你的密码,但是却是经由密码验证可以拿到的,也就是说一定意义上,这个令牌是可以代表一个人的身份的。也就是说,如果我们看到了令牌,就相当于是已经验证过账号密码了,作用类似于中国古代的尚方宝剑。面剑如面君。

令牌一般是一个加密过的字符串,以请求头的形式发送给服务端。而解密令牌之后往往可以通过解析JSON的方式来获得多个字段的信息:常见的比如权限,身份,用户的ID等等…

那么我们基本可以得出以下逻辑:

    账户密码          序列化         加密
用户 ------> 用户信息 ------> JSON ------> 令牌
      验证  (struct)       (string)     (string)

     请求头             解密        反序列化           判断
令牌 ------> 服务端接口 ------> JSON ------> 用户信息 ------> 验证成功

在看到这些之后,我想你的思路已经很清晰了,那么我们就开始直接写吧:

// ./util/token.go

package util

import (
    "encoding/json"

)

func GenerateToken() {

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夕Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值