Golang实现密码加密的示例详解
数据库在存储密码时,不能明文存储,需要加密后存储
加密算法有很多种,比如:对称加密,非对称加密,哈希算法,密码派生等
在加密之前,我们需要生成一些随机数,这些随机数称为盐
什么是盐?为什么需要盐?
盐是一种加密算法中的一种参数,它是一个随机数,用于增加破解密码的难度
把密码想象成一盘菜,盐就是调料,不加调料就是原汁原味的菜,调料加多加少,菜口味就不一样了
也就是说,盐的多少,会影响到加密后的结果,如果不使用盐,那么相同的密码,加密后的结果是一样的,这样就很容易被破解
盐的生成方式有很多种,比如:随机数,时间戳等
这里用随机数来举例:
- 定义一个字符串,里面包含了所有可能的字符
- 根据传入的长度,生成一个切片,并用随机数填充
- 遍历这个切片,取出随机数,然后对字符串长度取余,得到一个索引,然后把这个索引对应的字符,放到切片中
- 返回这个切片(这个切片就是盐)
func generateSalt(length int) []byte { const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" salt := make([]byte, length) rand.Read(salt) for key, val := range salt { salt[key] = alphanum[val%byte(len(alphanum))] } return salt }
生成盐之后,我们就可以对密码进行加密了
加密后的密码要使用 hex.EncodeToString
转换成字符串
salt := generateSalt(16) encoded := pbkdf2.Key([]byte("uccs"), salt, 100, 32, sha256.New) encodedPwd := hex.EncodeToString(encodedPwd)
因为这个加密算法是不可逆的,也就说你不能通过加密后的密码,反推出原始密码
那用户输入密码时,如何知道密码是否正确呢?
所以在存储加密后的密码时,还要存储盐和加密算法的参数
这里使用的是 pbkdf2
算法,它的参数有:原始密码,盐,迭代次数,密钥长度,哈希算法
所以最终存在数据的密码是:
pwd := fmt.Sprintf("%s$%s$%d$%d$%s", encodedPwd, salt, 100, 32, "pbkdf2-sha256")
这样在验证密码时,就可以通过这些参数,把用户输入的密码,加密后,再和数据库中存储的密码进行比较,如果相同,就说明密码正确
知识补充
当然除了上面的加密方式,Golang中还有很多加密算法的实现,下面小编就来为大家简单整理一下吧,希望对大家有所帮助
MD5加密
package main import ( "crypto/md5" "encoding/hex" "fmt" "io" ) func main() { h := md5.New() io.WriteString(h, "123456") sum := h.Sum(nil) fmt.Println(hex.EncodeToString(sum[:])) // e10adc3949ba59abbe56e057f20f883e }
凯撒密码加密
package main import ( "fmt" "strings" ) func caesar(r rune, shift int) rune { // Shift character by specified number of places. // ... If beyond range, shift backward or forward. s := int(r) + shift if s > 'z' { return rune(s - 26) } else if s < 'a' { return rune(s + 26) } return rune(s) } func main() { value := "test" fmt.Println(value) // Test the caesar method in a func argument to strings.Map. value2 := strings.Map(func(r rune) rune { return caesar(r, 18) }, value) value3 := strings.Map(func(r rune) rune { return caesar(r, -18) }, value2) fmt.Println(value2, value3) value4 := strings.Map(func(r rune) rune { return caesar(r, 1) }, value) value5 := strings.Map(func(r rune) rune { return caesar(r, -1) }, value4) fmt.Println(value4, value5) value = "exxegoexsrgi" result := strings.Map(func(r rune) rune { return caesar(r, -4) }, value) fmt.Println(value, result) } //输出结果 //test //lwkl test //uftu test //exxegoexsrgi attackatonce
AES对称加密
package main import ( "bytes" "crypto/aes" "fmt" "testing" ) //ECB模式解密 func ECBDecrypt(crypted, key []byte) ([]byte, error) { if !validKey(key) { return nil, fmt.Errorf("秘钥长度错误,当前传入长度为 %d",len(key)) } if len(crypted) < 1 { return nil, fmt.Errorf("源数据长度不能为0") } block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(crypted)%block.BlockSize() != 0 { return nil, fmt.Errorf("源数据长度必须是 %d 的整数倍,当前长度为:%d",block.BlockSize(), len(crypted)) } var dst []byte tmpData := make([]byte, block.BlockSize()) for index := 0; index < len(crypted); index += block.BlockSize() { block.Decrypt(tmpData, crypted[index:index+block.BlockSize()]) dst = append(dst, tmpData...) } dst, err = PKCS5UnPadding(dst) if err != nil { return nil, err } return dst, nil } //ECB模式加密 func ECBEncrypt(src, key []byte) ([]byte, error) { if !validKey(key) { return nil, fmt.Errorf("秘钥长度错误, 当前传入长度为 %d",len(key)) } block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(src) < 1 { return nil, fmt.Errorf("源数据长度不能为0") } src = PKCS5Padding(src, block.BlockSize()) if len(src)%block.BlockSize() != 0 { return nil, fmt.Errorf("源数据长度必须是 %d 的整数倍,当前长度为:%d",block.BlockSize(), len(src)) } var dst []byte tmpData := make([]byte, block.BlockSize()) for index := 0; index < len(src); index += block.BlockSize() { block.Encrypt(tmpData, src[index:index+block.BlockSize()]) dst = append(dst, tmpData...) } return dst, nil } // PKCS5填充 func PKCS5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } // 去除PKCS5填充 func PKCS5UnPadding(origData []byte) ([]byte, error) { length := len(origData) unpadding := int(origData[length-1]) if length < unpadding { return nil, fmt.Errorf("invalid unpadding length") } return origData[:(length - unpadding)], nil } // 秘钥长度验证 func validKey(key []byte) bool { k := len(key) switch k { default: return false case 16, 24, 32: return true } } func TestAes(t *testing.T){ srcData := "hello world !" key := []byte("abcdabcdabcdabcdabcdabcdabcdabcd") //测试加密 encData ,err := ECBEncrypt([]byte(srcData),(key)) if err != nil { t.Errorf(err.Error()) return } //测试解密 decData ,err := ECBDecrypt(encData,key) if err != nil { t.Errorf(err.Error()) return } t.Log(string(decData)) }
到此这篇关于Golang实现密码加密的示例详解的文章就介绍到这了,更多相关Golang密码加密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
golang channel多协程通信常用方法底层原理全面解析
channel 是 goroutine 与 goroutine 之间通信的重要桥梁,借助 channel,我们能很轻易的写出一个多协程通信程序,今天,我们就来看看这个 channel 的常用用法以及底层原理2023-09-09
最新评论