朱培民 發表於 2025-12-29 10:39:32

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

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">MD5</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">实现</a></li><li><a href="#_lab2_0_1">性能测试</a></li></ul><li><a href="#_label1">PBKDF2</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_2">实现</a></li><li><a href="#_lab2_1_3">性能测试</a></li></ul><li><a href="#_label2">Argon2</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_4">实现</a></li><li><a href="#_lab2_2_5">性能测试</a></li></ul><li><a href="#_label3">scrypt</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_6">实现</a></li><li><a href="#_lab2_3_7">性能测试</a></li></ul><li><a href="#_label4">bcrypt</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_8">实现</a></li><li><a href="#_lab2_4_9">性能测试</a></li></ul><li><a href="#_label5">测试总结</a></li><ul class="second_class_ul"></ul><li><a href="#_label6">完整代码</a></li><ul class="second_class_ul"></ul></ul></div><p>对于用户密码的存储一直以来都是用户系统的重中之重,对安全性有较高要求的系统会采取双端加密,即前端使用非对称加密+Base64编码,后端用私钥解密后再采用不可逆的哈希算法进行加密。</p>
<p>笔者以前一直都是在用md5加盐做玩具项目,直到最近才注意到这个问题,md5早就不再安全,虽然有动态加盐之类的方案但或许还是有些太费力了,于是探索了一下其他的加密方案。</p>
<p><strong>测试环境:</strong></p>
<p><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPU:</strong> AMD Ryzen 7 5800H with Radeon Graphics</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;go test -bench=. -benchmem</p>
<p><strong>使用库:</strong></p>
<div class="jb51code"><pre class="brush:go;">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"
)</pre></div>
<p class="maodian"><a name="_label0"></a></p><h2>MD5</h2>
<p>极致高效,CPU / 内存消耗极低却完全不安全(已被破解)</p>
<p>仅用于非敏感数据的哈希,<strong>绝对禁止用于密码存储</strong>;</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><p class="maodian"><a name="_lab2_1_2"></a></p><p class="maodian"><a name="_lab2_2_4"></a></p><p class="maodian"><a name="_lab2_3_6"></a></p><p class="maodian"><a name="_lab2_4_8"></a></p><h3>实现</h3>
<div class="jb51code"><pre class="brush:go;">// CreateMD5 MD5 加密
func CreateMD5(str string) string {
        h := md5.New()
        h.Write([]byte(str))
        return hex.EncodeToString(h.Sum(nil))
}</pre></div>
<p class="maodian"><a name="_lab2_0_1"></a></p><p class="maodian"><a name="_lab2_1_3"></a></p><p class="maodian"><a name="_lab2_2_5"></a></p><p class="maodian"><a name="_lab2_3_7"></a></p><p class="maodian"><a name="_lab2_4_9"></a></p><h3>性能测试</h3>
<div class="jb51code"><pre class="brush:go;">func BenchmarkMD5(b *testing.B) {
        pwd := testPassword + testAuthSalt
        b.ResetTimer()
        b.ReportAllocs()
        for i := 0; i &lt; b.N; i++ {
                _ = CreateMD5(pwd)
        }
}</pre></div>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkMD5-16</td><td>7181100</td><td>165.1 ns/op</td><td>80 B/op</td><td>3 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label1"></a></p><h2>PBKDF2</h2>
<p>CPU 密集型,中等安全,依赖迭代次数</p>
<h3>实现</h3>
<div class="jb51code"><pre class="brush:go;">// ====================== 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
        iterStr := parts
        hashBase64 := parts
        // 解析迭代次数
        iterations, err := strconv.Atoi(iterStr)
        if err != nil {
                return false, fmt.Errorf("解析迭代次数失败: %w", err)
        }
        if iterations &lt;= 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
}</pre></div>
<h3>性能测试</h3>
<p>100000次迭代、密钥长度32、盐长度16</p>
<div class="jb51code"><pre class="brush:go;">func BenchmarkPBKDF2_Create(b *testing.B) {
        b.StopTimer()
        AuthSalt = testAuthSalt
        b.StartTimer()
        b.ReportAllocs()
        for i := 0; i &lt; 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 &lt; b.N; i++ {
                _, err := VerifyPBKDF2(testPassword, hash)
                if err != nil {
                        b.Fatalf("PBKDF2验证失败: %v", err)
                }
        }
}      </pre></div>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkPBKDF2_Create-16</td><td>81</td><td>14668586 ns/op</td><td>1114 B/op</td><td>19 allocs/op</td></tr><tr><td>BenchmarkPBKDF2_Verify-16</td><td>80</td><td>14748718 ns/op</td><td>924 B/op</td><td>14 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label2"></a></p><h2>Argon2</h2>
<p>CPU + 内存双密集型,最高安全(2015 年密码哈希竞赛冠军,专门抵御 GPU/ASIC 暴力破解)</p>
<p>适合金融、政务此类高安全的系统,需要注意高并发下内存资源的管控</p>
<h3>实现</h3>
<div class="jb51code"><pre class="brush:go;">// ====================== 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
        timeStr := parts
        memoryStr := parts
        threadsStr := parts
        hashB64 := parts
        // 解析数值参数
        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
}</pre></div>
<h3>性能测试</h3>
<p>迭代3次、内存64MB、并行度4、派生密钥长度32、盐长度16</p>
<div class="jb51code"><pre class="brush:go;">func BenchmarkArgon2_Create(b *testing.B) {
        b.StopTimer()
        AuthSalt = testAuthSalt
        b.StartTimer()
        b.ReportAllocs()
        for i := 0; i &lt; 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 &lt; b.N; i++ {
                _, err := VerifyArgon2(testPassword, hash)
                if err != nil {
                        b.Fatalf("Argon2验证失败: %v", err)
                }
        }
}</pre></div>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkArgon2_Create-16</td><td>34</td><td>33258774 ns/op</td><td>67121944 B/op</td><td>104 allocs/op</td></tr><tr><td>BenchmarkArgon2_Verify-16</td><td>34</td><td>33207082 ns/op</td><td>67118572 B/op</td><td>92 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label3"></a></p><h2>scrypt</h2>
<p>内存密集型,高安全,高并发场景下的权衡选择</p>
<p>内存占用比Argon2更小且安全性也足够,在高并发场景下可能会用到</p>
<h3>实现</h3>
<div class="jb51code"><pre class="brush:go;">// ====================== scrypt ======================
// 内存资源消耗高, 安全性中
// scrypt 配置
const (
        scryptN      = 1 &lt;&lt; 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)
        originalHash, _ := base64.StdEncoding.DecodeString(parts)
        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
}</pre></div>
<h3>性能测试</h3>
<p>内存16MB、块大小8、并行度1、密钥长度32</p>
<div class="jb51code"><pre class="brush:go;">func BenchmarkScrypt_Create(b *testing.B) {
        b.StopTimer()
        AuthSalt = testAuthSalt
        b.StartTimer()
        b.ReportAllocs()
        for i := 0; i &lt; 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 &lt; b.N; i++ {
                _, err := VerifyScrypt(testPassword, hash)
                if err != nil {
                        b.Fatalf("scrypt验证失败: %v", err)
                }
        }
}</pre></div>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkScrypt_Create-16</td><td>37</td><td>30749478 ns/op</td><td>16784634 B/op</td><td>33 allocs/op</td></tr><tr><td>BenchmarkScrypt_Verify-16</td><td>37</td><td>30630054 ns/op</td><td>16781952 B/op</td><td>26 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label4"></a></p><h2>bcrypt</h2>
<p>CPU 密集型,中等安全,速度非常慢,好像是1999年的老东西了吧</p>
<h3>实现</h3>
<div class="jb51code"><pre class="brush:go;">// ====================== 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
}</pre></div>
<h3>性能测试</h3>
<div class="jb51code"><pre class="brush:go;">func BenchmarkBcrypt_Create(b *testing.B) {
        b.StopTimer()
        AuthSalt = testAuthSalt
        b.StartTimer()
        b.ReportAllocs()
        for i := 0; i &lt; 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 &lt; b.N; i++ {
                _ = VerifyBcrypt(testPassword, hash)
        }
}</pre></div>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkBcrypt_Create-16</td><td>5</td><td>208128060 ns/op</td><td>5824 B/op</td><td>13 allocs/op</td></tr><tr><td>BenchmarkBcrypt_Verify-16</td><td>5</td><td>209431180 ns/op</td><td>5339 B/op</td><td>13 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label5"></a></p><h2>测试总结</h2>
<p>5种加密方案各具特色,但 <strong>md5/bcrypt</strong> 虽然实现简单但是具有明显劣势不应该被选择。</p>
<p>在高安全性要求的系统中选用 <strong>Argon2 </strong>是最优方案,但需要对登录/注册接口的流量进行一定的限制避免资源耗尽。</p>
<p>而平衡安全性和成本的情况下 <strong>scrypt </strong>是最优方案。</p>
<p><strong>PBKDF2</strong> 对内存几乎没有需求,在小型系统种是最优方案。</p>
<table border="1" cellpadding="1" cellspacing="1"><tbody><tr><td>测试名称</td><td>迭代次数</td><td>单次操作耗时</td><td>单次内存分配</td><td>单次分配次数</td></tr><tr><td>BenchmarkMD5-16</td><td>7181100</td><td>165.1 ns/op</td><td>80 B/op</td><td>3 allocs/op</td></tr><tr><td>BenchmarkPBKDF2_Create-16</td><td>81</td><td>14668586 ns/op</td><td>1114 B/op</td><td>19 allocs/op</td></tr><tr><td>BenchmarkPBKDF2_Verify-16</td><td>80</td><td>14748718 ns/op</td><td>924 B/op</td><td>14 allocs/op</td></tr><tr><td>BenchmarkArgon2_Create-16</td><td>34</td><td>33258774 ns/op</td><td>67121944 B/op</td><td>104 allocs/op</td></tr><tr><td>BenchmarkArgon2_Verify-16</td><td>34</td><td>33207082 ns/op</td><td>67118572 B/op</td><td>92 allocs/op</td></tr><tr><td>BenchmarkScrypt_Create-16</td><td>37</td><td>30749478 ns/op</td><td>16784634 B/op</td><td>33 allocs/op</td></tr><tr><td>BenchmarkScrypt_Verify-16</td><td>37</td><td>30630054 ns/op</td><td>16781952 B/op</td><td>26 allocs/op</td></tr><tr><td>BenchmarkBcrypt_Create-16</td><td>5</td><td>208128060 ns/op</td><td>5824 B/op</td><td>13 allocs/op</td></tr><tr><td>BenchmarkBcrypt_Verify-16</td><td>5</td><td>209431180 ns/op</td><td>5339 B/op</td><td>13 allocs/op</td></tr></tbody></table>
<p class="maodian"><a name="_label6"></a></p><h2>完整代码</h2>
<div class="jb51code"><pre class="brush:go;">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
        iterStr := parts
        hashBase64 := parts
        iterations, err := strconv.Atoi(iterStr)
        if err != nil {
                return false, fmt.Errorf("解析迭代次数失败: %w", err)
        }
        if iterations &lt;= 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
        timeStr := parts
        memoryStr := parts
        threadsStr := parts
        hashB64 := parts
        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 &lt;&lt; 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)
        originalHash, _ := base64.StdEncoding.DecodeString(parts)
        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
}</pre></div>
<div class="jb51code"><pre class="brush:go;">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 &lt; b.N; i++ {
                _ = CreateMD5(pwd)
        }
}
func BenchmarkPBKDF2_Create(b *testing.B) {
        b.StopTimer()
        AuthSalt = testAuthSalt
        b.StartTimer()
        b.ReportAllocs()
        for i := 0; i &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; b.N; i++ {
                _ = VerifyBcrypt(testPassword, hash)
        }
}</pre></div>
頁: [1]
查看完整版本: Golang对于用户密码的加密解决方案