目 录CONTENT

文章目录
Go

go实现服务器中密码存储方案--argon2

过客
2025-11-19 / 0 评论 / 1 点赞 / 6 阅读 / 0 字

还记得2011年CSDN密码泄漏事件么,当被暴出来密码是用明文存储时,我都惊呆了。
我们当时用的是md5(盐值+密码),在当年那个时代的算力还算相对安全。但到现在GPU的算力,也不安全了。
Hashcat数据使用 RTX 4090 的实测数据对MD5破解速度已达​200–220 GH/s 约 2000 亿次/秒,如果加上集群,一般的密码分分种就破解了。所以呢,像MD5、SHA-256这种能被算力加速的摘要算法不太适合存储密码了。​

🛠️推荐方案

永远不要在前端存储明文密码,前端只负责收集,后端负责哈希,使用HTTPS加密传输。

graph LR; A[前端用户输入明文]-->B[HTTPS]; B-->C[后端 Argon2 哈希存储];

📊 性能 vs 安全权衡建议

场景 推荐 Argon2 参数 预期耗时
Web 应用登录 t=3, m=64MB, p=1 300–600 ms
高安全系统 t=5, m=128MB, p=2 800–1500 ms
移动/IoT 设备 t=2, m=16MB, p=1 100–300 ms

对于配置不是很高,但用户数并发的较高的环境,可以适当降低内存参数。实测在4核8G阿里云ESC上只跑简单逻辑Web应用时,高并发登录测试。

参数 平均耗时 95%响应时间 最大稳定并发
t=3, m=64MB, p=1 520 ms 780 ms ~80
t=2, m=19MB, p=1 280 ms 420 ms ~250
t=1, m=16MB, p=1 150 ms 220 ms ~400

对于硬件配置不高的一般Web应用,可以将参数设置为t=2, m=19MB, p=1,支持 200+ 并发登录 而不卡顿,用户感知延迟 < 0.5 秒

🔢 Go代码实现

喜欢直接贴完整的Demo代码

package main

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"fmt"
	"log"
	"strings"

	"golang.org/x/crypto/argon2"
)

// PasswordHashParams 定义密码哈希参数
type PasswordHashParams struct {
	time    uint32
	memory  uint32
	threads uint8
	keyLen  uint32
}

// User 表示用户信息
type User struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Password string `json:"password"` // 存储哈希后的密码
}

// 默认哈希参数
var defaultParams = &PasswordHashParams{
	time:    3,         // Argon2算法的运行时间
	memory:  64 * 1024, // 内存使用量 (64MB)
	threads: 4,         // 并行线程数
	keyLen:  32,        // 输出密钥长度
}

// GenerateSalt 生成随机盐值
func GenerateSalt() ([]byte, error) {
	salt := make([]byte, 16) // 16字节的盐值
	_, err := rand.Read(salt)
	if err != nil {
		return nil, err
	}
	return salt, nil
}

// HashPassword 对密码进行哈希处理
func HashPassword(password string) (string, error) {
	// 生成盐值
	salt, err := GenerateSalt()
	if err != nil {
		return "", err
	}

	// 使用Argon2算法生成哈希
	hash := argon2.IDKey([]byte(password), salt, defaultParams.time, defaultParams.memory, defaultParams.threads, defaultParams.keyLen)

	// 将盐值和哈希值编码为Base64并组合
	b64Salt := base64.StdEncoding.EncodeToString(salt)
	b64Hash := base64.StdEncoding.EncodeToString(hash)

	// 返回格式: $argon2id$v=time,m=memory,t=threads$salt$hash
	return fmt.Sprintf("$argon2id$v=%d,m=%d,t=%d$%s$%s",
		defaultParams.time, defaultParams.memory, defaultParams.threads,
		b64Salt, b64Hash), nil
}

// VerifyPassword 验证密码是否正确
func VerifyPassword(password, hash string) (bool, error) {
	// 解析哈希字符串格式
	parts := strings.Split(hash, "$")
	if len(parts) != 5 {
		return false, fmt.Errorf("无效的哈希格式")
	}

	var time, memory, threads uint32
	_, err := fmt.Sscanf(parts[2], "v=%d,m=%d,t=%d", &time, &memory, &threads)
	if err != nil {
		return false, err
	}

	// 解码盐值和哈希值
	salt, err := base64.StdEncoding.DecodeString(parts[3])
	if err != nil {
		return false, err
	}

	expectedHash, err := base64.StdEncoding.DecodeString(parts[4])
	if err != nil {
		return false, err
	}

	// 重新计算哈希
	actualHash := argon2.IDKey([]byte(password), salt, time, memory, uint8(threads), uint32(len(expectedHash)))

	// 使用恒定时间比较防止时序攻击
	if subtle.ConstantTimeCompare(expectedHash, actualHash) == 1 {
		return true, nil
	}

	return false, nil
}

// PasswordManager 密码管理器
type PasswordManager struct {
	users map[string]*User
}

// NewPasswordManager 创建新的密码管理器
func NewPasswordManager() *PasswordManager {
	return &PasswordManager{
		users: make(map[string]*User),
	}
}

// RegisterUser 注册新用户
func (pm *PasswordManager) RegisterUser(username, password string) error {
	if _, exists := pm.users[username]; exists {
		return fmt.Errorf("用户名 %s 已存在", username)
	}

	// 验证密码强度(简单示例)
	if len(password) < 8 {
		return fmt.Errorf("密码长度至少为8位")
	}

	// 对密码进行哈希处理
	hashedPassword, err := HashPassword(password)
	if err != nil {
		return fmt.Errorf("密码哈希失败: %v", err)
	}

	// 创建新用户
	user := &User{
		ID:       len(pm.users) + 1,
		Username: username,
		Password: hashedPassword,
	}

	pm.users[username] = user
	return nil
}

