还记得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)
}
运行结果:

评论区