go语言中的数据哈希
哈希算法
哈希函数的基本特征
- 输入可以是任意长度。
- 输出是固定长度。
- 根据输入很容易计算出输出。
- 根据输出很难计算出输入(几乎不可能)。
- 两个不同的输入几乎不可能得到相同的输出。
SHA
SHA(Secure Hash Algorithm) 安全散列算法,是一系列密码散列函数,有多个不同安全等级的版本:SHA-1,SHA-224,SHA-256,SHA-384,SHA-512。
其作用是防伪装,防窜扰,保证信息的合法性和完整性。
const (
MD4 Hash = 1 + iota // import golang.org/x/crypto/md4
MD5 // import crypto/md5
SHA1 // import crypto/sha1
SHA224 // import crypto/sha256
SHA256 // import crypto/sha256
SHA384 // import crypto/sha512
SHA512 // import crypto/sha512
MD5SHA1 // no implementation; MD5+SHA1 used for TLS RSA
RIPEMD160 // import golang.org/x/crypto/ripemd160
SHA3_224 // import golang.org/x/crypto/sha3
SHA3_256 // import golang.org/x/crypto/sha3
SHA3_384 // import golang.org/x/crypto/sha3
SHA3_512 // import golang.org/x/crypto/sha3
SHA512_224 // import crypto/sha512
SHA512_256 // import crypto/sha512
BLAKE2s_256 // import golang.org/x/crypto/blake2s
BLAKE2b_256 // import golang.org/x/crypto/blake2b
BLAKE2b_384 // import golang.org/x/crypto/blake2b
BLAKE2b_512 // import golang.org/x/crypto/blake2b
)
这里以sha-1为例
sha-1算法大致过程
- 填充。使得数据长度对512求余的结果为448。
- 在信息摘要后面附加64bit,表示原始信息摘要的长度。
- 初始化h0到h4,每个h都是32位。
- h0到h4历经80轮复杂的变换。
- 把h0到h4拼接起来,构成160位,返回。
package main
import (
"crypto/sha1"
"encoding/hex"
"fmt"
)
func Sha1(data string) string {
sha1 := sha1.New()
sha1.Write([]byte(data))
return hex.EncodeToString(sha1.Sum(nil))
}
func main() {
data := "因为我们没有什么不同"
fmt.Printf("SHA-1: %s\n", Sha1(data))
}
//SHA-1: 0cfea402af137e3793a6c8a80152b5ab74ba380b
MD5
MD5(Message-Digest Algorithm 5)信息-摘要算法5,算法流程跟SHA-1大体相似。MD5的输出是128位,比SHA-1短了32位。MD5相对易受密码分析的攻击,运算速度比SHA-1快。
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
)
func Md5(data string) string {
md5 := md5.New()
md5.Write([]byte(data))
return hex.EncodeToString(md5.Sum(nil))
}
func main() {
data := "因为我们没有什么不同"
fmt.Printf("MD5: %s\n", Md5(data))
}
哈希函数的应用场景
- 用户密码的存储。
- 文件上传/下载完整性校验。
- mysql大字段的快速对比。
HMac
Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥, 只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法
package main
import (
"crypto/hmac"
"fmt"
"io"
)
// 使用sha1的Hmac散列算法
func hmacHash(msg string, key string) (hashData []byte) {
k := []byte(key)
mac := hmac.New(sha1.New, k)
io.WriteString(mac, msg)
hashData = mac.Sum(nil)
return
}
func main() {
msg := "This is the message to hash!"
// hmac
hmacData := hmacHash(msg, "The key string!")
fmt.Printf("HMAC: %x\n", hmacData)
}
你觉得上面这些算法好吗?
如果你使用的是MD5算法来加密你的口令,你的口令长度只有小写字母再加上数字,假设口令的长度是6位,那么在目前一台比较新一点的PC机上,穷举所有的口令只需要40秒钟。几乎有90%以上的用户只用小写字母和数字来组织其口令。对于6位长度的密码只需要最多40秒就可以破解了,这可能会吓到你
因为MD5,SHA的算法速度太快了, 当用于消息摘要,还是很不错的, 但是用于password hash就不行了
,有没有适合password hash的算法喃?
bcrypt
bcrypt是这样的一个算法,因为它很慢,对于计算机来说,其慢得有点BT了,但却慢得刚刚好!对于验证用户口令来说是不慢的,对于穷举用户口令来说,其会让那些计算机变得如同蜗牛一样
bcrypt采用了一系列各种不同的Blowfish加密算法,并引入了一个work factor,这个工作因子可以让你决定这个算法的代价有多大。因为这些,这个算法不会因为计算机CPU处理速度变快了,而导致算法的时间会缩短了。因为,你可以增加work factor来把其性能降下来
同时bcrypt也是一种加盐的Hash方法,MD5 Hash时候,同一个报文经过hash的时候生成的是同一个hash值,在大数据的情况下,有些经过md5 hash的方法将会被破解(碰撞).使用BCrypt进行加密,同一个密码每次生成的hash值都是不相同的。每次加密的时候首先会生成一个随机数就是盐,之后将这个随机数与报文进行hash,得到 一个hash值
那一个被bcrypt hash过后的结果长啥样喃:
Bcrypt有四个变量:
- saltRounds: 正数,代表hash杂凑次数,数值越高越安全,默认10次。
- myPassword: 明文密码字符串。
- salt: 盐,一个128bits随机字符串,22字符
- myHash: 经过明文密码password和盐salt进行hash,个人的理解是默认10次下 ,循环加盐hash10次,得到myHash
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func main() {
password := "secret"
hash, _ := HashPassword(password) // ignore error for the sake of simplicity
fmt.Println("Password:", password)
fmt.Println("Hash: ", hash)
match := CheckPasswordHash(password, hash)
fmt.Println("Match: ", match)
}
应用
用户名密码校验
密码校验则是一个很常见的问题, 当我们设计用户中心时,是一个必不可少的功能, 为了安全,我们都不会保存用户的明文密码, 最好的方式就是保存为Hash, 这样即使是数据泄露了,也不会导致用户的明文密码泄露(hash的过程是不可逆的)
需求:
- 用户可以修改密码
- 修改密码时,禁止使用最近已经使用过的密码
- 能校验密码
// NewHashedPassword 生产hash后的密码对象
func NewHashedPassword(password string) (*Password, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
return nil, err
}
return &Password{
Password: string(bytes),
CreateAt: ftime.Now().Timestamp(),
UpdateAt: ftime.Now().Timestamp(),
}, nil
}
type Password struct {
// hash过后的密码
Password string
// 密码创建时间
CreateAt int64
// 密码更新时间
UpdateAt int64
// 密码需要被重置
NeedReset bool
// 需要重置的原因
ResetReason string
// 历史密码
History []string
// 是否过期
IsExpired bool
}
// Update 更新密码
func (p *Password) Update(new *Password, maxHistory uint, needReset bool) {
p.rotaryHistory(maxHistory)
p.Password = new.Password
p.NeedReset = needReset
p.UpdateAt = ftime.Now().Timestamp()
if !needReset {
p.ResetReason = ""
}
}
// IsHistory 检测是否是历史密码
func (p *Password) IsHistory(password string) bool {
for _, pass := range p.History {
err := bcrypt.CompareHashAndPassword([]byte(pass), []byte(password))
if err == nil {
return true
}
}
return false
}
// HistoryCount 保存了几个历史密码
func (p *Password) HistoryCount() int {
return len(p.History)
}
func (p *Password) rotaryHistory(maxHistory uint) {
if uint(p.HistoryCount()) < maxHistory {
p.History = append(p.History, p.Password)
} else {
remainHistry := p.History[:maxHistory]
p.History = []string{p.Password}
p.History = append(p.History, remainHistry...)
}
}
// CheckPassword 判断password 是否正确
func (p *Password) CheckPassword(password string) error {
err := bcrypt.CompareHashAndPassword([]byte(p.Password), []byte(password))
if err != nil {
return exception.NewUnauthorized("user or password not connrect")
}
return nil
}
总结
- 已经被破解了的Hash
- 速度较快的Hash,适于与内容摘要
- 加盐Hash
- 速度较慢的Hash,适用于密码保存
本文来自博客园,作者:厚礼蝎,转载请注明原文链接:https://www.cnblogs.com/guangdelw/p/17365866.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!