// AuthenticateUser 验证用户登录
func (pm *PasswordManager) AuthenticateUser(username, password string) (bool, *User, error) {
	user, exists := pm.users[username]
	if !exists {
		// 为了防止用户枚举攻击,即使用户不存在也返回false
		// 这里我们返回错误,但在实际应用中应该统一处理
		return false, nil, fmt.Errorf("用户名或密码错误")
	}

	// 验证密码
	valid, err := VerifyPassword(password, user.Password)
	if err != nil {
		return false, nil, fmt.Errorf("密码验证失败: %v", err)
	}

	if valid {
		return true, user, nil
	}

	return false, nil, fmt.Errorf("用户名或密码错误")
}

// ChangePassword 更改用户密码
func (pm *PasswordManager) ChangePassword(username, oldPassword, newPassword string) error {
	user, exists := pm.users[username]
	if !exists {
		return fmt.Errorf("用户不存在")
	}

	// 验证旧密码
	valid, err := VerifyPassword(oldPassword, user.Password)
	if err != nil || !valid {
		return fmt.Errorf("旧密码不正确")
	}

	// 验证新密码强度
	if len(newPassword) < 8 {
		return fmt.Errorf("新密码长度至少为8位")
	}

	// 生成新密码哈希
	newHashedPassword, err := HashPassword(newPassword)
	if err != nil {
		return fmt.Errorf("密码哈希失败: %v", err)
	}

	// 更新密码
	user.Password = newHashedPassword
	return nil
}

func main() {
	// 创建密码管理器
	pm := NewPasswordManager()

	// 示例:注册用户
	fmt.Println("=== 用户注册示例 ===")
	err := pm.RegisterUser("zngw", "securePassword123")
	if err != nil {
		log.Printf("注册失败: %v", err)
	} else {
		fmt.Println("用户 zngw 注册成功")
	}

	err = pm.RegisterUser("guoke", "anotherSecurePass456")
	if err != nil {
		log.Printf("注册失败: %v", err)
	} else {
		fmt.Println("用户 guoke 注册成功")
	}

	// 尝试重复注册
	err = pm.RegisterUser("zngw", "differentPassword")
	if err != nil {
		fmt.Printf("重复注册失败(预期): %v\n", err)
	}

	fmt.Println("\n=== 用户登录验证示例 ===")

	// 正确密码登录
	valid, user, err := pm.AuthenticateUser("zngw", "securePassword123")
	if err != nil {
		fmt.Printf("登录失败: %v\n", err)
	} else if valid {
		fmt.Printf("用户 %s 登录成功,用户ID: %d\n", user.Username, user.ID)
	}

	// 错误密码登录
	valid, _, err = pm.AuthenticateUser("zngw", "wrongPassword")
	if err != nil {
		fmt.Printf("登录失败(预期): %v\n", err)
	}

	// 不存在的用户登录
	valid, _, err = pm.AuthenticateUser("zw", "anyPassword")
	if err != nil {
		fmt.Printf("登录失败(预期): %v\n", err)
	}

	fmt.Println("\n=== 密码更改示例 ===")

	// 更改密码
	err = pm.ChangePassword("zngw", "securePassword123", "newSecurePassword789")
	if err != nil {
		fmt.Printf("密码更改失败: %v\n", err)
	} else {
		fmt.Println("密码更改成功")
	}

	// 使用新密码登录
	valid, user, err = pm.AuthenticateUser("zngw", "newSecurePassword789")
	if err != nil {
		fmt.Printf("新密码登录失败: %v\n", err)
	} else if valid {
		fmt.Printf("用户 %s 使用新密码登录成功\n", user.Username)
	}

	// 使用旧密码登录(应该失败)
	valid, _, err = pm.AuthenticateUser("zngw", "securePassword123")
	if err != nil {
		fmt.Printf("旧密码登录失败(预期): %v\n", err)
	}

	fmt.Println("\n=== 密码强度验证示例 ===")

	// 尝试注册弱密码
	err = pm.RegisterUser("weakuser", "123")
	if err != nil {
		fmt.Printf("弱密码注册失败(预期): %v\n", err)
	}

	// 密码哈希安全性演示
	fmt.Println("\n=== 密码哈希安全性演示 ===")
	password1 := "samePassword"
	password2 := "samePassword" // 相同密码
	hash1, err := HashPassword(password1)
	if err != nil {
		log.Printf("哈希1失败: %v", err)
		return
	}

	hash2, err := HashPassword(password2)
	if err != nil {
		log.Printf("哈希2失败: %v", err)
		return
	}

	fmt.Printf("相同密码的不同哈希:\n")
	fmt.Printf("哈希1: %s\n", hash1)
	fmt.Printf("哈希2: %s\n", hash2)
	fmt.Println("注意: 即使密码相同,哈希值也不同(因为使用了不同的随机盐值)")

	// 验证两个哈希都能正确验证密码
	valid1, _ := VerifyPassword(password1, hash1)
	valid2, _ := VerifyPassword(password2, hash2)
	fmt.Printf("哈希1验证结果: %t\n", valid1)
	fmt.Printf("哈希2验证结果: %t\n", valid2)
}

运行结果:

1
Go
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区