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> CPU:</strong> AMD Ryzen 7 5800H with Radeon Graphics</p>
<p> 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 < 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 <= 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 < 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)
}
}
} </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 < 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)
}
}
}</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 << 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 < 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)
}
}
}</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 < 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)
}
}</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 <= 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 << 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 < 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)
}
}</pre></div>
頁:
[1]