Golang对于用户密码的加密解决方案

作者:

文章目录
  • 极致高效,CPU / 内存消耗极低却完全不安全(已被破解) 仅用于非敏感数据的哈希,绝对禁止用于密码存储;
  • CPU 密集型,中等安全,依赖迭代次数
  • CPU + 内存双密集型,最高安全(2015 年密码哈希竞赛冠军,专门抵御 GPU/ASIC 暴力破解) 适合金融、政务此类高安全的系统,需要注意高并发下内存资源的管控
  • 内存密集型,高安全,高并发场景下的权衡选择 内存占用比Argon2更小且安全性也足够,在高并发场景下可能会用到
  • CPU 密集型,中等安全,速度非常慢,好像是1999年的老东西了吧
  • 5种加密方案各具特色,但 md5/bcrypt 虽然实现简单但是具有明显劣势不应该被选择。 在高安全性要求的系统中选用 Argon2 是最优方案,但需要对登录/注册接口的流量进行一定的限制避免资源耗尽。 而平衡安全性和成本的情况下 scrypt 是最优方案。 PBKDF2 对内存几乎没有需求,在小型系统种是最优方案。 测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数 BenchmarkMD5-16 7181100 165.1 ns/op 80 B/op 3 allocs/op BenchmarkPBKDF2_Create-16 81 14668586 ns/op 1114 B/op 19 allocs/op BenchmarkPBKDF2_Verify-16 80 14748718 ns/op 924 B/op 14 allocs/op BenchmarkArgon2_Create-16 34 33258774 ns/op 67121944 B/op 104 allocs/op BenchmarkArgon2_Verify-16 34 33207082 ns/op 67118572 B/op 92 allocs/op BenchmarkScrypt_Create-16 37 30749478 ns/op 16784634 B/op 33 allocs/op BenchmarkScrypt_Verify-16 37 30630054 ns/op 16781952 B/op 26 allocs/op BenchmarkBcrypt_Create-16 5 208128060 ns/op 5824 B/op 13 allocs/op BenchmarkBcrypt_Verify-16 5 209431180 ns/op 5339 B/op 13 allocs/op
  • package crypto import ( “crypto/md5” “crypto/rand” “crypto/sha256” “crypto/subtle” “encoding/base64” “encoding/hex” “errors” “fmt” “strconv” “strings” “golang.org/x/crypto/argon2” “golang.org/x/crypto/bcrypt” “golang.org/x/crypto/pbkdf2” “golang.org/x/crypto/scrypt” ) var AuthSalt = “GolangIsGood” // ====================== MD5 ====================== func CreateMD5(str string) string { h := md5.New() h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) } // ====================== PBKDF2 ====================== const ( PBKDF2Iterations = 100000 PBKDF2KeyLength = 32 SaltLength = 16 ) func CreatePBKDF2(password string) (string, error) { salt := make([]byte, SaltLength) n, err := rand.Read(salt) if err != nil { return “”, fmt.Errorf(“生成随机盐失败: %w”, err) } if n != SaltLength { return “”, errors.New(“生成的盐长度不足”) } globalSalt := AuthSalt passwordWithGlobalSalt := password + globalSalt hash := pbkdf2.Key( []byte(passwordWithGlobalSalt), salt, PBKDF2Iterations, PBKDF2KeyLength, sha256.New, ) saltBase64 := base64.StdEncoding.EncodeToString(salt) hashBase64 := base64.StdEncoding.EncodeToString(hash) storedStr := fmt.Sprintf(“%s:%d:%s”, saltBase64, PBKDF2Iterations, hashBase64) return storedStr, nil } func VerifyPBKDF2(password string, storedStr string) (bool, error) { parts := strings.Split(storedStr, “:”) if len(parts) != 3 { return false, errors.New(“存储的哈希格式错误”) } saltBase64 := parts[0] iterStr := parts[1] hashBase64 := parts[2] iterations, err := strconv.Atoi(iterStr) if err != nil { return false, fmt.Errorf(“解析迭代次数失败: %w”, err) } if iterations <= 0 { return false, errors.New(“迭代次数必须大于0”) } salt, err := base64.StdEncoding.DecodeString(saltBase64) if err != nil { return false, fmt.Errorf(“解码盐失败: %w”, err) } originalHash, err := base64.StdEncoding.DecodeString(hashBase64) if err != nil { return false, fmt.Errorf(“解码哈希值失败: %w”, err) } globalSalt := AuthSalt passwordWithGlobalSalt := password + globalSalt newHash := pbkdf2.Key( []byte(passwordWithGlobalSalt), salt, iterations, len(originalHash), sha256.New, ) if subtle.ConstantTimeCompare(newHash, originalHash) == 1 { return true, nil } return false, nil } // ====================== Argon2 ====================== const ( argon2Time = 3 argon2Memory = 64 * 1024 argon2Threads = 4 argon2KeyLen = 32 saltLen = 16 ) func CreateArgon2(password string) (string, error) { salt := make([]byte, saltLen) if _, err := rand.Read(salt); err != nil { return “”, fmt.Errorf(“生成盐失败: %w”, err) } globalSalt := AuthSalt pwd := []byte(password + globalSalt) hash := argon2.IDKey( pwd, salt, uint32(argon2Time), uint32(argon2Memory), uint8(argon2Threads), uint32(argon2KeyLen), ) saltB64 := base64.StdEncoding.EncodeToString(salt) hashB64 := base64.StdEncoding.EncodeToString(hash) storedStr := fmt.Sprintf(“%s:%d:%d:%d:%s”, saltB64, argon2Time, argon2Memory, argon2Threads, hashB64) return storedStr, nil } func VerifyArgon2(password, storedHash string) (bool, error) { parts := strings.Split(storedHash, “:”) if len(parts) != 5 { return false, fmt.Errorf(“哈希格式错误, 需5部分, 实际%d部分”, len(parts)) } saltB64 := parts[0] timeStr := parts[1] memoryStr := parts[2] threadsStr := parts[3] hashB64 := parts[4] time, err := strconv.Atoi(timeStr) if err != nil { return false, fmt.Errorf(“解析time失败: %w”, err) } memory, err := strconv.Atoi(memoryStr) if err != nil { return false, fmt.Errorf(“解析memory失败: %w”, err) } threads, err := strconv.Atoi(threadsStr) if err != nil { return false, fmt.Errorf(“解析threads失败: %w”, err) } salt, err := base64.StdEncoding.DecodeString(saltB64) if err != nil { return false, fmt.Errorf(“解码盐失败: %w”, err) } originalHash, err := base64.StdEncoding.DecodeString(hashB64) if err != nil { return false, fmt.Errorf(“解码哈希失败: %w”, err) } globalSalt := AuthSalt pwd := []byte(password + globalSalt) newHash := argon2.IDKey( pwd, salt, uint32(time), uint32(memory), uint8(threads), uint32(len(originalHash)), ) return subtle.ConstantTimeCompare(newHash, originalHash) == 1, nil } // ====================== scrypt ====================== const ( scryptN = 1 << 14 scryptR = 8 scryptP = 1 scryptKeyLen = 32 ) func CreateScrypt(password string) (string, error) { salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return “”, err } globalSalt := AuthSalt pwd := []byte(password + globalSalt) hash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, scryptKeyLen) if err != nil { return “”, err } saltB64 := base64.StdEncoding.EncodeToString(salt) hashB64 := base64.StdEncoding.EncodeToString(hash) return fmt.Sprintf(“%s:%s”, saltB64, hashB64), nil } func VerifyScrypt(password, storedHash string) (bool, error) { parts := strings.Split(storedHash, “:”) if len(parts) != 2 { return false, errors.New(“哈希格式错误”) } salt, _ := base64.StdEncoding.DecodeString(parts[0]) originalHash, _ := base64.StdEncoding.DecodeString(parts[1]) globalSalt := AuthSalt pwd := []byte(password + globalSalt) newHash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, len(originalHash)) if err != nil { return false, err } return subtle.ConstantTimeCompare(newHash, originalHash) == 1, nil } // ====================== bcrypt ====================== func CreateBcrypt(password string) (string, error) { globalSalt := AuthSalt pwd := []byte(password + globalSalt) cost := 12 hash, err := bcrypt.GenerateFromPassword(pwd, cost) if err != nil { return “”, err } return string(hash), nil } func VerifyBcrypt(password, storedHash string) bool { globalSalt := AuthSalt pwd := []byte(password + globalSalt) err := bcrypt.CompareHashAndPassword([]byte(storedHash), pwd) return err == nil } package crypto import ( “testing” ) const ( testPassword = “123456@gofurry” testAuthSalt = “GolangIsGood” ) // ====================== Benchmark ====================== func BenchmarkMD5(b *testing.B) { pwd := testPassword + testAuthSalt b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _ = CreateMD5(pwd) } } func BenchmarkPBKDF2_Create(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := CreatePBKDF2(testPassword) if err != nil { b.Fatalf(“PBKDF2加密失败: %v”, err) } } } func BenchmarkPBKDF2_Verify(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt hash, err := CreatePBKDF2(testPassword) if err != nil { b.Fatalf(“预生成PBKDF2哈希失败: %v”, err) } b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := VerifyPBKDF2(testPassword, hash) if err != nil { b.Fatalf(“PBKDF2验证失败: %v”, err) } } } func BenchmarkArgon2_Create(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := CreateArgon2(testPassword) if err != nil { b.Fatalf(“Argon2加密失败: %v”, err) } } } func BenchmarkArgon2_Verify(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt hash, err := CreateArgon2(testPassword) if err != nil { b.Fatalf(“预生成Argon2哈希失败: %v”, err) } b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := VerifyArgon2(testPassword, hash) if err != nil { b.Fatalf(“Argon2验证失败: %v”, err) } } } func BenchmarkScrypt_Create(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := CreateScrypt(testPassword) if err != nil { b.Fatalf(“scrypt加密失败: %v”, err) } } } func BenchmarkScrypt_Verify(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt hash, err := CreateScrypt(testPassword) if err != nil { b.Fatalf(“预生成scrypt哈希失败: %v”, err) } b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := VerifyScrypt(testPassword, hash) if err != nil { b.Fatalf(“scrypt验证失败: %v”, err) } } } func BenchmarkBcrypt_Create(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := CreateBcrypt(testPassword) if err != nil { b.Fatalf(“bcrypt加密失败: %v”, err) } } } func BenchmarkBcrypt_Verify(b *testing.B) { b.StopTimer() AuthSalt = testAuthSalt hash, err := CreateBcrypt(testPassword) if err != nil { b.Fatalf(“预生成bcrypt哈希失败: %v”, err) } b.StartTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _ = VerifyBcrypt(testPassword, hash) } } 到此这篇关于探索Golang对于用户密码的加密方案的文章就介绍到这了,更多相关goland密码加密内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Go使用SM3哈希算法和随机盐实现用户密码加密 Golang使用bcrypt实现密码加密和校验的操作代码 Go语言crypto包创建自己的密码加密工具实现示例 Golang常用的几种密码加密方式分享 Golang实现密码加密的示例详解 golang使用bcrypt包对密码进行加密的方法实现
  • 目录
    • MD5
      • 实现
      • 性能测试
    • PBKDF2
      • 实现
      • 性能测试
    • Argon2
      • 实现
      • 性能测试
    • scrypt
      • 实现
      • 性能测试
    • bcrypt
      • 实现
      • 性能测试
    • 测试总结
      • 完整代码

        对于用户密码的存储一直以来都是用户系统的重中之重,对安全性有较高要求的系统会采取双端加密,即前端使用非对称加密+Base64编码,后端用私钥解密后再采用不可逆的哈希算法进行加密。

        笔者以前一直都是在用md5加盐做玩具项目,直到最近才注意到这个问题,md5早就不再安全,虽然有动态加盐之类的方案但或许还是有些太费力了,于是探索了一下其他的加密方案。

        测试环境:

                CPU: AMD Ryzen 7 5800H with Radeon Graphics

                go test -bench=. -benchmem

        使用库:

        import (
        	"crypto/md5"
        	"crypto/rand"
        	"crypto/sha256"
        	"crypto/subtle"
        	"encoding/base64"
        	"encoding/hex"
        	"errors"
        	"fmt"
        	"strconv"
        	"strings"
        	"golang.org/x/crypto/argon2"
        	"golang.org/x/crypto/bcrypt"
        	"golang.org/x/crypto/pbkdf2"
        	"golang.org/x/crypto/scrypt"
        )

        极致高效,CPU / 内存消耗极低却完全不安全(已被破解)

        仅用于非敏感数据的哈希,绝对禁止用于密码存储

        // CreateMD5 MD5 加密
        func CreateMD5(str string) string {
        	h := md5.New()
        	h.Write([]byte(str))
        	return hex.EncodeToString(h.Sum(nil))
        }

        func BenchmarkMD5(b *testing.B) {
        	pwd := testPassword + testAuthSalt
        	b.ResetTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_ = CreateMD5(pwd)
        	}
        }
        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkMD5-16 7181100 165.1 ns/op 80 B/op 3 allocs/op

        CPU 密集型,中等安全,依赖迭代次数

        // ====================== PBKDF2 ======================
        // CPU资源消耗高, 安全性中
        // PBKDF2 核心配置
        const (
        	PBKDF2Iterations = 100000 // 迭代次数
        	PBKDF2KeyLength  = 32     // 密钥长度 32字节=256位
        	SaltLength       = 16     // 独立盐长度 16字节=128位
        )
        // CreatePBKDF2 生成PBKDF2哈希
        // return: 盐(base64):迭代次数:哈希值(base64)
        func CreatePBKDF2(password string) (string, error) {
        	// 生成每个用户的独立随机盐
        	salt := make([]byte, SaltLength)
        	n, err := rand.Read(salt)
        	if err != nil {
        		return "", fmt.Errorf("生成随机盐失败: %w", err)
        	}
        	if n != SaltLength {
        		return "", errors.New("生成的盐长度不足")
        	}
        	// 拼接全局盐
        	globalSalt := AuthSalt
        	passwordWithGlobalSalt := password + globalSalt
        	// 计算PBKDF2哈希
        	hash := pbkdf2.Key(
        		[]byte(passwordWithGlobalSalt), // 密码+全局盐
        		salt,                           // 独立随机盐
        		PBKDF2Iterations,               // 迭代次数
        		PBKDF2KeyLength,                // 派生密钥长度
        		sha256.New,                     // 底层哈希函数(HMAC-SHA256)
        	)
        	// 拼接存储字符串
        	saltBase64 := base64.StdEncoding.EncodeToString(salt)
        	hashBase64 := base64.StdEncoding.EncodeToString(hash)
        	storedStr := fmt.Sprintf("%s:%d:%s", saltBase64, PBKDF2Iterations, hashBase64)
        	return storedStr, nil
        }
        // VerifyPBKDF2 验证密码是否匹配PBKDF2哈希
        // password: 明文密码
        // storedStr: 加密后的哈希字符串
        func VerifyPBKDF2(password string, storedStr string) (bool, error) {
        	// 拆分存储的字符串
        	parts := strings.Split(storedStr, ":")
        	if len(parts) != 3 {
        		return false, errors.New("存储的哈希格式错误")
        	}
        	// 解析各部分参数
        	saltBase64 := parts[0]
        	iterStr := parts[1]
        	hashBase64 := parts[2]
        	// 解析迭代次数
        	iterations, err := strconv.Atoi(iterStr)
        	if err != nil {
        		return false, fmt.Errorf("解析迭代次数失败: %w", err)
        	}
        	if iterations <= 0 {
        		return false, errors.New("迭代次数必须大于0")
        	}
        	// 解码盐
        	salt, err := base64.StdEncoding.DecodeString(saltBase64)
        	if err != nil {
        		return false, fmt.Errorf("解码盐失败: %w", err)
        	}
        	// 解码原始哈希值
        	originalHash, err := base64.StdEncoding.DecodeString(hashBase64)
        	if err != nil {
        		return false, fmt.Errorf("解码哈希值失败: %w", err)
        	}
        	// 拼接全局盐
        	globalSalt := AuthSalt
        	passwordWithGlobalSalt := password + globalSalt
        	// 重新计算哈希
        	newHash := pbkdf2.Key(
        		[]byte(passwordWithGlobalSalt),
        		salt,
        		iterations,
        		len(originalHash),
        		sha256.New,
        	)
        	// 安全比较哈希, 等价于 hmac.Equal(newHash, originalHash)
        	if subtle.ConstantTimeCompare(newHash, originalHash) == 1 {
        		return true, nil
        	}
        	return false, nil
        }

        100000次迭代、密钥长度32、盐长度16

        func BenchmarkPBKDF2_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreatePBKDF2(testPassword)
        		if err != nil {
        			b.Fatalf("PBKDF2加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkPBKDF2_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreatePBKDF2(testPassword)
        	if err != nil {
        		b.Fatalf("预生成PBKDF2哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyPBKDF2(testPassword, hash)
        		if err != nil {
        			b.Fatalf("PBKDF2验证失败: %v", err)
        		}
        	}
        }        
        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkPBKDF2_Create-16 81 14668586 ns/op 1114 B/op 19 allocs/op
        BenchmarkPBKDF2_Verify-16 80 14748718 ns/op 924 B/op 14 allocs/op

        CPU + 内存双密集型,最高安全(2015 年密码哈希竞赛冠军,专门抵御 GPU/ASIC 暴力破解)

        适合金融、政务此类高安全的系统,需要注意高并发下内存资源的管控

        // ====================== Argon2 ======================
        // CPU+内存资源消耗高, 安全性高
        // Argon2 配置
        const (
        	argon2Time    = 3         // 迭代次数
        	argon2Memory  = 64 * 1024 // 内存占用(64MB)
        	argon2Threads = 4         // 并行度
        	argon2KeyLen  = 32        // 派生密钥长度
        	saltLen       = 16        // 盐长度
        )
        // CreateArgon2 生成 Argon2id 哈希
        func CreateArgon2(password string) (string, error) {
        	// 生成随机盐
        	salt := make([]byte, saltLen)
        	if _, err := rand.Read(salt); err != nil {
        		return "", fmt.Errorf("生成盐失败: %w", err)
        	}
        	// 计算 Argon2id 哈希
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	hash := argon2.IDKey(
        		pwd,
        		salt,
        		uint32(argon2Time),
        		uint32(argon2Memory),
        		uint8(argon2Threads),
        		uint32(argon2KeyLen),
        	)
        	// 拼接参数
        	saltB64 := base64.StdEncoding.EncodeToString(salt)
        	hashB64 := base64.StdEncoding.EncodeToString(hash)
        	storedStr := fmt.Sprintf("%s:%d:%d:%d:%s",
        		saltB64, argon2Time, argon2Memory, argon2Threads, hashB64)
        	return storedStr, nil
        }
        // VerifyArgon2 验证 Argon2 哈希
        func VerifyArgon2(password, storedHash string) (bool, error) {
        	// 拆分参数
        	parts := strings.Split(storedHash, ":")
        	if len(parts) != 5 {
        		return false, fmt.Errorf("哈希格式错误, 需5部分, 实际%d部分", len(parts))
        	}
        	// 拆分参数
        	saltB64 := parts[0]
        	timeStr := parts[1]
        	memoryStr := parts[2]
        	threadsStr := parts[3]
        	hashB64 := parts[4]
        	// 解析数值参数
        	time, err := strconv.Atoi(timeStr)
        	if err != nil {
        		return false, fmt.Errorf("解析time失败: %w", err)
        	}
        	memory, err := strconv.Atoi(memoryStr)
        	if err != nil {
        		return false, fmt.Errorf("解析memory失败: %w", err)
        	}
        	threads, err := strconv.Atoi(threadsStr)
        	if err != nil {
        		return false, fmt.Errorf("解析threads失败: %w", err)
        	}
        	// 解码盐和原始哈希
        	salt, err := base64.StdEncoding.DecodeString(saltB64)
        	if err != nil {
        		return false, fmt.Errorf("解码盐失败: %w", err)
        	}
        	originalHash, err := base64.StdEncoding.DecodeString(hashB64)
        	if err != nil {
        		return false, fmt.Errorf("解码哈希失败: %w", err)
        	}
        	// 重新计算哈希
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	newHash := argon2.IDKey(
        		pwd,
        		salt,
        		uint32(time),
        		uint32(memory),
        		uint8(threads),
        		uint32(len(originalHash)),
        	)
        	// 安全比较
        	if subtle.ConstantTimeCompare(newHash, originalHash) == 1 {
        		return true, nil
        	}
        	return false, nil
        }

        迭代3次、内存64MB、并行度4、派生密钥长度32、盐长度16

        func BenchmarkArgon2_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateArgon2(testPassword)
        		if err != nil {
        			b.Fatalf("Argon2加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkArgon2_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateArgon2(testPassword)
        	if err != nil {
        		b.Fatalf("预生成Argon2哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyArgon2(testPassword, hash)
        		if err != nil {
        			b.Fatalf("Argon2验证失败: %v", err)
        		}
        	}
        }
        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkArgon2_Create-16 34 33258774 ns/op 67121944 B/op 104 allocs/op
        BenchmarkArgon2_Verify-16 34 33207082 ns/op 67118572 B/op 92 allocs/op

        内存密集型,高安全,高并发场景下的权衡选择

        内存占用比Argon2更小且安全性也足够,在高并发场景下可能会用到

        // ====================== scrypt ======================
        // 内存资源消耗高, 安全性中
        // scrypt 配置
        const (
        	scryptN      = 1 << 14 // 内存因子(2^14=16384, 值越大内存占用越高)
        	scryptR      = 8       // 块大小
        	scryptP      = 1       // 并行因子
        	scryptKeyLen = 32      // 密钥长度
        )
        // CreateScrypt 生成 scrypt 哈希
        func CreateScrypt(password string) (string, error) {
        	// 生成随机盐
        	salt := make([]byte, 16)
        	if _, err := rand.Read(salt); err != nil {
        		return "", err
        	}
        	// 计算 scrypt 哈希
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	hash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, scryptKeyLen)
        	if err != nil {
        		return "", err
        	}
        	// 拼接盐和哈希
        	saltB64 := base64.StdEncoding.EncodeToString(salt)
        	hashB64 := base64.StdEncoding.EncodeToString(hash)
        	return fmt.Sprintf("%s:%s", saltB64, hashB64), nil
        }
        // VerifyScrypt 验证 scrypt 哈希
        func VerifyScrypt(password, storedHash string) (bool, error) {
        	parts := strings.Split(storedHash, ":")
        	if len(parts) != 2 {
        		return false, errors.New("哈希格式错误")
        	}
        	salt, _ := base64.StdEncoding.DecodeString(parts[0])
        	originalHash, _ := base64.StdEncoding.DecodeString(parts[1])
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	newHash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, len(originalHash))
        	if err != nil {
        		return false, err
        	}
        	return subtle.ConstantTimeCompare(newHash, originalHash) == 1, nil
        }

        内存16MB、块大小8、并行度1、密钥长度32

        func BenchmarkScrypt_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateScrypt(testPassword)
        		if err != nil {
        			b.Fatalf("scrypt加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkScrypt_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateScrypt(testPassword)
        	if err != nil {
        		b.Fatalf("预生成scrypt哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyScrypt(testPassword, hash)
        		if err != nil {
        			b.Fatalf("scrypt验证失败: %v", err)
        		}
        	}
        }
        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkScrypt_Create-16 37 30749478 ns/op 16784634 B/op 33 allocs/op
        BenchmarkScrypt_Verify-16 37 30630054 ns/op 16781952 B/op 26 allocs/op

        CPU 密集型,中等安全,速度非常慢,好像是1999年的老东西了吧

        // ====================== bcrypt ======================
        // 内存资源消耗高, 安全性中, 速度慢
        // CreateBcrypt 生成 bcrypt 哈希
        func CreateBcrypt(password string) (string, error) {
        	// 密码+全局盐
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	// 成本因子
        	cost := 12
        	hash, err := bcrypt.GenerateFromPassword(pwd, cost)
        	if err != nil {
        		return "", err
        	}
        	return string(hash), nil
        }
        // VerifyBcrypt 验证 bcrypt 哈希
        func VerifyBcrypt(password, storedHash string) bool {
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	// bcrypt.CompareHashAndPassword 自动处理盐和成本因子
        	err := bcrypt.CompareHashAndPassword([]byte(storedHash), pwd)
        	return err == nil
        }

        func BenchmarkBcrypt_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateBcrypt(testPassword)
        		if err != nil {
        			b.Fatalf("bcrypt加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkBcrypt_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateBcrypt(testPassword)
        	if err != nil {
        		b.Fatalf("预生成bcrypt哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_ = VerifyBcrypt(testPassword, hash)
        	}
        }
        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkBcrypt_Create-16 5 208128060 ns/op 5824 B/op 13 allocs/op
        BenchmarkBcrypt_Verify-16 5 209431180 ns/op 5339 B/op 13 allocs/op

        5种加密方案各具特色,但 md5/bcrypt 虽然实现简单但是具有明显劣势不应该被选择。

        在高安全性要求的系统中选用 Argon2 是最优方案,但需要对登录/注册接口的流量进行一定的限制避免资源耗尽。

        而平衡安全性和成本的情况下 scrypt 是最优方案。

        PBKDF2 对内存几乎没有需求,在小型系统种是最优方案。

        测试名称 迭代次数 单次操作耗时 单次内存分配 单次分配次数
        BenchmarkMD5-16 7181100 165.1 ns/op 80 B/op 3 allocs/op
        BenchmarkPBKDF2_Create-16 81 14668586 ns/op 1114 B/op 19 allocs/op
        BenchmarkPBKDF2_Verify-16 80 14748718 ns/op 924 B/op 14 allocs/op
        BenchmarkArgon2_Create-16 34 33258774 ns/op 67121944 B/op 104 allocs/op
        BenchmarkArgon2_Verify-16 34 33207082 ns/op 67118572 B/op 92 allocs/op
        BenchmarkScrypt_Create-16 37 30749478 ns/op 16784634 B/op 33 allocs/op
        BenchmarkScrypt_Verify-16 37 30630054 ns/op 16781952 B/op 26 allocs/op
        BenchmarkBcrypt_Create-16 5 208128060 ns/op 5824 B/op 13 allocs/op
        BenchmarkBcrypt_Verify-16 5 209431180 ns/op 5339 B/op 13 allocs/op

        package crypto
        import (
        	"crypto/md5"
        	"crypto/rand"
        	"crypto/sha256"
        	"crypto/subtle"
        	"encoding/base64"
        	"encoding/hex"
        	"errors"
        	"fmt"
        	"strconv"
        	"strings"
        	"golang.org/x/crypto/argon2"
        	"golang.org/x/crypto/bcrypt"
        	"golang.org/x/crypto/pbkdf2"
        	"golang.org/x/crypto/scrypt"
        )
        var AuthSalt = "GolangIsGood"
        // ====================== MD5 ======================
        func CreateMD5(str string) string {
        	h := md5.New()
        	h.Write([]byte(str))
        	return hex.EncodeToString(h.Sum(nil))
        }
        // ====================== PBKDF2 ======================
        const (
        	PBKDF2Iterations = 100000
        	PBKDF2KeyLength  = 32
        	SaltLength       = 16
        )
        func CreatePBKDF2(password string) (string, error) {
        	salt := make([]byte, SaltLength)
        	n, err := rand.Read(salt)
        	if err != nil {
        		return "", fmt.Errorf("生成随机盐失败: %w", err)
        	}
        	if n != SaltLength {
        		return "", errors.New("生成的盐长度不足")
        	}
        	globalSalt := AuthSalt
        	passwordWithGlobalSalt := password + globalSalt
        	hash := pbkdf2.Key(
        		[]byte(passwordWithGlobalSalt),
        		salt,
        		PBKDF2Iterations,
        		PBKDF2KeyLength,
        		sha256.New,
        	)
        	saltBase64 := base64.StdEncoding.EncodeToString(salt)
        	hashBase64 := base64.StdEncoding.EncodeToString(hash)
        	storedStr := fmt.Sprintf("%s:%d:%s", saltBase64, PBKDF2Iterations, hashBase64)
        	return storedStr, nil
        }
        func VerifyPBKDF2(password string, storedStr string) (bool, error) {
        	parts := strings.Split(storedStr, ":")
        	if len(parts) != 3 {
        		return false, errors.New("存储的哈希格式错误")
        	}
        	saltBase64 := parts[0]
        	iterStr := parts[1]
        	hashBase64 := parts[2]
        	iterations, err := strconv.Atoi(iterStr)
        	if err != nil {
        		return false, fmt.Errorf("解析迭代次数失败: %w", err)
        	}
        	if iterations <= 0 {
        		return false, errors.New("迭代次数必须大于0")
        	}
        	salt, err := base64.StdEncoding.DecodeString(saltBase64)
        	if err != nil {
        		return false, fmt.Errorf("解码盐失败: %w", err)
        	}
        	originalHash, err := base64.StdEncoding.DecodeString(hashBase64)
        	if err != nil {
        		return false, fmt.Errorf("解码哈希值失败: %w", err)
        	}
        	globalSalt := AuthSalt
        	passwordWithGlobalSalt := password + globalSalt
        	newHash := pbkdf2.Key(
        		[]byte(passwordWithGlobalSalt),
        		salt,
        		iterations,
        		len(originalHash),
        		sha256.New,
        	)
        	if subtle.ConstantTimeCompare(newHash, originalHash) == 1 {
        		return true, nil
        	}
        	return false, nil
        }
        // ====================== Argon2 ======================
        const (
        	argon2Time    = 3
        	argon2Memory  = 64 * 1024
        	argon2Threads = 4
        	argon2KeyLen  = 32
        	saltLen       = 16
        )
        func CreateArgon2(password string) (string, error) {
        	salt := make([]byte, saltLen)
        	if _, err := rand.Read(salt); err != nil {
        		return "", fmt.Errorf("生成盐失败: %w", err)
        	}
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	hash := argon2.IDKey(
        		pwd,
        		salt,
        		uint32(argon2Time),
        		uint32(argon2Memory),
        		uint8(argon2Threads),
        		uint32(argon2KeyLen),
        	)
        	saltB64 := base64.StdEncoding.EncodeToString(salt)
        	hashB64 := base64.StdEncoding.EncodeToString(hash)
        	storedStr := fmt.Sprintf("%s:%d:%d:%d:%s", saltB64, argon2Time, argon2Memory, argon2Threads, hashB64)
        	return storedStr, nil
        }
        func VerifyArgon2(password, storedHash string) (bool, error) {
        	parts := strings.Split(storedHash, ":")
        	if len(parts) != 5 {
        		return false, fmt.Errorf("哈希格式错误, 需5部分, 实际%d部分", len(parts))
        	}
        	saltB64 := parts[0]
        	timeStr := parts[1]
        	memoryStr := parts[2]
        	threadsStr := parts[3]
        	hashB64 := parts[4]
        	time, err := strconv.Atoi(timeStr)
        	if err != nil {
        		return false, fmt.Errorf("解析time失败: %w", err)
        	}
        	memory, err := strconv.Atoi(memoryStr)
        	if err != nil {
        		return false, fmt.Errorf("解析memory失败: %w", err)
        	}
        	threads, err := strconv.Atoi(threadsStr)
        	if err != nil {
        		return false, fmt.Errorf("解析threads失败: %w", err)
        	}
        	salt, err := base64.StdEncoding.DecodeString(saltB64)
        	if err != nil {
        		return false, fmt.Errorf("解码盐失败: %w", err)
        	}
        	originalHash, err := base64.StdEncoding.DecodeString(hashB64)
        	if err != nil {
        		return false, fmt.Errorf("解码哈希失败: %w", err)
        	}
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	newHash := argon2.IDKey(
        		pwd,
        		salt,
        		uint32(time),
        		uint32(memory),
        		uint8(threads),
        		uint32(len(originalHash)),
        	)
        	return subtle.ConstantTimeCompare(newHash, originalHash) == 1, nil
        }
        // ====================== scrypt ======================
        const (
        	scryptN      = 1 << 14
        	scryptR      = 8
        	scryptP      = 1
        	scryptKeyLen = 32
        )
        func CreateScrypt(password string) (string, error) {
        	salt := make([]byte, 16)
        	if _, err := rand.Read(salt); err != nil {
        		return "", err
        	}
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	hash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, scryptKeyLen)
        	if err != nil {
        		return "", err
        	}
        	saltB64 := base64.StdEncoding.EncodeToString(salt)
        	hashB64 := base64.StdEncoding.EncodeToString(hash)
        	return fmt.Sprintf("%s:%s", saltB64, hashB64), nil
        }
        func VerifyScrypt(password, storedHash string) (bool, error) {
        	parts := strings.Split(storedHash, ":")
        	if len(parts) != 2 {
        		return false, errors.New("哈希格式错误")
        	}
        	salt, _ := base64.StdEncoding.DecodeString(parts[0])
        	originalHash, _ := base64.StdEncoding.DecodeString(parts[1])
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	newHash, err := scrypt.Key(pwd, salt, scryptN, scryptR, scryptP, len(originalHash))
        	if err != nil {
        		return false, err
        	}
        	return subtle.ConstantTimeCompare(newHash, originalHash) == 1, nil
        }
        // ====================== bcrypt ======================
        func CreateBcrypt(password string) (string, error) {
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	cost := 12
        	hash, err := bcrypt.GenerateFromPassword(pwd, cost)
        	if err != nil {
        		return "", err
        	}
        	return string(hash), nil
        }
        func VerifyBcrypt(password, storedHash string) bool {
        	globalSalt := AuthSalt
        	pwd := []byte(password + globalSalt)
        	err := bcrypt.CompareHashAndPassword([]byte(storedHash), pwd)
        	return err == nil
        }
        package crypto
        import (
        	"testing"
        )
        const (
        	testPassword = "123456@gofurry"
        	testAuthSalt = "GolangIsGood"
        )
        // ====================== Benchmark ======================
        func BenchmarkMD5(b *testing.B) {
        	pwd := testPassword + testAuthSalt
        	b.ResetTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_ = CreateMD5(pwd)
        	}
        }
        func BenchmarkPBKDF2_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreatePBKDF2(testPassword)
        		if err != nil {
        			b.Fatalf("PBKDF2加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkPBKDF2_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreatePBKDF2(testPassword)
        	if err != nil {
        		b.Fatalf("预生成PBKDF2哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyPBKDF2(testPassword, hash)
        		if err != nil {
        			b.Fatalf("PBKDF2验证失败: %v", err)
        		}
        	}
        }
        func BenchmarkArgon2_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateArgon2(testPassword)
        		if err != nil {
        			b.Fatalf("Argon2加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkArgon2_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateArgon2(testPassword)
        	if err != nil {
        		b.Fatalf("预生成Argon2哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyArgon2(testPassword, hash)
        		if err != nil {
        			b.Fatalf("Argon2验证失败: %v", err)
        		}
        	}
        }
        func BenchmarkScrypt_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateScrypt(testPassword)
        		if err != nil {
        			b.Fatalf("scrypt加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkScrypt_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateScrypt(testPassword)
        	if err != nil {
        		b.Fatalf("预生成scrypt哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := VerifyScrypt(testPassword, hash)
        		if err != nil {
        			b.Fatalf("scrypt验证失败: %v", err)
        		}
        	}
        }
        func BenchmarkBcrypt_Create(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_, err := CreateBcrypt(testPassword)
        		if err != nil {
        			b.Fatalf("bcrypt加密失败: %v", err)
        		}
        	}
        }
        func BenchmarkBcrypt_Verify(b *testing.B) {
        	b.StopTimer()
        	AuthSalt = testAuthSalt
        	hash, err := CreateBcrypt(testPassword)
        	if err != nil {
        		b.Fatalf("预生成bcrypt哈希失败: %v", err)
        	}
        	b.StartTimer()
        	b.ReportAllocs()
        	for i := 0; i < b.N; i++ {
        		_ = VerifyBcrypt(testPassword, hash)
        	}
        }

        到此这篇关于探索Golang对于用户密码的加密方案的文章就介绍到这了,更多相关goland密码加密内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

        您可能感兴趣的文章:

        • Go使用SM3哈希算法和随机盐实现用户密码加密
        • Golang使用bcrypt实现密码加密和校验的操作代码
        • Go语言crypto包创建自己的密码加密工具实现示例
        • Golang常用的几种密码加密方式分享
        • Golang实现密码加密的示例详解
        • golang使用bcrypt包对密码进行加密的方法实现

        站内搜索