这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
1 为什么要加密?
密码不能以明文的形式保存到数据库,否则数据库泄露时用户的密码就会被攻击者获知,对用户的数据隐私造成更大的损失。
常用的加密措施有 MD5 和 SHA 系列算法。
在Go中,基于MD5算法实现密码加密的方法:
import (
"fmt"
"crypto/md5"
)
func PasswordEncryption(password string) {
h := md5.New()
if _, err := io.WriteString(h, password); err != nil {
return err
}
encryptedPwd := fmt.Sprintf("%x", h.Sum(nil))
}
将加密后的密码存储在数据库中。
密码验证时,对明文密码进行相同的加密操作,再与数据库中的密文对比即可。
2 为什么要加盐?
虽然上述加密算法的不可逆特性能够保证攻击者不能通过解密的方式获取到用户的明文密码信息,但是攻击者仍可通过“彩虹表攻击”获取明文密码。
彩虹表(rainbow table)是一个用于加密散列函数逆运算的预先计算好的表,常用于破解机密过的密码散列。查找表常常用于包含有限字符固定长度纯文本密码的加密。(更多相关内容请参照:彩虹表 rainbow-tables)
彩虹表攻击能够成功的原因在于
- 应用中,加密密码的算法是公开可知的
- 用户习惯于使用诸如生日、姓名缩写等方便记忆的密码,而两段相同的密码经过加密得到的密文也是相同的
因此,我们可以通过增加用户密码随机性的方式,来降低彩虹表攻击的成功率。具体的做法,在对密码加密之前,由服务为用户密码添加一段随机生成的字符,这段随机生成的字符即为“盐”(salt)。这样,即便相同的密码,因为添加的盐不同,最终的密文也不会相同,极大地提高了用户密码的私密性。
3 Go语言实现密码加盐存储
实现
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func PasswordEncryption(password string) string {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hashedPassword)
}
func PasswordValidation(password, hashedPassword string) error {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
if err != nil {
return err
}
return nil
}
func main() {
password := "bravozyzlovefish"
// encryption
hashedPassword := PasswordEncryption(password)
// validation
err := PasswordValidation(password, hashedPassword)
if err != nil {
fmt.Println(err)
}
}
密码串解析
假如生成的密码串为
$2a$10$ESkb/bwSyISLgq1bOH0C2utXdb.hcH9oBQD1hUnfDOzm4bMKK6EX2
$ 为分隔符
2a bcrypt加密版本号
10 Cost值
ESkb/bwSyISLgq1bOH0C2utXdb 盐
hcH9oBQD1hUnfDOzm4bMKK6EX2 密码密文
为增加保护程度,在时间允许的前提下,可以重复多次进行加盐加密。