前言
在系统开发中,为了保护用户的密码安全,不被恶意窃取,我们需要对密码进行加密和验证。本文将介绍Go语言如何使用bcrypt库来实现安全的密码加密。
首先,为什么要加密?试想,如果服务器的数据库被盗,那攻击者就可以拿着明文的密码进行登入篡改、信息泄露等。
一些基础
- bcrypt: 是专门为密码存储而设计的算法,基于Blowfish加密算法变形而来,由Niels Provos和David Mazières发表于1999年的USENIX。
- SALT值(盐值): 属于随机值.用户注册时,系统用来和用户密码进行组合而生成的随机数值,称作salt值,通称为加盐值。
- 彩虹表: 预先计算出所有可能的hash值,比如6位密码的所有hash值(大小写加数字,只有62^6个)
加密过程与原理
当用户首次提供密码时(通常是注册时),由系统自动添加随机生成的salt值,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的加盐值,然后散列,再比较散列值,以确定密码是否正确。
为用户密码添加Salt值,使得加密的得到的密文更加冷僻,不宜查询。即使黑客有密文查询到的值,也是加了salt值的密码,而非用户设置的密码。salt值是随机生成的一组字符串,可以包括随机的大小写字母、数字、字符,位数可以根据要求而不一样。
bcrypt
bcrypt 包实现了 Provos 和 Mazières 的 bcrypt 自适应哈希算法。
加密过程与源码
- 生成盐值:在对密码进行哈希之前,需要生成一个随机的盐值。 源码参考:
go
unencodedSalt := make([]byte, maxSaltSize)
_, err = io.ReadFull(rand.Reader, unencodedSalt)
if err != nil {
return nil, err
}
- 使用生成的盐值和密码进行哈希运算。源码参考:
go
p.salt = base64Encode(unencodedSalt)
hash, err := bcrypt(password, p.cost, p.salt)
if err != nil {
return nil, err
}
- 可选修改迭代次数来增加哈希函数的计算复杂度, 增加迭代次数会导致哈希函数的计算时间增加,从而增加攻击者破解密码的难度。源码参考:
go
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
csalt, err := base64Decode(salt)
if err != nil {
return nil, err
}
// Bug compatibility with C bcrypt implementations. They use the trailing
// NULL in the key string during expansion.
// We copy the key to prevent changing the underlying array.
ckey := append(key[:len(key):len(key)], 0)
c, err := blowfish.NewSaltedCipher(ckey, csalt)
if err != nil {
return nil, err
}
var i, rounds uint64
rounds = 1 << cost
for i = 0; i < rounds; i++ {
blowfish.ExpandKey(ckey, c)
blowfish.ExpandKey(csalt, c)
}
return c, nil
}
使用Bcrypt进行密码加密的一个例子:
first 安装
arduino
go get golang.org/x/crypto/bcrypt
Example:
go
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
var (
password = "123456"
)
hashedPassword, err := HashPassword(password)
if err != nil {
fmt.Println("Error hashing password:", err)
return
}
fmt.Println("未加密:", password)
fmt.Println("加密后:", hashedPassword)
if err := CheckPasswordHash("202020", hashedPassword); err != nil {
fmt.Println("密码不匹配:", err)
} else {
fmt.Println("密码匹配")
}
if err := CheckPasswordHash(password, hashedPassword); err != nil {
fmt.Println("密码不匹配:", err)
} else {
fmt.Println("密码匹配!")
}
}
func CheckPasswordHash(password, hash string) error {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 10)
return string(bytes), err
}
执行输出:
js
未加密: 123456
加密后: $2a$10$EvmU.zY9qGx7QntUFG9Qf.G6SRytkf8GMtFMsn1IYjnIMbZ6gUd4O
密码不匹配: crypto/bcrypt: hashedPassword is not the hash of the given password
密码匹配!
注意:
- bcrypt接受最长密码的长度 72 字节,在使用时注意密码长度
- 迭代次数 Cost 如果给定的成本小于 MinCost(4),则成本将改为 DefaultCos(10)
小结
为了保护用户的密码不被恶意攻击者窃取,如服务器数据库被盗、 碰撞、彩虹表等,可以通过使用Bcrypt算法加密。同时介绍了加密的过程和Go Bcrypt源码,包括生成随机盐值、安全哈希值和修改迭代次数。文中通过一个示例,演示了如何使用进行加密、校验的过程。
参考
- Documentation pkg.go.dev/golang.org/...
- 密码安全技术 zhuanlan.zhihu.com/p/379281284
- Gin 框架之用户密码加密 juejin.cn/post/732489...
- 为什么加盐值再加密能降低彩虹表攻击? www.zhihu.com/question/60...