Golang對于用戶密碼的加密解決方案
對于用戶密碼的存儲一直以來都是用戶系統(tǒng)的重中之重,對安全性有較高要求的系統(tǒng)會采取雙端加密,即前端使用非對稱加密+Base64編碼,后端用私鑰解密后再采用不可逆的哈希算法進行加密。
筆者以前一直都是在用md5加鹽做玩具項目,直到最近才注意到這個問題,md5早就不再安全,雖然有動態(tài)加鹽之類的方案但或許還是有些太費力了,于是探索了一下其他的加密方案。
測試環(huán)境:
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" )
MD5
極致高效,CPU / 內(nèi)存消耗極低卻完全不安全(已被破解)
僅用于非敏感數(shù)據(jù)的哈希,絕對禁止用于密碼存儲;
實現(xiàn)
// 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)
}
}| 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| BenchmarkMD5-16 | 7181100 | 165.1 ns/op | 80 B/op | 3 allocs/op |
PBKDF2
CPU 密集型,中等安全,依賴迭代次數(shù)
實現(xiàn)
// ====================== PBKDF2 ======================
// CPU資源消耗高, 安全性中
// PBKDF2 核心配置
const (
PBKDF2Iterations = 100000 // 迭代次數(shù)
PBKDF2KeyLength = 32 // 密鑰長度 32字節(jié)=256位
SaltLength = 16 // 獨立鹽長度 16字節(jié)=128位
)
// CreatePBKDF2 生成PBKDF2哈希
// return: 鹽(base64):迭代次數(shù):哈希值(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, // 迭代次數(shù)
PBKDF2KeyLength, // 派生密鑰長度
sha256.New, // 底層哈希函數(shù)(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("存儲的哈希格式錯誤")
}
// 解析各部分參數(shù)
saltBase64 := parts[0]
iterStr := parts[1]
hashBase64 := parts[2]
// 解析迭代次數(shù)
iterations, err := strconv.Atoi(iterStr)
if err != nil {
return false, fmt.Errorf("解析迭代次數(shù)失敗: %w", err)
}
if iterations <= 0 {
return false, errors.New("迭代次數(shù)必須大于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("預(yù)生成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)
}
}
} | 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| 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 |
Argon2
CPU + 內(nèi)存雙密集型,最高安全(2015 年密碼哈希競賽冠軍,專門抵御 GPU/ASIC 暴力破解)
適合金融、政務(wù)此類高安全的系統(tǒng),需要注意高并發(fā)下內(nèi)存資源的管控
實現(xiàn)
// ====================== Argon2 ======================
// CPU+內(nèi)存資源消耗高, 安全性高
// Argon2 配置
const (
argon2Time = 3 // 迭代次數(shù)
argon2Memory = 64 * 1024 // 內(nèi)存占用(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),
)
// 拼接參數(shù)
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) {
// 拆分參數(shù)
parts := strings.Split(storedHash, ":")
if len(parts) != 5 {
return false, fmt.Errorf("哈希格式錯誤, 需5部分, 實際%d部分", len(parts))
}
// 拆分參數(shù)
saltB64 := parts[0]
timeStr := parts[1]
memoryStr := parts[2]
threadsStr := parts[3]
hashB64 := parts[4]
// 解析數(shù)值參數(shù)
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次、內(nèi)存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("預(yù)生成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)
}
}
}| 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| 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 |
scrypt
內(nèi)存密集型,高安全,高并發(fā)場景下的權(quán)衡選擇
內(nèi)存占用比Argon2更小且安全性也足夠,在高并發(fā)場景下可能會用到
實現(xiàn)
// ====================== scrypt ======================
// 內(nèi)存資源消耗高, 安全性中
// scrypt 配置
const (
scryptN = 1 << 14 // 內(nèi)存因子(2^14=16384, 值越大內(nèi)存占用越高)
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
}性能測試
內(nèi)存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("預(yù)生成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)
}
}
}| 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| 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 |
bcrypt
CPU 密集型,中等安全,速度非常慢,好像是1999年的老東西了吧
實現(xiàn)
// ====================== bcrypt ======================
// 內(nèi)存資源消耗高, 安全性中, 速度慢
// 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("預(yù)生成bcrypt哈希失敗: %v", err)
}
b.StartTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = VerifyBcrypt(testPassword, hash)
}
}| 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| 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 |
測試總結(jié)
5種加密方案各具特色,但 md5/bcrypt 雖然實現(xiàn)簡單但是具有明顯劣勢不應(yīng)該被選擇。
在高安全性要求的系統(tǒng)中選用 Argon2 是最優(yōu)方案,但需要對登錄/注冊接口的流量進行一定的限制避免資源耗盡。
而平衡安全性和成本的情況下 scrypt 是最優(yōu)方案。
PBKDF2 對內(nèi)存幾乎沒有需求,在小型系統(tǒng)種是最優(yōu)方案。
| 測試名稱 | 迭代次數(shù) | 單次操作耗時 | 單次內(nèi)存分配 | 單次分配次數(shù) |
| 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("解析迭代次數(shù)失敗: %w", err)
}
if iterations <= 0 {
return false, errors.New("迭代次數(shù)必須大于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("預(yù)生成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("預(yù)生成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("預(yù)生成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("預(yù)生成bcrypt哈希失敗: %v", err)
}
b.StartTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = VerifyBcrypt(testPassword, hash)
}
}到此這篇關(guān)于探索Golang對于用戶密碼的加密方案的文章就介紹到這了,更多相關(guān)goland密碼加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang運行報錯找不到包:package?xxx?is?not?in?GOROOT的解決過程
這篇文章主要給大家介紹了關(guān)于Golang運行報錯找不到包:package?xxx?is?not?in?GOROOT的解決過程,文中通過圖文介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-07-07
Go?處理大數(shù)組使用?for?range?和?for?循環(huán)的區(qū)別
這篇文章主要介紹了Go處理大數(shù)組使用for?range和for循環(huán)的區(qū)別,對于遍歷大數(shù)組而言,for循環(huán)能比for?range循環(huán)更高效與穩(wěn)定,這一點在數(shù)組元素為結(jié)構(gòu)體類型更加明顯,下文具體分析感興趣得小伙伴可以參考一下2022-05-05
golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法
今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08
Golang實現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實例探究
這篇文章主要為大家介紹了Golang實現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01

